diff --git a/%E5%AE%9F%E8%A3%85%E8%AA%AC%E6%98%8E%E6%9B%B8.md b/%E5%AE%9F%E8%A3%85%E8%AA%AC%E6%98%8E%E6%9B%B8.md index 12a57b6..19c435a 100644 --- a/%E5%AE%9F%E8%A3%85%E8%AA%AC%E6%98%8E%E6%9B%B8.md +++ b/%E5%AE%9F%E8%A3%85%E8%AA%AC%E6%98%8E%E6%9B%B8.md @@ -1,7 +1,7 @@ --- title: 'BTRC Hub 仕様書' -subtitle: '現行仕様と計画仕様の統合版(2026-03-23)' -date: '2026-03-23' +subtitle: '現行仕様と計画仕様の統合版(2026-04-20)' +date: '2026-04-20' lang: ja-JP toc: true toc-depth: 2 @@ -11,6 +11,7 @@ geometry: - margin=22mm fontsize: 10pt mainfont: 'Noto Sans CJK JP' +monofont: 'Noto Sans Mono CJK JP' header-includes: - | \usepackage{longtable} @@ -24,66 +25,94 @@ header-includes: # 本書の位置づけ -本書は、**最新ソーススナップショット btrc-hub-feature_297.zip** を起点に、BTRC Hub の現行仕様を再構成したうえで、開発者ヒアリングによって得られた **未実装だが今後仕様書へ載せるべき内容** を統合したものである。 +本書は、2026-04-19 時点で置かれている資材と、2026-04-20 の追加開発者ヒアリングをもとに、BTRC Hub の仕様を最新化したものである。 -本書の役割は 2 つある。 +- `btrc-hub-main.zip`: 現行ソースコード +- `btrc-hub.wiki.zip`: 開発 Wiki +- `btrc-hub.issue.sql`: Gitea issue エクスポート +- `BTRC_Hub_仕様書_2026-03-23.md`: 既存仕様書 -1. **現行仕様の説明書** - 実際にコードへ存在する API・画面・永続データ・制約を整理する。 +本書の目的は、実装済みの事実と将来計画を分離して、開発・公開判断・今後の設計変更に耐える基準文書にすることである。 -2. **計画仕様の設計書** - 一般公開に向けて実装予定の機能、公開段階、運用方針、未確定論点を明文化する。 +## 情報の優先順位 -参照元は次の 5 系統である。 +| 優先順位 | 情報源 | 扱い | +| ---: | --- | --- | +| 1 | 現行ソースコード、`db/schema.rb`、ルーティング | 現行仕様の根拠 | +| 2 | フロントエンドの画面・型・API 呼出 | 画面仕様と実利用導線の根拠 | +| 3 | Wiki と issue | 設計意図、未実装計画、運用課題の根拠 | +| 4 | 既存仕様書 | 体系化済みの文脈として再利用 | -1. バックエンド実装(Rails 8 API) -2. フロントエンド実装(React + Vite) -3. db/schema.rb -4. 既存仕様書 BTRC_Hub_現行仕様書_2026-03-22.md -5. 2026-03-23 時点の開発者ヒアリング結果 - -情報の優先順位は明確である。 - -- **第 1 優先**: 現在のソースコードと db/schema.rb -- **第 2 優先**: フロントエンドの画面実装とルーティング -- **第 3 優先**: 開発者が明示した計画仕様 -- **第 4 優先**: 既存仕様書の文脈整理 - -したがって本書では、記述を次の 3 種に分ける。 +本書では、仕様を次の 3 種に分ける。 | 表記 | 意味 | | --- | --- | -| 現行仕様 | 現在のコードとスキーマで確認できるもの | -| 計画仕様(確定) | 実装は未了だが、方針として採用済のもの | -| 計画仕様(未確定) | 方向性はあるが、具体形が今後詰まるもの | +| 現行仕様 | 現在のコード、スキーマ、画面から確認できるもの | +| 計画仕様(確定) | 実装は未完だが方針として採用済みのもの | +| 計画仕様(未確定) | 方向性はあるが、実装方針・責務・公開範囲が未整理のもの | -この区別を曖昧にすると、「実装済み」と「願望」が混ざって腐る。 -本書はそこを分離し、**開発・公開判断・将来の実装順序づけ** に耐える文書とする。 +## 2026-03-23 版からの主要更新 +今回の最新化で、次の項目を現行仕様へ昇格した。 + +| 項目 | 2026-04-19 時点の扱い | +| --- | --- | +| 投稿版管理 `post_versions` | 実装済み。`/posts/versions` と耕作履歴画面で使用 | +| 投稿履歴からの復元 | フロントで実装済み。ただし復元は専用 API ではなく `PUT /posts/:id` を使う | +| 素材集 `materials` | API と画面が実装済み。ただしトップナビでは非表示 | +| 素材の版テーブル `material_versions` | テーブルのみ存在。モデル・記録処理・API は未接続 | +| Wiki 画像 `wiki_assets` | テーブルのみ存在。現行ルート・モデル・画面は未接続 | +| 上位タグ D&D 操作 | 投稿詳細サイドバーで実装済み。API は admin のみ許可 | +| タグ階層取得 `/tags/with-depth` | 実装済み。素材サイドバーで使用 | +| 利用規約 `/tos` | MDX ページとルートは存在。ただしトップナビでは非表示 | +| R2 ストレージ | production で Active Storage service `:r2` を使用 | +| whenever スケジュール | `nico:sync` と `post_similarity:calc` が `config/schedule.rb` に定義済み | +| `tag_similarities` | テーブル・モデル・計算タスクは存在。ただし現行 UI での明確な表示導線は薄い | +| 素材作成権限 | 公開時も guest に素材作成を許可する方針 | +| Wiki 画像記法 | 専用記法を採用する方針。具体案は `![alt](asset:1)` 系 | + +逆に、次のものは現行仕様として書くと嘘になる。 + +- Wiki 画像アップロードの実用 UI/API +- 素材履歴の表示・復元 +- 汎用設定 `settings` の取得・保存・反映 API +- BAN の認可強制 +- 投稿編集の排他制御 +- 別名タグ管理 UI +- Wiki 本文検索 +- Theatre の会場一覧・作成・編集 +- 管理画面 +- 通知機能 # システム概要 ## 目的 -BTRC Hub は、ぼざろクリーチャーシリーズ関連コンテンツへのリンクを集約し、タグ・Wiki・関連付けによって横断的に検索・整理するためのシステムである。対象はコンテンツ本体ではなく **リンク** であり、知識を共同編集しやすくすることが主眼にある。 +BTRC Hub は、ぼざろクリーチャーシリーズ関連コンテンツへのリンクを集約し、タグ・Wiki・素材・外部同期によって横断的に検索・整理するためのシステムである。 + +重要なのは、保持する対象がコンテンツ本体ではなく **リンクと付帯知識** である点である。開発 Wiki でも、danbooru 的な整理性は参考にしつつ、コンテンツ本体の転載ではなくリンク集約に留める方針が示されている。 -現行ソースから読み取れる中心目的は次の 4 点である。 +## 中核価値 -- 複数プラットフォーム上の関連コンテンツを横断管理すること -- タグ付けによって検索性と再発見性を上げること -- Wiki によってタグ単位の知識を蓄積すること -- ニコニコ外部情報を内部タグ体系へ取り込むこと +現行 BTRC Hub の中核価値は次の 5 つである。 + +1. 複数プラットフォーム上の関連コンテンツをリンクとして集約する。 +2. タグ・別名・上位タグ・外部タグ連携で再発見性を高める。 +3. Wiki によりタグ単位の説明と知識を蓄積する。 +4. ニコニコ外部 DB から投稿・タグ情報を日次同期する。 +5. 素材集・上映会など、整理基盤の周辺機能を育てる。 ## 非目的 -現時点のコードベースは、次を中核機能とはしていない。 +現行の実装思想上、次は中核目的ではない。 - 汎用 SNS 化 -- 本文コメント中心の会話サービス化 +- コメント欄中心の交流サービス化 - コンテンツ本体の保存・転載 -- 多人数配信基盤そのものの実装 +- 動画配信基盤そのものの実装 +- ニジカ Wiki や虹鹿園と同一目的の重複サービス化 -ただし後述のとおり、**上映会(Theatre)機能** はすでにソースへ入っており、完全な SNS ではないが共同視聴寄りの要素は持ち始めている。 +ただし、Theatre や素材集のように、整理基盤から派生する共同利用機能は実装され始めている。 ## 技術構成 @@ -91,276 +120,331 @@ BTRC Hub は、ぼざろクリーチャーシリーズ関連コンテンツへ | --- | --- | | バックエンド | Rails 8.0.2, MySQL 8, Active Storage | | フロントエンド | React 19.1, Vite 6.3, TypeScript 5.8 | -| 状態管理・通信 | TanStack Query 5, Axios | -| UI | Tailwind CSS 3, Framer Motion 12 | -| 補助ライブラリ | Nokogiri, MiniMagick, Discard, diff-lcs, MarkdownIt, react-markdown-editor-lite | -| バッチ | Rake task による同期・移行・類似度計算 | - -## 現行仕様の要点 - -現行ソースを一言で言うと、BTRC Hub は **タグ付きリンク集 UI を持つ知識統合基盤** である。核になっているのは次の 4 本柱である。 - -1. **投稿**: URL を中心としたリンクオブジェクト -2. **タグ**: カテゴリ・別名・上位タグ・外部連携を持つ知識単位 -3. **Wiki**: 行単位ストレージと改訂履歴を持つページ体系 -4. **上映会**: 単一会場内での共同視聴とコメント機能 - -2026-03-08 版から見て特に重要な更新点は次である。 - -- 投稿検索は **タイトル / URL / 各種日時 / 並び順** に対応した -- タグ検索画面が実装され、**詳細条件と並び替え** が入った -- タグ検索は **not: 接頭辞** による否定検索を内部的に扱う -- Theatre 系の **API と画面** が追加された -- tag_name_sanitisation_rules による **タグ名サニタイズ層** が入った -- 旧仕様書で「未実装」としていた項目の一部が、現在は実装済になっている +| 通信・状態管理 | Axios, TanStack Query 5 | +| UI | Tailwind CSS 3, Framer Motion 12, Radix UI 系コンポーネント | +| Markdown | react-markdown, remark-gfm, react-markdown-editor-lite, 独自 Wiki 自動リンク | +| D&D | @dnd-kit/core | +| 画像処理 | MiniMagick | +| 外部取得 | Nokogiri, open-uri, Node screenshot script | +| 論理削除 | discard | +| PDF/差分等 | diff-lcs | +| ストレージ | development/test は Disk、production は Cloudflare R2 相当の S3 互換ストレージ | +| バッチ | Rake task、whenever schedule | # 利用者と認証 ## ユーザモデル -users.role は文字列 enum であり、現行ロールは次の 3 種である。 +`users.role` は文字列 enum であり、現行ロールは次の 3 種である。 | ロール | 意味 | | --- | --- | -| guest | 閲覧中心。編集系は不可 | +| guest | 初期発行ユーザ。閲覧中心 | | member | 投稿・Wiki・タグ更新など通常の編集可 | -| admin | member 権限に加え、管理系操作可 | +| admin | member 権限に加え、上位タグ設定など管理寄り操作可 | + +`User#gte_member?` は `member` または `admin` を真とする。 ## 認証方式 認証は ID / パスワードではなく、**引継ぎコード** による軽量認証である。 -- クライアントは localStorage.user_code を保持する -- API 呼び出し時は X-Transfer-Code ヘッダで送信する -- サーバは users.inheritance_code と照合して current_user を決定する +- ユーザ作成時に UUID 形式の `inheritance_code` を発行する。 +- フロントは `localStorage.user_code` に保持する。 +- API 呼び出し時、`X-Transfer-Code` ヘッダへ付与する。 +- `ApplicationController#authenticate_user` はヘッダ値から `User.find_by(inheritance_code:)` する。 -ApplicationController の認証処理は非常に薄く、**ヘッダからコードを読むだけ** である。禁止ユーザや禁止 IP の強制チェックはここでは行っていない。 +この仕組みは簡単で UX は軽いが、通常のパスワード認証より防御面は薄い。公開段階では、権限範囲を絞る設計が必須である。 ## 初回利用フロー -現行フロントの起動フローは次である。 +現行フロントの起動時処理は次の通り。 -1. localStorage.user_code を確認する -2. 存在すれば POST /users/verify に送る -3. 無効なら POST /users で guest ユーザを新規発行する -4. 有効ならそのユーザ情報を採用する +1. `localStorage.user_code` を読む。 +2. 存在する場合は `POST /users/verify` で検証する。 +3. 無効なら `POST /users` で guest ユーザを新規発行する。 +4. 存在しない場合も `POST /users` で guest ユーザを発行する。 +5. API レスポンスは camelCase に変換してフロント型へ流す。 ## IP 紐づけ -POST /users/verify 実行時、サーバは request.remote_ip を 16 バイトのバイナリに変換し、ip_addresses と user_ips に記録する。したがって現行システムは、少なくとも内部的には **引継ぎコードと IP の対応を保持する設計** である。 +`POST /users/verify` 実行時、サーバは `request.remote_ip` を 16 バイトのバイナリへ変換し、`ip_addresses` と `user_ips` に記録する。 -## 注意点 +したがって、現行システムは **引継ぎコードと IP の対応を保持する土台** を持つ。ただし、`users.banned` と `ip_addresses.banned` はまだ認可処理へ強制接続されていない。 -- users.banned と ip_addresses.banned はスキーマ上に存在する -- しかし現スナップショットでは **禁止フラグを認可で強制する実装は見当たらない** -- guest から member / admin への昇格 API や UI も存在しない +## ユーザ設定画面 -つまり、ユーザ管理の土台はあるが、**運用ロジックはまだ薄い**。 +現行 UI の `/users/settings` では次を扱う。 + +- 表示名更新 +- 引継ぎコード表示 +- 引継ぎコード更新 +- 他ブラウザからの引継ぎ + +`settings` テーブルは存在するが、汎用設定の API・反映処理は未接続である。 # ドメインモデル -## 投稿 posts +## 投稿 `posts` -投稿は URL を中心にしたリンクオブジェクトであり、タイトル、サムネイル、元コンテンツの作成日時範囲、親投稿参照などを持つ。 +投稿は URL を中心にしたリンクオブジェクトである。 | 属性 | 概要 | | --- | --- | -| url | 一意な参照先 URL | -| title | 表示用タイトル | -| thumbnail | Active Storage で保持する実サムネイル | -| thumbnail_base | 外部取得元のサムネイル URL | -| parent_id | 親投稿参照 | -| uploaded_user_id | 投稿者。同期投稿時は NULL | -| original_created_from / original_created_before | 元コンテンツ作成日時の範囲 | +| `url` | 参照先 URL。一意。HTTP/HTTPS のみ | +| `title` | 表示用タイトル。NULL 可 | +| `thumbnail` | Active Storage 添付 | +| `thumbnail_base` | 外部サムネイル元 URL。NULL 可 | +| `parent_id` | 親投稿参照。現行では 1 親 | +| `uploaded_user_id` | 投稿者。同期投稿では NULL | +| `original_created_from` | 元コンテンツ作成日時の下限 | +| `original_created_before` | 元コンテンツ作成日時の上限(より前) | +| `created_at` / `updated_at` | 投稿レコードの作成・更新日時 | -### 現行ルール +### URL 正規化 -- URL は HTTP / HTTPS のみ許可 -- URL は保存前に正規化される - - 前後空白の除去 - - ホストの小文字化 - - パス末尾 / の除去 -- url は一意 -- original_created_from と original_created_before は、両方ある場合 from < before が必須 -- 添付サムネイルは 180x180 にリサイズされる +保存前に次を行う。 -## タグ tags と tag_names +- 前後空白の除去 +- HTTP/HTTPS URL であることの検証 +- host の小文字化 +- path 末尾 `/` の除去 -タグは 2 層構造で管理される。 +### オリジナル作成日時 -- tag_names: タグ名文字列そのもの -- tags: カテゴリや件数を持つ実体タグ +`original_created_from` と `original_created_before` が両方ある場合、`from < before` が必須である。 -この分離により、別名タグは tag_names.canonical_id で表現される。 +この仕様は「日時そのもの」ではなく、「その範囲内にリンク先コンテンツが公開された」ことを表す。ニコニコ同期では分単位で `from` と `before = from + 1 minute` を生成する。 -### カテゴリ +## 投稿版 `post_versions` -現行カテゴリは次の 7 種である。 +`post_versions` は投稿のスナップショット履歴である。 -| カテゴリ | 用途 | +| 属性 | 概要 | | --- | --- | -| deerjikist | ニジラー | -| meme | 原作・ネタ元・ミーム等 | -| character | キャラクター | -| general | 一般 | -| material | 素材 | -| meta | メタタグ | -| nico | ニコニコタグ | - -### タグ名と別名のルール - -- canonical_id が NULL の tag_name が実体名 -- canonical_id が非 NULL のものは別名 -- 別名の参照先も別名であってはならない -- 別名名には : を含めてはならない -- タグまたは Wiki を持つ tag_name は別名化できない +| `post_id` | 対象投稿 | +| `version_no` | 投稿ごとの版番号。1 以上 | +| `event_type` | `create` / `update` / `discard` / `restore` | +| `title` | 当該版のタイトル | +| `url` | 当該版の URL | +| `thumbnail_base` | 当該版のサムネイル元 URL | +| `tags` | 当該版のタグ名スナップショット。空白区切り | +| `parent_id` | 当該版の親投稿 | +| `original_created_from` / `original_created_before` | 当該版の元コンテンツ作成日時範囲 | +| `created_at` | 版作成日時 | +| `created_by_user_id` | 版作成ユーザ。bot 操作は NULL | -### タグ名サニタイズ +`PostVersion` は更新・削除不可の読み取り専用履歴として扱われる。`PostVersionRecorder` は投稿をロックし、直前版と同一スナップショットであれば `update` 版を増やさない。 + +### バックフィル仕様 + +`20260409123700_create_post_versions` では、既存の `post_tags` 履歴から投稿版を再構成する。 + +- 投稿ごとに `version_no = 1` の `create` 版を作る。 +- `post_tags.created_at` / `discarded_at` をイベントとして並べる。 +- 1 秒以内のタグ変更を同一バケットとしてまとめる。 +- 初回タグ付与が `posts.created_at + 1 second` 以内なら `create` 版へ統合する。 + +これは過去履歴の近似復元であり、過去の投稿本文変更を完全に再現するものではない。 + +## タグ `tags` とタグ名 `tag_names` + +タグは 2 層構造である。 + +- `tag_names`: 文字列としての名前。別名もここで扱う。 +- `tags`: カテゴリ・件数・実体としてのタグ。 + +`tags.tag_name_id` は一意であり、実体タグは canonical な `tag_names` を参照する。 + +### カテゴリ + +| カテゴリ | 表示名 | 用途 | +| --- | --- | --- | +| `deerjikist` | ニジラー | 投稿者・人物・作者 | +| `meme` | 原作・ネタ元・ミーム等 | 元ネタ、文脈、シリーズ | +| `character` | キャラクター | キャラクタ | +| `general` | 一般 | 通常タグ | +| `material` | 素材 | 素材タグ | +| `meta` | メタタグ | 運用・状態タグ | +| `nico` | ニコニコタグ | 外部ニコニコタグ | -tag_name_sanitisation_rules は、タグ名の禁則・置換ルールを優先度付きで持つ内部テーブルである。 +### 別名 -- source_pattern は正規表現文字列 -- replacement で置換を行う -- TagName のバリデーション時に TagNameSanitisationRule.sanitise が使われる -- TagNameSanitisationRule.apply! は既存タグ名の一括変換と統合も行う +`tag_names.canonical_id` が NULL のものは実体名、非 NULL のものは別名である。 -これは UI に出ていないが、**タグ命名の下位レイヤ** として重要である。 +制約は次の通り。 -### タグ正規化ルール +- 別名の参照先も別名であってはならない。 +- 別名名には `:` を含められない。 +- タグまたは Wiki を持つ `tag_name` は別名化できない。 +- 検索・投稿正規化・補完では canonical 名へ解決する。 +- `/tags/name/:name` は exact match であり、別名解決 API ではない。 + +### タグ名サニタイズ -投稿作成・更新時にタグ文字列は正規化される。 +`tag_name_sanitisation_rules` は、タグ名を正規表現置換する内部ルールである。 -- 既知プレフィクスでカテゴリ判定する - - general: / gen: - - deerjikist: / djk: - - meme: - - character: / chr: - - material: / mtr: - - meta: -- プレフィクス除去後、別名なら canonical 名へ正規化する -- 必要ならタグを自動生成する +- 主キーは `priority`。 +- `source_pattern` は正規表現。 +- `replacement` で置換する。 +- `TagName` バリデーションで `sanitise(name) == name` を要求する。 +- `apply!` は既存 `TagName` を一括変換し、衝突時はタグ統合または `TagName` 差し替えを行う。 + +この機能は UI には出ていない。鯖缶向けの内部運用層である。 ### 自動付与タグ -通常のタグ正規化では次の自動補完がある。 +`Tag.normalise_tags` は投稿作成・更新時に次を行う。 | 条件 | 自動付与 | | --- | --- | -| 正規化後タグ数が 10 未満 | タグ希望 | -| ニジラー系タグが 1 つも無い | ニジラー情報不詳 | +| `with_tagme: true` かつタグ数が 10 未満 | `タグ希望` | +| ニジラー系タグが 1 件もない | `ニジラー情報不詳` | + +投稿作成時は `with_tagme: true`。投稿更新時は `with_tagme: false` なので、更新時には `タグ希望` は自動付与されない。 ### ニコニコタグの扱い -手入力タグに nico: プレフィクスが含まれていた場合、Tag.normalise_tags は NicoTagNormalisationError を投げる。つまり、nico カテゴリは **通常の手入力経路では直接追加させない設計** である。 +通常投稿のタグ入力で `nico:` プレフィクスが含まれた場合、`Tag::NicoTagNormalisationError` が発生し、リクエストは `400 Bad Request` になる。 -## 投稿とタグの関係 post_tags +`nico` カテゴリは手入力で直接追加するものではなく、ニコニコ同期・ニコタグ連携経路で扱う。 -投稿とタグは多対多で結ばれ、関係自体が履歴対象である。 +## 投稿とタグの関係 `post_tags` -- 関係削除は discarded_at による論理削除 -- created_user_id と deleted_user_id を保持 -- 現在有効な (post_id, tag_id) は一意 -- post_count は counter_cache と削除時減算で保たれる +`post_tags` は投稿とタグの多対多関係であり、同時にタグ付与履歴の基礎である。 -post_tags には is_active と active_unique_key の生成列があり、有効レコード重複を DB レベルでも抑えている。 +- `discarded_at` による論理削除。 +- `created_user_id` と `deleted_user_id` を保持。 +- 有効な `(post_id, tag_id)` は一意。 +- `PostTag#discard_by!` は `tags.post_count` を減算する。 +- 物理削除は禁止される。 -## 上位タグ tag_implications +現在の履歴表示の主役は `post_versions` だが、`post_tags` にもタグ単位のイベント履歴が残る。 -タグには親子関係を付与できる。 +## 上位タグ `tag_implications` -- tag_id が子 -- parent_tag_id が親 -- (tag_id, parent_tag_id) は一意 +`tag_implications` は子タグと親タグの関係である。 + +- `tag_id`: 子タグ +- `parent_tag_id`: 親タグ +- `(tag_id, parent_tag_id)` は一意 - 自己参照は禁止 -Tag.expand_parent_tags により、投稿保存時は **親タグが再帰的に自動付与** される。 +投稿保存時には `Tag.expand_parent_tags` により、指定タグの親タグが再帰的に自動付与される。 + +現行 UI では、投稿詳細サイドバーでタグを D&D することにより、上位タグ設定・解除・カテゴリ変更を行える。成功には admin 権限が必要である。 -## ニコニコタグ連携 nico_tag_relations +## ニコタグ連携 `nico_tag_relations` -nico カテゴリのタグと、それ以外の内部タグを関連付けるテーブルである。 +`nico_tag_relations` は外部ニコニコタグと内部タグの橋渡しである。 -- nico_tag_id は nico カテゴリ必須 -- tag_id は nico カテゴリ禁止 +- `nico_tag_id` は `nico` カテゴリ必須。 +- `tag_id` は `nico` カテゴリ禁止。 +- `GET /tags/nico` でニコタグと連携先タグを取得する。 +- `PUT /tags/nico/:id` で連携先タグを更新する。 -これにより、外部ニコタグから内部タグ知識へ橋渡しできる。 +ニコニコ同期では、新しく付いたニコタグに対してだけ、既存の連携先内部タグを自動付与する。 -## ニジラー deerjikists +## ニジラー `deerjikists` -deerjikists は外部人物識別子と内部タグの対応表である。 +`deerjikists` は外部プラットフォーム上の人物識別子と内部タグを対応させる。 -- 主キーは (platform, code) の複合主キー -- platform は現状 nico / youtube -- 対応先タグは deerjikist カテゴリ必須 +- 主キーは `(platform, code)` の複合主キー。 +- `platform` は `nico` / `youtube`。 +- 対応先タグは `deerjikist` カテゴリ必須。 + +ニコニコ同期では、投稿者コードに対応する `deerjikist` があれば、そのタグを自動付与する。存在しない場合、既存内部タグにニジラー系タグが無ければ `ニジラー情報不詳` を付与する。 ## Wiki -Wiki は単なる本文持ちテーブルではなく、**ページ本体と改訂履歴を分離した版管理システム** である。 +Wiki は、ページ本体と改訂履歴を DB 内で持つ版管理システムである。 -### ページ wiki_pages +### `wiki_pages` -- tag_name_id がタイトルに対応 -- 1 タイトルにつき 1 ページ -- created_user_id / updated_user_id を保持 +- `tag_name_id` がタイトルに対応する。 +- 1 タイトルにつき 1 ページ。 +- `created_user_id` / `updated_user_id` を持つ。 +- `next_asset_no` を持つが、現行の画像アップロード UI/API は未接続。 -### 改訂 wiki_revisions +### `wiki_revisions` -- kind は content または redirect -- base_revision_id により改訂チェーンを持つ -- message を保持できる -- lines_count と tree_sha256 を持つ +- `kind` は `content` または `redirect`。 +- `base_revision_id` で改訂の親を持つ。 +- `message` を持てる。 +- `lines_count` と `tree_sha256` を持つ。 +- `redirect` の場合は `redirect_page_id` を持つ。 ### 行ストレージ -- 本文は wiki_revision_lines を介して行単位で保持される -- 各行本体は wiki_lines に sha256 付きで重複排除保存される +- 本文は `wiki_revision_lines` を介して行単位で構成される。 +- 行本文は `wiki_lines` に SHA-256 付きで保存される。 +- 同じ行は重複排除される。 -### 重要な意味 +これは単なる Markdown 本文 1 カラム保存ではない。DB 上に Git 風の行ストアを持つ構造である。 -現行 Wiki は、ページ本文を毎回丸ごと 1 カラムに保存する方式ではない。**改訂と行ストアを分離した Git 風の構造** を DB 上で持っている。 +## Wiki 画像 `wiki_assets` -## 閲覧済フラグ user_post_views +`wiki_assets` テーブルは存在する。 -ユーザ単位で投稿の閲覧済状態を管理する。 +| 属性 | 概要 | +| --- | --- | +| `wiki_page_id` | 対象ページ | +| `no` | ページ内画像番号 | +| `alt_text` | 代替テキスト | +| `sha256` | ファイル重複判定用 SHA-256 | +| `created_by_user_id` | 作成者 | -- 主キーは (user_id, post_id) の複合主キー -- 投稿詳細画面から付与 / 解除できる +ただし、2026-04-19 時点では `WikiAsset` モデル、controller、route、フロント実装が確認できない。したがって **スキーマ準備済み、機能未接続** と扱う。 -## 類似度 post_similarities, tag_similarities +2026-04-20 の開発者ヒアリングでは、Wiki 画像は通常 URL ではなく **専用記法** で扱う方針になった。候補は `![代替テキスト](asset:1)` のように、ページ内画像番号または asset 識別子を Markdown の URL 部へ入れる方式である。実装時は、Markdown レンダラ側で `asset:` スキームを検出し、対象ページの `wiki_assets.no` から Active Storage URL へ解決する。 -投稿とタグには事前計算された類似度テーブルがある。 +## 素材 `materials` -- cos はコサイン類似度 -- 各行の主キーは (対象, 相手) の複合主キー -- 計算結果は上位 20 件のみ保持する +`materials` は素材集の実体である。 -## 上映会 theatres, theatre_comments, theatre_watching_users +| 属性 | 概要 | +| --- | --- | +| `url` | 参考 URL。NULL 可 | +| `file` | Active Storage 添付。画像・動画・音声を主に想定 | +| `tag_id` | 対応タグ。現行モデルでは一意 | +| `parent_id` | 親素材 | +| `created_by_user_id` / `updated_by_user_id` | 作成・更新ユーザ | +| `discarded_at` | 論理削除 | +| `active_url` | 未削除時のみ URL を返す生成列。一意制約あり | -Theatre は、共同視聴用の会場を表すドメインである。 +`Material` は `url` または `file` のどちらかを必須とする。タグは `character` または `material` カテゴリを許容する。これは単なる実装の緩さではなく、`character` を「下位区分としての material を包括する代表素材タグ」として扱う設計判断である。したがって、素材タグとしては `material` が基本だが、代表素材・キャラクタ素材の受け皿として `character` も許容する。 -### theatres +`material_versions` テーブルは存在するが、モデル・履歴記録・API・画面は未接続である。 -| 属性 | 概要 | -| --- | --- | -| name | 会場名 | -| opens_at / closes_at | 公開期間 | -| kind | 種別。現スナップショットでは未活用 | -| current_post_id | 現在上映中の投稿 | -| current_post_started_at | 再生開始時刻 | -| next_comment_no | コメント番号の採番用 | -| host_user_id | 現在のホスト | -| created_by_user_id | 作成者 | +## 閲覧済 `user_post_views` -### theatre_comments +ユーザ単位で投稿の閲覧済状態を管理する。 + +- 主キーは `(user_id, post_id)`。 +- 投稿詳細画面でボタンにより付与・解除できる。 +- 自動既読化の設定は将来構想としてあるが、現行は未接続。 + +## 類似度 `post_similarities` / `tag_similarities` + +投稿・タグには類似度の事前計算テーブルがある。 + +- `post_similarities`: 投稿に付いたタグ集合から投稿同士の cosine 類似度を計算。 +- `tag_similarities`: タグに属する投稿集合からタグ同士の cosine 類似度を計算。 +- それぞれ上位 20 件を保存する。 -- 主キーは (theatre_id, no) -- コメント本文と投稿ユーザを持つ -- discarded_at があるが、現行 API では削除機能なし +投稿詳細では `post.related(limit: 20)` により関連投稿を返す。タグ類似度については、テーブル・モデル・タスクはあるが、現行 UI での主導線は薄い。 -### theatre_watching_users +## 上映会 `theatres` + +Theatre は共同視聴用の会場ドメインである。 + +| テーブル | 役割 | +| --- | --- | +| `theatres` | 会場、現在上映中投稿、ホスト、期間 | +| `theatre_comments` | 会場コメント。主キーは `(theatre_id, no)` | +| `theatre_watching_users` | 在席ユーザ。`expires_at` で 30 秒 TTL | -- 主キーは (theatre_id, user_id) -- expires_at で在席期限を持つ -- active スコープは expires_at >= Time.current +現行では会場一覧・作成・編集は存在しない。トップメニューは固定で `/theatres/1` を指す。 # 現行機能仕様 @@ -368,85 +452,115 @@ Theatre は、共同視聴用の会場を表すドメインである。 ### API -GET /posts +`GET /posts` -### 条件 - -現行実装は次の検索パラメータを受け付ける。 +### パラメータ | パラメータ | 意味 | | --- | --- | -| tags | 空白区切りタグ列 | -| match | all または any | -| title | タイトル部分一致 | -| url | URL 部分一致 | -| original_created_from / original_created_to | 元コンテンツ作成日時範囲 | -| created_from / created_to | 投稿作成日時範囲 | -| updated_from / updated_to | 更新日時範囲 | -| page, limit | ページング | -| order | field:direction | +| `tags` | 空白区切りタグ列 | +| `match` | `all` または `any` | +| `title` | タイトル部分一致 | +| `url` | URL 部分一致 | +| `original_created_from` / `original_created_to` | 元コンテンツ作成日時範囲 | +| `created_from` / `created_to` | 投稿作成日時範囲 | +| `updated_from` / `updated_to` | 投稿更新日時範囲 | +| `page` / `limit` | ページング | +| `order` | `field:direction` | + +`order` の field は次の 5 種である。 + +- `title` +- `url` +- `original_created_at` +- `created_at` +- `updated_at` -order の field は次の 5 種である。 +`title` と `url` は direction 未指定時に昇順、それ以外は降順である。 -- title -- url -- original_created_at -- created_at -- updated_at +### タグ検索 -### タグ検索の意味論 +タグ検索は次の意味を持つ。 -- match=all または未指定: AND 的に絞り込む -- match=any: OR 的に結合する -- 各タグリテラルは検索前に TagName.canonicalise で canonical 名へ正規化される -- not: 接頭辞を付けたタグは否定条件になる +| 指定 | 意味 | +| --- | --- | +| `match=all` または未指定 | 各タグを AND 的に絞り込む | +| `match=any` | 各タグ条件を OR 的に結合する | +| `not:タグ名` | 否定条件 | +| 別名タグ | 検索前に canonical 名へ解決される | + +`match=any` では否定条件も OR の一部として扱われる。つまり `A not:B` は「A が付いている、または B が付いていない」に近い挙動になる。ここは直感とずれやすい。 -したがって現行仕様では、**別名吸収付きの AND / OR / NOT 相当** が最低限入っている。2026-03-08 版の「OR / NOT 未実装」は、もう事実ではない。 +### 更新日時 -### 更新日時の扱い +一覧返却の `updated_at` は、単純な `posts.updated_at` ではない。 -投稿一覧の updated_at は単純な posts.updated_at ではない。バックエンドは +サーバは次の大きい方を `updated_at_all` として返し、並び替えにも使う。 -- 投稿本体の updated_at -- post_tags の最新 updated_at +- `posts.updated_at` +- 対象投稿に紐づく `post_tags.updated_at` の最大値 -の大きい方を updated_at_all として計算し、それを返却にも並び替えにも使う。つまり、**タグ変更も投稿更新として扱う** のが現行仕様である。 +タグ変更も投稿更新として扱う設計である。 -### ランダム遷移 +### 返却 -GET /posts/random が存在する。これは tags と match による絞り込み結果からランダム 1 件を返す。 +返却は `{ posts, count }`。各投稿にはタグ・投稿者・サムネイル URL が含まれる。 + +## ランダム投稿 + +### API + +`GET /posts/random` + +`tags` と `match` を受け取り、絞り込み結果から `RAND()` で 1 件返す。 ## 投稿詳細 ### API -GET /posts/:id +`GET /posts/:id` -### 返却内容 +### 返却 - 投稿基本情報 -- タグ木構造 -- 関連投稿(最大 20 件) +- タグツリー +- 関連投稿 最大 20 件 - 閲覧済フラグ -### 埋め込み表示 +タグは `tag_implications` に基づきツリー化される。ループがあっても再帰停止する。 -現行の専用埋め込み対応は次の通り。 +### 画面 -| サイト | 現行条件 | -| --- | --- | -| ニコニコ動画 | nicovideo.jp/watch/... | -| YouTube | youtube.com で v パラメータあり | -| X / Twitter | x.com または twitter.com の /status/:id | -| その他 | ユーザ確認後に iframe | +投稿詳細画面では次が表示される。 + +- 埋め込み表示 +- タグツリー +- 投稿情報サイドバー +- 関連投稿 +- 編集タブ +- 閲覧済トグル +- 履歴リンク -つまり「YouTube 全般」ではない。youtu.be などは現実装上は専用扱いされない。 +投稿詳細サイドバーでは、D&D により上位タグ設定・解除・カテゴリ変更を試みる。API 側で admin 権限が要求されるため、member / guest は失敗する。 + +## 投稿埋め込み + +`PostEmbed` の現行専用対応は次の通り。 + +| サイト | 条件 | 表示 | +| --- | --- | --- | +| ニコニコ動画 | `nicovideo.jp/watch/:id` | `NicoViewer` | +| YouTube | `youtube.com` かつ `v` パラメータあり | `react-youtube` | +| X / Twitter | `x.com` または `twitter.com` の `/status/:id` | `TwitterEmbed` | +| その他 | ユーザ確認後 | `iframe` | + +`youtu.be`、Pixiv、Bluesky、Tiktok、bilibili、ニジカ投稿局などは専用埋め込み未実装である。 ## 新規投稿 ### API -POST /posts +`POST /posts` ### 権限 @@ -454,62 +568,122 @@ member 以上。 ### 入力 -- title -- url -- thumbnail -- tags -- original_created_from -- original_created_before +- `title` +- `url` +- `thumbnail` +- `tags` +- `original_created_from` +- `original_created_before` ### 保存フロー -1. 投稿保存 -2. サムネイル添付と縮小 -3. タグ正規化 -4. 親タグ展開 -5. post_tags 同期 +1. 投稿レコード作成。 +2. サムネイル添付。 +3. タグ名正規化。 +4. 親タグ展開。 +5. `post_tags` 同期。 +6. サムネイルを 180x180 へリサイズ。 +7. `post_versions` に `create` 版を記録。 + +`nico:` タグが含まれる場合は `400 Bad Request`。 ## 投稿編集 ### API -PUT /posts/:id +`PUT /posts/:id` ### 権限 member 以上。 -### 現行仕様 +### 更新対象 + +- `title` +- `tags` +- `original_created_from` +- `original_created_before` + +URL とサムネイルの通常編集は現行 API では扱っていない。 -- 更新対象は title, original_created_from, original_created_before, tags -- 既存の nico カテゴリタグは維持したまま、一般タグ側を再計算する -- 排他制御は未実装 +既存の `nico` カテゴリタグは維持され、一般タグ側だけが再正規化される。その後、親タグ展開と `post_tags` 同期を行い、差分があれば `post_versions` に `update` 版を記録する。 -## 閲覧済フラグ +### 排他制御 -- POST /posts/:id/viewed -- DELETE /posts/:id/viewed +現行 API には投稿編集の排他制御がない。issue では `updated_at` をリクエストに含め、差があれば 409 を返す構想があるが、未実装である。 -認証済ユーザであれば操作可能。 +## 閲覧済 -## 投稿履歴 +| API | 意味 | +| --- | --- | +| `POST /posts/:id/viewed` | 閲覧済にする | +| `DELETE /posts/:id/viewed` | 未閲覧に戻す | + +認証済ユーザなら操作可能である。 + +## 投稿版履歴 ### API -GET /posts/changes +`GET /posts/versions` + +### パラメータ + +| パラメータ | 意味 | +| --- | --- | +| `post` | 投稿 ID で絞り込み | +| `tag` | タグ ID で絞り込み | +| `page` / `limit` | ページング | + +タグで絞り込む場合、現在版または直前版のタグスナップショットに対象タグ名が含まれる版を返す。これにより、タグが追加された版だけでなく、削除された版も拾える。 + +### 返却 + +返却は `{ versions, count }`。 + +各 version は次を含む。 + +- `post_id` +- `version_no` +- `event_type` +- `title.current` / `title.prev` +- `url.current` / `url.prev` +- `thumbnail.current` / `thumbnail.prev` +- `thumbnail_base.current` / `thumbnail_base.prev` +- `tags[]`: `context` / `added` / `removed` +- `original_created_from.current` / `.prev` +- `original_created_before.current` / `.prev` +- `created_at` +- `created_by_user` -### 条件 +現行では `thumbnail.current` / `prev` は常に NULL で、サムネイル表示は `thumbnail_base` に依存する。 -- id: 投稿 ID で絞り込み -- tag: タグ ID で絞り込み -- page, limit +### 画面 -### 意味 +フロントの `/posts/changes` は、内部的には `GET /posts/versions` を呼ぶ。つまり画面名は「耕作履歴」だが、主役データはタグイベントではなく投稿版スナップショットである。 + +### 復元 + +履歴画面の「復元」は専用の restore API ではない。選択版の `title`、`tags`、`original_created_from`、`original_created_before` を `PUT /posts/:id` へ送る。 + +そのため、以下の制限がある。 + +- `url` は復元されない。 +- `thumbnail_base` は現時点では復元されない。将来的に入力欄を追加した段階で復元対象へ含める方針である。 +- `event_type = restore` の版は現行フローでは作られない。 +- `nico:` タグは復元送信時に除外される。 + +2026-04-20 の開発者ヒアリングでは、`thumbnail_base` は将来的に復元対象へ含める方針とされた。ただし現行フロントの編集入力欄に出てこない項目であるため、現段階では復元しない。これは「履歴として保持しない」のではなく、「編集 UI が責任を持てる項目だけ戻す」という暫定仕様である。 + +## 旧タグ変更履歴 + +### API -返すのは投稿本体の変更履歴ではなく、**タグ付与関係の履歴** である。 +`GET /posts/changes` -- 追加は change_type = add -- 削除は change_type = remove +この API は `post_tags` から `add` / `remove` のイベント列を生成する。現行画面の主導線ではなくなっているが、API としては残っている。 + +`/posts/versions` と `/posts/changes` は意味が違う。2026-04-20 の開発者ヒアリングでは、`/posts/changes` は将来的に廃止する可能性が高いと整理された。現時点では後方互換上残っている旧 API として扱う。 # タグ機能 @@ -517,92 +691,125 @@ GET /posts/changes ### API -GET /tags +`GET /tags` -### 条件 +### パラメータ | パラメータ | 意味 | | --- | --- | -| post | 特定投稿に付いたタグだけに絞る | -| name | 名前部分一致 | -| category | カテゴリ一致 | -| post_count_gte / post_count_lte | 投稿件数範囲 | -| created_from / created_to | 作成日時範囲 | -| updated_from / updated_to | 更新日時範囲 | -| page, limit | ページング | -| order | 並び順 | - -order の field は name, category, post_count, created_at, updated_at に対応する。カテゴリ並びは DB 上で独自順序化されている。 - -### フロント画面 - -/tags 画面は現時点で実装済みであり、名前・カテゴリ・件数・日時の詳細条件と並び替え UI を持つ。 +| `post` | 特定投稿に付いたタグだけへ絞る | +| `name` | 名前部分一致 | +| `category` | カテゴリ一致 | +| `post_count_gte` / `post_count_lte` | 投稿件数範囲 | +| `created_from` / `created_to` | 作成日時範囲 | +| `updated_from` / `updated_to` | 更新日時範囲 | +| `page` / `limit` | ページング | +| `order` | `field:direction` | + +`order` の field は次の 5 種である。 + +- `name` +- `category` +- `post_count` +- `created_at` +- `updated_at` + +カテゴリ並びは独自順序である。 + +1. `deerjikist` +2. `meme` +3. `character` +4. `general` +5. `material` +6. `meta` +7. `nico` ## タグ補完 ### API -GET /tags/autocomplete +`GET /tags/autocomplete` ### パラメータ | パラメータ | デフォルト | 意味 | | --- | --- | --- | -| q | なし | 検索文字列 | -| nico | true | nico: 接頭辞側候補を含める | -| present | true | post_count > 0 のタグに絞る | +| `q` | なし | 検索文字列 | +| `nico` | `true` | `nico:` 側候補を含める | +| `present` | `true` | `post_count > 0` のタグに絞る | -### 現行挙動 +### 挙動 -- q 先頭の not: は除去してから補完する -- canonical 名の前方一致に加えて、別名前方一致も拾う -- 別名ヒット時は canonical 側タグを返し、matched_alias を付ける -- 返却順は post_count DESC, tag_names.name -- 最大 20 件 - -2026-03-08 版にあった present_only というパラメータ名は現行実装と一致しない。**現在のパラメータ名は present** である。 +- `q` 先頭の `not:` は除去して補完する。 +- canonical 名の前方一致を拾う。 +- 別名の前方一致も拾い、canonical 側のタグを返す。 +- 別名ヒット時は `matched_alias` を付ける。 +- 並びは `post_count DESC, tag_names.name`。 +- 最大 20 件。 ## タグ詳細 -- GET /tags/:id -- GET /tags/name/:name +| API | 意味 | +| --- | --- | +| `GET /tags/:id` | ID でタグ取得 | +| `GET /tags/name/:name` | 名前完全一致でタグ取得 | +| `GET /tags/:id/deerjikists` | タグに紐づくニジラー一覧 | +| `GET /tags/name/:name/deerjikists` | 名前指定版 | +| `GET /tags/name/:name/materials` | 素材ツリー取得 | + +`/tags/name/:name` は exact match であり、別名解決をしない。 + +## タグ階層取得 + +### API + +`GET /tags/with-depth` + +### パラメータ + +| パラメータ | 意味 | +| --- | --- | +| `parent` | 親タグ ID。未指定ならルートタグを返す | -/tags/name/:name は exact match であり、別名から canonical タグへ自動解決する API ではない。検索と補完は canonical 化するが、**詳細取得 API は exact name** である点に注意が要る。 +### 返却 + +対象カテゴリは `meme` / `character` / `material`。各タグに `has_children` と `children: []` を付けて返す。 + +現行では素材サイドバーで使用される。`meme` は子を持つものだけ表示される。 ## タグ更新 ### API -PUT /tags/:id +`PUT /tags/:id` ### 権限 member 以上。 -### 現行でできること +### 更新対象 -- タグ名変更 -- カテゴリ変更 +- `name` +- `category` -### 現行でできないこと +タグ名変更は `tag.tag_name.update!`、カテゴリ変更は `tag.update!` で行われる。 -- 別名タグ作成 UI / API -- tag sanitisation rule の管理 UI / API -- タグ統合 UI / API +## 上位タグ管理 -内部実装はかなり進んでいるが、表面 API はまだ薄い。 +| API | 意味 | 権限 | +| --- | --- | --- | +| `POST /tags/:parent_id/children/:child_id` | child を parent の子にする | admin | +| `DELETE /tags/:parent_id/children/:child_id` | child を parent の子から外す | admin | -## 上位タグ管理 +現行フロントでは、投稿詳細サイドバーの D&D 操作からこれらを叩く。 -- POST /tags/:parent_id/children/:child_id -- DELETE /tags/:parent_id/children/:child_id +ただし、専用の「上位タグ設定画面」は未実装である。 -権限は admin。管理 UI は現スナップショットに存在しない。 +## 別名タグ管理 -## タグからニジラー参照 +モデル・検索・補完・正規化の土台はあるが、別名の作成・解除・一覧管理 UI/API は未実装である。 -- GET /tags/:id/deerjikists -- GET /tags/name/:name/deerjikists +issue では「管理者用別名タグ作成画面」が残っている。公開前にここはかなり重要である。 # ニコニコ連携 @@ -610,134 +817,114 @@ member 以上。 ### API -GET /tags/nico +`GET /tags/nico` -### 現行仕様 +### 挙動 -- updated_at DESC で取得 -- cursor に ISO8601 時刻を渡すカーソルページング -- limit デフォルトは 20 -- 各ニコタグに対して linked_tags を返す +- `Tag.nico_tags` を `updated_at DESC` で取得する。 +- `cursor` に ISO8601 時刻を渡すと、その時刻より古いものを取得する。 +- `limit` デフォルトは 20。 +- 各ニコタグに `linked_tags` を付与して返す。 ## ニコタグ更新 ### API -PUT /tags/nico/:id +`PUT /tags/nico/:id` ### 権限 member 以上。 -### 現行仕様 +### 挙動 -- 入力 tags を内部タグ列として再正規化する -- 連携先に nico カテゴリタグは指定不可 +- 対象タグは `nico` カテゴリ必須。 +- 入力 `tags` を通常タグとして再正規化する。 +- 連携先に `nico` カテゴリは指定不可。 +- 成功時、連携先タグ配列を返す。 -## 日次同期バッチ nico:sync +## 日次同期 `nico:sync` -現行タスクは、外部ニコニコ DB から動画情報を取り込み、投稿・タグ体系へ反映する。 +現行タスクは外部 Python スクリプト `get_videos.py` を呼び、ニコニコ動画情報を取り込む。 ### 主な流れ -1. 外部 Python スクリプト get_videos.py を実行 -2. ニコニコ動画 URL 既存投稿を探し、あれば更新、なければ新規作成 -3. タイトル・投稿日・サムネイルを反映 -4. 外部タグを nico: 接頭辞付き内部タグへ変換 -5. 新規に付いたニコタグについてだけ、連携済内部タグを自動付与 -6. ニジラー対応が見つからなければ ニジラー情報不詳 を補う -7. 内部タグ構成が変化した場合は bot操作 を付与 - -新規投稿時に初期付与されるメタタグは次である。 - -- タグ希望 -- bot操作 -- ニコニコ -- 動画 - -これは単なるミラーではなく、**外部タグを内部知識へ変換する ETL** と見た方が正確である。 +1. `MYSQL_USER`、`MYSQL_PASS`、`NIZIKA_NICO_PATH` を環境変数から読む。 +2. `python3 #{NIZIKA_NICO_PATH}/get_videos.py` を実行する。 +3. 既存投稿は `nicovideo.jp/watch/:code` の正規表現で検索する。 +4. タイトル、元投稿日、サムネイルを更新する。 +5. 新規投稿なら `タグ希望`、`bot操作`、`ニコニコ`、`動画` を初期付与する。 +6. 外部タグを `nico:` 付き内部タグへ変換する。 +7. 新しく追加されたニコタグについてだけ、連携済内部タグを展開する。 +8. deerjikist 対応があればニジラータグを付与する。 +9. 必要に応じて `bot操作` を追加する。 +10. 作成・変更があれば `PostVersionRecorder` で投稿版を記録する。 -# ニジラー管理 +これは単なるミラーではなく、外部タグを内部知識へ変換する ETL である。 -## API - -- GET /deerjikists/:platform/:code -- PUT /deerjikists/:platform/:code -- DELETE /deerjikists/:platform/:code - -## 権限 - -更新・削除は member 以上。 - -## 制約 - -- platform は現状 nico / youtube -- 紐づけ先タグは deerjikist カテゴリ必須 - -# Wiki +# Wiki 機能 ## 一覧・検索 -### API - -- GET /wiki -- GET /wiki/search - -### 現行挙動 +| API | 意味 | +| --- | --- | +| `GET /wiki` | Wiki 一覧またはタイトル検索 | +| `GET /wiki/search` | `index` への委譲 | -- title 未指定なら全件 -- title 指定時は tag_names.name LIKE %title% で上位 20 件 +`title` 未指定なら全件、指定時は `tag_names.name LIKE %title%` で最大 20 件を返す。 -フロントの /wiki 画面には「内容」入力欄もあるが、バックエンドはそれを受け取っていない。**現行の Wiki 検索はタイトル部分一致のみ** である。 +フロントの `/wiki` は検索画面であり、本文検索欄に見える UI があっても、バックエンドは本文を検索していない。現行の Wiki 検索はタイトル検索のみである。 ## 詳細表示 -### API - -- GET /wiki/:id -- GET /wiki/title/:title +| API | 意味 | +| --- | --- | +| `GET /wiki/:id` | ID 指定 | +| `GET /wiki/title/:title` | タイトル完全一致 | -### パラメータ +`version` パラメータがある場合は指定改訂を表示する。なければ最新改訂を表示する。 -- version: 指定時はその改訂を表示 +返却には次が含まれる。 -### 返却内容 +- `id` +- `title` +- `body` +- `revision_id` +- `pred` +- `succ` +- `updated_at` -- id, title -- body -- revision_id -- pred, succ -- updated_at +## リダイレクト -### リダイレクト +改訂が `redirect` の場合、サーバはリダイレクト先タイトルへ `301 Moved Permanently` で遷移させる。 -改訂 kind = redirect の場合、サーバは 301 Moved Permanently でリダイレクト先タイトルへ飛ばす。 +ただし現行 UI にはリダイレクト作成・編集導線がない。 ## 存在確認 -- GET /wiki/:id/exists -- GET /wiki/title/:title/exists - -存在すれば 204 No Content、無ければ 404。 +| API | 成功時 | 失敗時 | +| --- | --- | --- | +| `GET /wiki/:id/exists` | 204 | 404 | +| `GET /wiki/title/:title/exists` | 204 | 404 | ## 差分表示 ### API -GET /wiki/:id/diff?from=...&to=... +`GET /wiki/:id/diff?from=...&to=...` -### 現行仕様 +### 挙動 -- from 省略時は空本文相当との比較 -- to 省略時は現行改訂との比較 -- content 改訂同士のみ比較可能 -- 結果は context, added, removed の列として返す +- `from` 省略時は空本文相当。 +- `to` 省略時は現行改訂。 +- `content` 改訂同士のみ比較可。 +- 結果は `context` / `added` / `removed` の列で返す。 ## 作成 ### API -POST /wiki +`POST /wiki` ### 権限 @@ -745,99 +932,140 @@ member 以上。 ### 入力 -- title -- body -- message(任意) +- `title` +- `body` +- `message` 任意 -### 保存 - -- 対応する tag_name を作成または再利用 -- Wiki::Commit.content! により初回改訂を生成 +タイトルに対応する `TagName` を作成または再利用し、`Wiki::Commit.content!` で初回改訂を作る。 ## 更新 ### API -PUT /wiki/:id +`PUT /wiki/:id` ### 権限 member 以上。 -### 現行仕様 +### 挙動 -- タイトル変更は受け付けない -- body と message を受け取れる -- 実際のコミットは Wiki::Commit.content! を通る +- `title` と `body` は必須。 +- タイトル変更は受け付けない。変更されていれば 422。 +- `message` は受け取れる。 +- `Wiki::Commit.content!` で改訂を作る。 ### 競合制御の実態 -Wiki::Commit 自体は base_revision_id による競合検出を備えている。しかし WikiPagesController#update はクライアント送信値を使わず、**サーバ側で今の current_revision.id を読み直してそれをベースにしている**。つまり、コード上は衝突検出器があるが、現行 API とフロントはそれを活かし切っていない。 +`Wiki::Commit` は `base_revision_id` による競合検出を持つ。しかし `WikiPagesController#update` はクライアントから受けた基底版ではなく、更新直前にサーバ側で `page.current_revision.id` を読み直して使う。 + +つまり、衝突検出器はあるが、現行 API はクライアント起点の競合検出を十分に活かしていない。 ## 履歴 ### API -GET /wiki/changes +`GET /wiki/changes` -### 条件 +### パラメータ -- id を渡すと特定ページ履歴だけ返す -- 未指定なら全体履歴 +| パラメータ | 意味 | +| --- | --- | +| `id` | Wiki ページ ID。未指定なら全体履歴 | -### 現行仕様 +最大 200 件の `WikiRevision` を返す。`pred` は返るが、`succ` は常に NULL である。 -- WikiRevision 単位の履歴を最大 200 件返す -- pred は返るが succ は常に nil +# 素材集 -## フロント実装の注意点 +## 位置づけ -- 新規・編集画面は Markdown エディタを使う -- message 入力欄はフロントにまだ出ていない -- リダイレクト編集 UI も無い -- タイトル入力欄はあるが、更新時は変更するとバックエンドが 422 を返す +素材集は、タグに紐づく画像・動画・音声・参考 URL を管理する機能である。 -# 設定・ユーザ管理 +現行ではルートと画面は存在するが、トップナビでは `素材` メニューが `visible: false` になっている。つまり機能は実装され始めているが、正式導線としてはまだ隠し気味である。 -## 現行 UI でできること +## 画面 -/users/settings 画面でできるのは次である。 +| パス | 概要 | +| --- | --- | +| `/materials` | 素材一覧/タグ別素材ツリー | +| `/materials/new` | 素材追加 | +| `/materials/:id` | 素材詳細・編集 | +| `/materials/search` | import はあるがルートはコメントアウト | -- 表示名更新 -- 引継ぎコード表示 -- 他ブラウザからの引継ぎ +素材一覧は左サイドバーのタグ階層から対象タグを選び、対応する素材と子タグの素材を表示する。 ## API -- POST /users -- POST /users/verify -- GET /users/me -- POST /users/code/renew -- PUT /users/:id +| API | 権限 | 意味 | +| --- | --- | --- | +| `GET /materials` | 認証不要 | 素材一覧 | +| `GET /materials/:id` | 認証不要 | 素材詳細 | +| `POST /materials` | 認証済み、guest 可 | 素材作成 | +| `PUT /materials/:id` | member 以上 | 素材更新 | +| `DELETE /materials/:id` | member 以上 | 論理削除 | -## 注意点 +## 一覧 + +`GET /materials` は次を受け付ける。 + +| パラメータ | 意味 | +| --- | --- | +| `page` / `limit` | ページング | +| `tag_id` | タグで絞り込み | +| `parent_id` | 親素材で絞り込み | + +作成日時降順、ID 降順で返す。 + +## 詳細 + +`GET /materials/:id` は素材情報に加え、対応タグの Wiki 本文を `wiki_page_body` として返す。 + +## 作成・更新 + +### 作成 + +`POST /materials` は認証済みなら可能で、member 権限を要求しない。BTRC Hub では初回利用時に guest ユーザが自動発行されるため、公開時も guest に素材作成を許可する仕様とする。 + +入力は次の通り。 + +- `tag` +- `file` +- `url` + +`tag` は必須。`file` または `url` のどちらかは必須。対象タグがなければ作成し、カテゴリは `material` になる。 -- settings テーブルは存在する -- しかし **汎用ユーザ設定 API も反映ロジックも現行スナップショットでは未接続** -- フロントには /users や /users/:id への隠しメニュー項目があるが、対応ルートは未実装 +### 更新 + +`PUT /materials/:id` は member 以上。 + +`tag` と、`file` または `url` が必須。`file` が無い状態で更新すると既存添付ファイルは purge される。 + +## 現行の弱点 + +- `material_versions` は未接続。 +- 素材追加は認証済み guest でも可能であり、公開時には再検討が必要。 +- ファイル種別の厳密なサーバ側制限は薄い。 +- タグ一意のため、1 タグに複数素材を持つ設計ではない。 # プレビュー取得 ## API -- GET /preview/title -- GET /preview/thumbnail - -## 権限 +| API | 意味 | +| --- | --- | +| `GET /preview/title` | 対象 URL の `` を取得 | +| `GET /preview/thumbnail` | Node screenshot でサムネイル生成 | -認証済ユーザであれば利用可能。 +認証済ユーザのみ使用可能。 -## 現行仕様 +## 注意点 -- title は対象 URL の HTML を開き `<title>` を読む -- thumbnail は Node スクリプトでスクリーンショットを撮り、180x180 に縮小して返す +- URL が `http://` または `https://` で始まらなければ `http://` を補う。 +- `title` は `URI.open` と Nokogiri で取得する。 +- `thumbnail` は `node lib/screenshot.js` を shell 経由で実行し、MiniMagick で 180x180 に縮小する。 +- SSRF 対策・ドメイン制限・サイト別最適化は未整備。 -ただしコード中に TODO が複数残っており、**既知サイト個別最適化のない暫定ユーティリティ** である。 +公開前に最も危ない箇所の一つである。便利機能ではあるが、外部 URL 取得は防御を固めないと穴になる。 # 上映会(Theatre) @@ -845,461 +1073,401 @@ GET /wiki/changes ### API -GET /theatres/:id - -### 返却内容 - -- id, name -- opens_at, closes_at -- created_by_user -- created_at, updated_at +`GET /theatres/:id` -現在上映中の投稿や在席者は、この API では返さない。 +返却は `id`、`name`、`opens_at`、`closes_at`、作成者、作成・更新日時。現在上映中投稿や在席者は返さない。 -## 在席通知と会場状態取得 +## 在席通知と状態取得 ### API -PUT /theatres/:id/watching +`PUT /theatres/:id/watching` ### 権限 認証済ユーザ。 -### 現行挙動 +### 挙動 -- theatre_watching_users の expires_at を「現在 + 30 秒」に更新する -- もしホストが空、または現在ホストが在席していなければ、呼び出しユーザをホストにする -- 次を返す - - host_flg - - post_id - - post_started_at - - watching_users +- `theatre_watching_users.expires_at` を現在 + 30 秒へ更新する。 +- ホストが空、または現在ホストが在席していなければ、呼び出しユーザをホストにする。 +- `host_flg`、`post_id`、`post_started_at`、`watching_users` を返す。 -つまりこれは heartbeat と state fetch を兼ねた API である。 +これは heartbeat と state fetch を兼ねた API である。 ## 次の投稿へ進める ### API -PATCH /theatres/:id/next_post +`PATCH /theatres/:id/next_post` ### 権限 -認証済かつ現在ホストであること。 +認証済み、かつ現在ホストであること。 -### 現行仕様 +### 挙動 -- 候補は nicovideo.jp または youtube.com を含む投稿のみ -- その中から RAND() で 1 件選ぶ -- current_post_id と current_post_started_at を更新する +- `url LIKE '%nicovideo.jp%'` または `url LIKE '%youtube.com%'` の投稿から `RAND()` で 1 件選ぶ。 +- `current_post_id` と `current_post_started_at` を更新する。 ## コメント -### API - -- GET /theatres/:theatre_id/comments -- POST /theatres/:theatre_id/comments - -### 現行仕様 - -- 一覧は no_gt で差分取得できる -- 返却順は no ASC -- 投稿は認証済ユーザなら可 -- 空本文は 422 Unprocessable Entity -- 採番は theatre.next_comment_no を with_lock で進める +| API | 意味 | +| --- | --- | +| `GET /theatres/:theatre_id/comments` | コメント取得 | +| `POST /theatres/:theatre_id/comments` | コメント投稿 | -## フロント側の同期方式 +`GET` は `no_gt` で差分取得できるが、現行コード上は `no DESC` で返す。旧仕様にあった `no ASC` ではない。 -Theatre はサーバだけで完結していない。現行の同期制御は次のように **クライアント依存** である。 +`POST` は認証済ユーザなら可能。空本文は 422。採番は `theatre.next_comment_no` を `with_lock` で進める。 -- 1.5 秒ごとにコメント差分取得と watching heartbeat を送る -- ホスト側ブラウザが、post_started_at + 動画長 + 3 秒 を超えたと判断したら post_id = null 扱いにする -- その結果、ホスト側ブラウザが PATCH /next_post を叩いて次の動画へ進める -- ニコニコ埋め込みでは、再生位置のドリフトが 5 秒を超えると seek で補正する +## フロント同期 -ここが重要で、**自動進行はサーバジョブではなくホストブラウザ駆動** である。ホストがいない、またはホストのブラウザが止まると進行も止まりうる。 +Theatre はサーバ単独制御ではなく、ホストブラウザ依存の同期方式である。 -## 既知の制限 +- 一定間隔で heartbeat とコメント差分取得を行う。 +- ホスト側ブラウザが進行判断する。 +- 次の投稿へ進めるのもホスト側ブラウザが `PATCH /next_post` を叩く。 -- Theatre 一覧 API / 作成 API / 編集 API は無い -- フロントのトップメニューは固定で /theatres/1 を指している -- theatres.kind カラムは現スナップショットでは未使用 +ホストが落ちると進行が止まる可能性がある。将来的にサーバ制御へ寄せる余地がある。 # 画面構成 -現行フロントに存在する主要画面は次の通りである。 +現行フロントの主要ルートは次の通り。 | パス | 概要 | | --- | --- | -| /posts | 投稿一覧。タグ検索結果に応じて単一タグなら Wiki タブも出す | -| /posts/search | 詳細検索。タイトル / URL / タグ / 各種日時 / 並び順 | -| /posts/new | 新規投稿 | -| /posts/:id | 投稿詳細 | -| /posts/changes | 耕作履歴。投稿 ID またはタグ ID で絞れる | -| /tags | タグ一覧・検索 | -| /tags/nico | ニコニコ連携画面 | -| /theatres/:id | 上映会場 | -| /wiki | Wiki 一覧・検索 | -| /wiki/:title | Wiki 詳細 | -| /wiki/new | Wiki 新規作成 | -| /wiki/:id/edit | Wiki 編集 | -| /wiki/:id/diff | Wiki 差分 | -| /wiki/changes | Wiki 履歴 | -| /users/settings | 設定 | - -### UI 上の補足 - -- トップナビはレスポンシブ対応済み -- モバイルではメニューが折りたたみ表示になる -- /tags/aliases, /tags/implications, /users, /users/:id へのメニュー項目はコード上にあるが、visible: false で隠され、対応ルートも実装されていない +| `/` | `/posts` へリダイレクト | +| `/posts` | 投稿一覧 | +| `/posts/search` | 投稿詳細検索 | +| `/posts/new` | 新規投稿 | +| `/posts/:id` | 投稿詳細 | +| `/posts/changes` | 耕作履歴。内部的に `/posts/versions` を使用 | +| `/tags` | タグ一覧・検索 | +| `/tags/nico` | ニコニコ連携 | +| `/theatres/:id` | 上映会場 | +| `/materials` | 素材集 | +| `/materials/new` | 素材追加 | +| `/materials/:id` | 素材詳細・編集 | +| `/wiki` | Wiki 検索 | +| `/wiki/:title` | Wiki 詳細 | +| `/wiki/new` | Wiki 新規作成 | +| `/wiki/:id/edit` | Wiki 編集 | +| `/wiki/:id/diff` | Wiki 差分 | +| `/wiki/changes` | Wiki 履歴 | +| `/users/settings` | ユーザ設定 | +| `/settings` | `/users/settings` へリダイレクト | +| `/tos` | 利用規約 MDX ページ | +| `/more` | その他メニュー | + +## ナビゲーション + +トップナビはレスポンシブ対応済みである。 + +表示中の主メニューは次の通り。 + +- 広場 +- タグ +- 上映会 +- Wiki + +次はメニュー定義上はあるが `visible: false` で非表示。 + +- 素材 +- ユーザ +- 法規 +- 別名タグ +- 上位タグ +- 素材検索 +- 素材履歴 +- ユーザ一覧/詳細 # バッチ・運用仕様 ## 類似度計算 -| タスク | 内容 | +| Rake task | 内容 | | --- | --- | -| post_similarity:calc | 投稿同士の類似度再計算 | -| tag_similarity:calc | タグ同士の類似度再計算 | - -類似度計算は上位 20 件のみ保存する。特徴量は +| `post_similarity:calc` | 投稿同士の類似度再計算 | +| `tag_similarity:calc` | タグ同士の類似度再計算 | -- 投稿類似度: 投稿に付いたタグ集合 -- タグ類似度: タグに属する投稿集合 - -である。 +現行 schedule では `post_similarity:calc` の日次実行だけが定義されている。`tag_similarity:calc` は task はあるが、schedule には見当たらない。 ## Wiki 移行 -wiki:migrate は Gollum 形式の旧 Wiki を DB 版管理へ移行するタスクである。開発史としては、**Gollum から DB 内版管理への移行** がすでに行われている。 +`wiki:migrate` は Gollum 形式の旧 Wiki を DB 版管理へ移行するタスクである。 -## ニコニコ同期 +現行コードと issue から見て、Wiki は Git/Gollum 管理から DB 管理へ移行済みである。 -nico:sync は外部 DB からニコニコ動画情報を引き込み、投稿・ニコタグ・内部タグ連携を更新する中核バッチである。 - -# 非機能要件・既知のギャップ・計画への接続 - -## 整合性・監査性 - -- URL 一意制約あり -- active post_tags 一意制約あり -- タグ付与者・削除者を保持 -- Wiki 改訂者を保持 -- 投稿履歴 API と Wiki 履歴 API あり +## ニコニコ同期 -## 性能・取得単位 +`nico:sync` は日次同期の中核タスクである。`config/schedule.rb` 上は 1 日 1 回、15:00 に production 環境で実行する定義になっている。 -- 投稿一覧の既定件数は 20 -- タグ一覧の既定件数も 20 -- 補完は最大 20 件 -- Wiki 履歴 API は最大 200 件 +本番 crontab との時刻差が出る可能性はあるため、運用仕様として固定するなら `schedule.rb` と実際の crontab を合わせて管理する必要がある。 -## セキュリティと運用上の弱点 +## R2 / Active Storage -- banned フラグは保存されるが認可へ未接続 -- 引継ぎコード認証は軽量だが、通常のパスワード認証より防御面は薄い -- preview API は暫定実装であり、ドメイン別最適化も SSRF 対策の明示も弱い +production では次を使用する。 -## 現時点で明確な未接続箇所 +- `config.active_storage.service = :r2` +- `storage.yml` の `r2` は S3 service +- 環境変数: + - `R2_ENDPOINT` + - `R2_ACCESS_KEY_ID` + - `R2_SECRET_ACCESS_KEY` + - `R2_BUCKET` +- `region: auto` +- `request_checksum_calculation: when_required` -1. **別名タグ管理** - - DB / モデルはある - - だが現行 UI / API は不足 +`routes.default_url_options[:host]` は `hub.nizika.monster/api` に設定されている。Active Storage URL 生成に関わるため、production の URL 設計では重要である。 -2. **タグ名サニタイズ規則管理** - - モデルと適用ロジックはある - - だが現行 UI / API は無い +# 非機能要件・運用上の注意 -3. **settings テーブル** - - テーブルはある - - 汎用設定の保存・取得・反映経路は未整備 +## 整合性 -4. **Wiki の編集メッセージ** - - API は受け取れる - - フロントは入力欄を出していない +現行で DB・モデル側にある主な整合性は次の通り。 -5. **Wiki 本文検索** - - 画面に入力欄はある - - バックエンドはタイトルしか見ていない +- `posts.url` 一意。 +- HTTP/HTTPS URL のみ許可。 +- 有効な `post_tags(post_id, tag_id)` は一意。 +- `post_tags` は論理削除。 +- `tag_names.name` 一意。 +- `tags.tag_name_id` 一意。 +- `tag_implications(tag_id, parent_tag_id)` 一意。 +- `tag_implications` は自己参照禁止。 +- `post_versions(post_id, version_no)` 一意。 +- `post_versions` は読み取り専用。 +- `user_post_views` は複合主キー。 +- `theatre_comments` は `(theatre_id, no)` 複合主キー。 -6. **Theatre の進行制御** - - 機能は動く - - ただし自動進行はホストブラウザ依存 +## セキュリティ上の弱点 -## 仕様として特に重要な見直し点 +公開前に明確に潰すべき弱点は次である。 -2026-03-08 版から見て、次は修正済みとして扱うべきである。 +1. **BAN 未強制** + - `users.banned` と `ip_addresses.banned` はあるが、認証・認可で強制されていない。 -- 投稿一覧の明示的なソート指定は **実装済** -- タグ検索専用画面は **実装済** -- not: を含む否定検索は **内部的に実装済** -- Theatre 系 API / 画面は **実装済** +2. **preview API の SSRF リスク** + - 任意 URL をサーバから取得しうる。 + - 内部 IP・localhost・メタデータ IP などの拒否が必要。 -逆に、次はまだ「あるつもり」で書くと嘘になる。 +3. **引継ぎコード認証の軽さ** + - コード漏洩時の乗っ取りリスクがある。 + - 権限昇格・重要操作では追加防御が必要。 -- 別名タグ管理 UI -- 汎用設定画面 -- Theatre 一覧 / 作成 / 管理 -- Wiki 本文検索 -- 厳密なクライアント起点の競合解決 +4. **素材作成は guest 許可方針** + - `POST /materials` は認証済みなら可能で member 以上を要求しない。 + - 2026-04-20 の開発者ヒアリングで、公開時も guest に素材作成を許す方針とした。 + - ただし公開運用では、BAN 強制・アップロード制限・通報導線との組み合わせが必須である。 -# 現時点の総括 +5. **投稿編集の競合制御なし** + - 同時編集時に後勝ちで上書きされる。 -BTRC Hub は、表面上は「タグ付きリンク集」だが、内部実装はそれより一段重い。 +6. **iframe 埋め込み** + - 対応外サイトを iframe で表示できるが、外部ページの安全性・X-Frame-Options・ユーザ警告の設計が必要。 -- 投稿は URL と履歴志向のタグ付け対象である -- タグは別名・サニタイズ・階層・外部連携を持つ -- Wiki は版管理付き知識ベースである -- ニコニコ同期は ETL であり、外部タグを内部知識へ変換する -- Theatre は共同視聴の入口まで到達しているが、制御はまだブラウザ依存である +## 性能・ページング -要するに現行 BTRC Hub の正体は、**リンク集 UI を持つタグ・Wiki・外部同期・共同視聴の複合基盤** である。 +現行の主要既定件数は次の通り。 +| 対象 | 既定/上限 | +| --- | --- | +| 投稿一覧 | `limit` 既定 20 | +| タグ一覧 | `limit` 既定 20 | +| タグ補完 | 最大 20 | +| ニコタグ一覧 | `limit` 既定 20、cursor あり | +| Wiki 検索 | 最大 20 | +| Wiki 履歴 | 最大 200 | +| 投稿関連 | 最大 20 | # 計画仕様 -本章は、現行コードにはまだ全面反映されていないが、今後の BTRC Hub にとって重要な仕様をまとめる。 -公開判断や実装優先度を考える際は、現行仕様だけでなく本章を合わせて参照する。 +本章は現行コードに全面反映されていないが、今後の BTRC Hub にとって重要な仕様をまとめる。 ## 公開方針 -### 公開段階 - -BTRC Hub の公開は一気に完全一般公開へ進めるのではなく、段階的に引き上げる。 +一般公開は段階的に進める。 -| 段階 | 意味 | 想定時期 | -| --- | --- | --- | -| 第 3 段階 | 誰でも閲覧可、投稿・編集は申請制 | 初回一般公開 | -| 第 4 段階 | 履歴管理・差し戻し・BAN 運用が整った後、閲覧中心以外の開放範囲を拡張 | 将来 | - -計画仕様(確定)として、**初回一般公開は第 3 段階** を前提とする。 -いきなり広く編集を開けるのではなく、履歴管理と運用装備が整ってから第 4 段階へ進む。 - -### 公開時の主役 +| 段階 | 意味 | +| --- | --- | +| 第 3 段階 | 誰でも閲覧可。投稿・編集は申請制 | +| 第 4 段階 | 履歴・差し戻し・BAN 運用が成熟した後、編集開放範囲を拡張 | -BTRC Hub が一般公開時に前面へ出す主役は **タグ整理基盤** である。 -投稿・Wiki・ニコニコ連携・上映会はその周辺機能ではあるが、中心価値は「タグを軸にコンテンツと知識を整理し、再発見しやすくすること」に置く。 +初回一般公開では、投稿・Wiki・タグ編集の中心権限は申請制とする。ただし、2026-04-20 の開発者ヒアリングにより、素材作成については guest にも許可する方針とする。これは全編集開放ではなく、素材集に限定した例外である。BAN 強制・アップロード制限・通報導線が弱い状態で広範な編集を開けるのは危ない。 -### 公開前に必須とするもの +## 公開前の必須装備 -開発者ヒアリング時点で、一般公開前に必要と認識されている主要項目は次である。 +公開前に最低限必要なものは次である。 - 利用規約 - プライバシーポリシー -- 御意見番 -- banned user / banned IP の強制適用 +- BAN 強制適用 - 管理画面 +- 履歴閲覧と差し戻し +- 別名タグ管理 UI - Wiki 本文検索 -- Wiki の事前整備 -- タグ別名管理 UI -- タグ別名解除 UI -- 履歴管理の表示・差し戻し -- お遊び機能としてのアキネータ風機能 +- preview API の安全化 +- 投稿編集の排他制御 +- 素材作成 guest 許可に伴う安全制限 -このうち、利用規約・プライバシーポリシー・BAN・管理画面・履歴管理は、公開後の運用破綻を防ぐための最低装備である。 +お遊び機能は価値があるが、運用防御より優先してはいけない。ここを間違えると、公開時に火事場劇場になる。 -## 運営・モデレーション計画 +## 御意見番 -### 御意見番 +計画仕様(確定)として、御意見番を問い合わせ・不具合報告導線として設ける。 -計画仕様(確定)として、**御意見番** という意見送信機能を用意する。これは単なるお問い合わせフォームではなく、次を自動添付できることを目標とする。 +目標は次の情報を自動添付できること。 - 直前の操作ログ - 画面スクリーンショット -- 必要に応じた環境情報 - -狙いは、バグ報告・使い勝手の不満・誤操作報告を、再現に使える材料つきで受け取ることにある。 -一般公開後の保守効率へ直結するため、通常の問い合わせ導線より重要度が高い。 - -### BAN と差し戻し +- ブラウザ・OS など環境情報 +- 対象ページ URL +- ユーザ ID -計画仕様(確定)として、次の管理系操作を整備する。 +これは単なる問い合わせフォームではなく、再現性のある報告を受け取るための保守装置である。 -- ユーザ BAN -- IP BAN -- 投稿削除 -- Wiki 等の差し戻し -- 履歴表示からの復元導線 +## BAN と管理画面 -guest の権限は初期公開時点では閲覧のみとするが、履歴管理と BAN 運用が十分に整った後は、限定的な編集権限を与える余地を残す。 +計画仕様(確定)として、管理画面では少なくとも次を扱う。 -### 管理画面 - -計画仕様(確定)として、少なくとも次を扱える管理画面を整備する。 - -- BAN の付与と解除 +- ユーザ BAN / 解除 +- IP BAN / 解除 - 問題投稿・問題編集の確認 -- 履歴閲覧と差し戻し -- タグ別名・サニタイズ規則の管理 -- 送信された御意見番の確認 - -## 履歴管理・監査性の拡張 - -現行実装でも post_tags と wiki_revisions を中心とした履歴基盤は存在する。 -今後はこれを、内部保持だけでなく **公開サービスとして使える履歴機能** に引き上げる。 - -### 対象 - -計画仕様(確定)として、次の履歴を重視する。 - -- タグ記載履歴 -- 投稿情報の変更履歴 -- 変更者の表示 -- 差分表示 -- 版ごとの復元 - -Wiki については版管理が既に中核であるため、表示・差し戻し・競合検出が整って初めて一般公開に耐える。 - -### 公開段階との関係 - -第 4 段階へ移る条件の一つは、履歴が「保存されている」だけでなく、**閲覧・追跡・差し戻しが運用できる水準** に達することである。 - -## Wiki 計画仕様 - -### 位置づけ - -Wiki はタグへの説明を蓄積する知識基盤である。 -「主要タグに説明が付いていること」がタグ整理基盤としての説得力を決める。 - -### 主要ページ整備方針 - -開発者ヒアリングでは、主要タグに対して **約 5000 ページ規模** の説明ページが最終的に必要とされている。 -これは「公開前に必ず 5000 ページ作る」という意味ではなく、**主要タグ群をカバーするための目標規模** と位置づける。 - -計画仕様(確定)として、 +- 投稿版履歴からの復元 +- Wiki 履歴からの巻き戻し +- タグ別名作成・解除 +- タグサニタイズ規則管理 +- 御意見番一覧確認 -- 主な対象は **タグへの説明ページ** -- 主要タグが概ね 5000 程度存在する想定 -- 初期整備は手作業で行う -- 1 ページの最小要件は **1 行説明でもよい** +## 履歴管理 -とする。 +投稿版管理は現行実装済みだが、公開サービスとしては未完成である。 -したがって、量よりもまず「主要導線上のタグに最低限の説明がある」状態を目指す。 +今後必要なものは次。 -### リダイレクトと別名の関係 +- `restore` イベント型の明確化 +- URL・サムネイルを含む復元方針。ただし `thumbnail_base` は編集入力欄の追加後に復元対象へ含める +- 投稿削除・復元の版記録 +- 素材履歴の実装 +- Wiki 巻き戻し UI +- 誰が何を戻したかの監査表示 -計画仕様(確定)として、Wiki のリダイレクトは **タグ別名機能をリダイレクトとして使う** 方向で整理する。履歴の主たる管理責任は `tag_names` 側に置く。 +## Wiki 計画 -この方針により、少なくとも次の整合が期待される。 +Wiki は主要タグの説明を蓄積する知識基盤である。 -- タグ別名検索と Wiki 参照先の連動 -- 別名から正規名ページへの自然な到達 -- 履歴の責務を Wiki 側へ分散させないこと - -### Wiki の追加実装項目 - -計画仕様(確定)として、次を重点実装対象とする。 +優先すべき追加項目は次。 - 本文検索 -- 版の巻き戻し -- 競合検出 +- 競合検出のクライアント連動 - 下書き保存 -- 画像埋め込み - -画像埋め込みは、単なるMarkdown拡張ではなく、説明ページの情報密度と可読性を上げるための要件である。 +- 遷移時警告 +- 画像アップロード +- ページ保護 +- Wiki サイドバー +- リダイレクト編集 UI -## タグ機能の計画仕様 +`wiki_assets` はスキーマだけ先に入っているため、実装するならモデル・API・Markdown 埋め込み記法まで一気に決めるべきである。2026-04-20 時点の方針は専用記法であり、通常の外部 URL と混同しない。 -### 別名管理 +## タグ計画 -計画仕様(確定)として、タグ別名について少なくとも次を実現する。 +優先度が高いものは次。 -- 別名の作成 -- 別名の解除 -- 別名を含む検索 -- Wiki 参照先の連動 -- 別名からの参照解決の連動 +- 別名タグ作成 UI/API +- 別名タグ解除 UI/API +- 別名と Wiki 参照先の連動 +- 上位タグ専用管理画面 +- 同定文字・検索同定・禁止文字ルール +- タグ詳細ページの責務整理 -ここでいう「Wiki 連動」は、単にタグ詳細にリンクを出すだけではなく、別名経由で参照しても正規の説明へ辿り着けることを意味する。 +特に同定文字は、現在の MySQL collation が `utf8mb4_0900_ai_ci` であるため、平仮名/片仮名、濁点、半濁点などの同一視問題と関係する。ここは仕様として正面から扱う必要がある。 -### タグ詳細ページの位置づけ +## 素材計画 -タグ詳細ページについては、現時点で **「そもそも詳細ページが必須か」自体が再検討事項** である。 -現行には `/tags/:id` が存在するが、将来設計では次のどちらを主にするかは未確定である。 +素材集は実装が始まっているが、公開前に次を決める必要がある。 -- タグ詳細ページを知識・管理の中心に育てる -- Wiki ページを実質的な詳細ページとして扱う +- 1 タグ 1 素材でよいか、複数素材を許可するか。 +- `character` カテゴリは、下位区分の `material` を包括する代表素材タグとして許可し続ける。 +- `POST /materials` は公開時も guest に開ける。 +- `material_versions` をどう記録するか。 +- 画像・動画・音声以外を許可するか。 +- 素材ダウンロードや一括取得を許可するか。 -よって、タグ詳細ページの最終的な責務は **計画仕様(未確定)** とする。 - -### タグ名サニタイズ +## 投稿計画 -tag_name_sanitisation_rules は基本的に内部運用向け機能とする。 -計画仕様(確定)として、サニタイズ規則の操作は **鯖缶専用** の管理経路で行う。 +優先度が高いものは次。 -想定用途は次である。 +- 投稿編集の排他制御 +- 別投稿からタグをインポート +- URL・サムネイル復元を含む履歴復元。ただし `thumbnail_base` は入力欄追加後に復元対象化 +- 埋め込み対応拡張 +- 親投稿の用途整理 +- `/posts/changes` の将来的廃止と `/posts/versions` への一本化 -- ルール変更時に一括適用する -- 命名揺れの整理を管理側で行う -- 一般ユーザへ細かいルールを露出しすぎない +## 埋め込み対応計画 -## 投稿・検索の計画仕様 +issue と既存仕様から見る優先候補は次。 -### 投稿詳細 +1. ニコニコ +2. YouTube +3. Pixiv +4. ニジカ投稿局 +5. Twitter / X +6. Bluesky +7. Tiktok +8. bilibili +9. その他 iframe -投稿詳細ページでは、今後も次を重視する。 +現行で専用実装済みなのはニコニコ、YouTube の一部、Twitter/X である。 -- 埋め込み -- 関聯タグ -- 履歴 -- ブックマーク +## 設定機能 -特に履歴は、「投稿にどのタグがどのように付け替えられたか」を追えるようにする意味で重要である。 +`settings` テーブルは `key` と JSON `value` の汎用形式になっている。将来的には次を扱う想定である。 -### 検索 +- テーマ +- タグカテゴリごとの色設定 +- ミュートタグ +- 非表示タグ +- 閲覧済の自動化設定 +- 埋め込み自動再生可否 +- 投稿一覧のページング/無限ローディング設定 -開発者ヒアリング時点で、検索の最重要追加項目は **Wiki 本文検索** である。 -他の検索拡張もありうるが、本仕様書では本文検索を最優先の計画仕様として扱う。 +現行 UI では表示名と引継ぎコードが中心であり、汎用設定は未接続である。 -### 閲覧済・マイリスト +## 通知機能 -閲覧済フラグは将来的にも重要だが、そのまま独立機能として肥大化させるか、**マイリスト機能と統合するか** は未確定である。 -したがって閲覧済管理の最終形は **計画仕様(未確定)** とする。 +通知機能は未実装である。想定される通知は次。 -## 上映会(Theatre)の計画仕様 +- 新規投稿 +- Wiki 変更 +- 自分が関わったタグ・投稿の変更 +- 上映会で誰かが見ている通知 +- 御意見番への返信/対応 -### 位置づけ +通知は機能横断基盤になるため、場当たり的に作ると後で詰まる。まず通知対象、既読管理、表示場所を決めるべきである。 -一般公開時の上映会は **実験機能** と位置づける。 -サイトの看板機能として押し出すのではなく、タグ整理基盤の周辺にある遊び・共同視聴機能として扱う。 +## Theatre 計画 -### 必要機能 +Theatre は一般公開時点では実験機能扱いが妥当である。 -計画仕様(確定)として、次を将来的に整備する。 +将来的に必要なものは次。 - 会場一覧 - 会場作成 - 会場編集 - 複数会場 -- チャット +- マイリスト連動 +- サーバ主導の進行制御 +- チャット管理 - 誰かが見ている通知 -「誰かが見ている通知」は上映会だけでなく、サイト全体の通知機能の一部として発展する余地がある。 - -### マイリストとの関係 - -上映会は、以前から構想されている **マイリストをみんなでリアルタイム視聴する機能** の延長線上にある。 -したがって、今後の Theatre は単独の会場機能というより、マイリストと連動するウォッチパーティ基盤へ寄る可能性が高い。 - -## 権限・公開範囲の計画仕様 - -### ロール - -現行どおり `guest / member / admin` の 3 ロールを基本とする。 -moderator 等の中間ロールは、現時点では採用済み方針ではない。 - -### guest の扱い - -計画仕様(確定)として、初回一般公開時の guest は閲覧のみである。 -ただし、履歴機能と BAN が十分に整った場合には、guest でも一部編集を認める余地を残す。 - -## 法務・ポリシー文書 +## 法務・ポリシー ### 利用規約 -計画仕様(確定)として、利用規約には少なくとも次を含める。 +`/tos` ページは存在するが、公開用文書として内容精査が必要である。 + +最低限含めるべき項目は次。 - 禁止コンテンツ - 著作権侵害禁止 @@ -1314,187 +1482,193 @@ moderator 等の中間ロールは、現時点では採用済み方針ではな ### プライバシーポリシー -計画仕様(確定)として、プライバシーポリシーでは少なくとも次を収集情報として明示する。 +最低限明示すべき収集情報は次。 - IP アドレス - 引継ぎコード - Cookie / localStorage - 編集履歴 - アクセスログ +- 素材アップロード情報 - 外部埋め込み先との通信 -- お問い合わせ内容 +- 御意見番の送信内容 -## 外部連携の計画仕様 +# 追加ヒアリング反映事項 -### 優先する埋め込み先 +2026-04-20 の開発者ヒアリングで、次の点を仕様として反映した。 -計画仕様(確定)として、今後の埋め込み対応優先順は次のとおりとする。 +1. **素材集の権限** + - `POST /materials` は公開時も guest に許可する。 + - ただし、これは全編集権限の開放ではなく、素材作成に限定した例外である。 + - BAN 強制・アップロード制限・通報導線がないまま公開すると荒らし耐性が低いため、安全制限は別途必須である。 -1. ニコニコ -2. YouTube -3. Pixiv -4. ニジカ投稿局 -5. Twitter -6. Bluesky -7. その他 +2. **投稿復元の範囲** + - 現行の復元対象は `title`、`tags`、`original_created_from`、`original_created_before` とする。 + - `thumbnail_base` は将来的に復元対象へ含める方針である。 + - ただし、現行 UI では `thumbnail_base` の入力欄がないため、入力欄を追加するまでは復元しない。 + - `url` についても、編集 UI と責務が整理されるまでは慎重に扱う。 -### preview API +3. **`/posts/changes` の扱い** + - 旧タグイベント API として現時点では残す。 + - 将来的には廃止し、主導線は `/posts/versions` へ寄せる可能性が高い。 -preview API は、**今の便利さを保ったまま安全化する** ことを目標とする。 -したがって、単純な停止ではなく、利便性をできるだけ落とさない安全化が望まれる。 +4. **Wiki 画像の記法** + - `wiki_assets` を使う場合は専用記法を採用する。 + - 具体案は `![代替テキスト](asset:1)` のような形式である。 + - 実装時は Markdown レンダラで `asset:` スキームを解決し、ページ内の `wiki_assets.no` または asset ID から添付 URL を生成する。 -## 設定機能の計画仕様 +5. **素材カテゴリ** + - `character` は下位区分としての `material` を包括する代表素材タグとして扱う。 + - そのため、素材タグとして `material` だけでなく `character` も許容する。 -計画仕様(確定)として、設定機能では次のようなユーザ別カスタマイズを扱う想定である。 +引き続き判断が必要な点は次である。 -- テーマ -- タグカテゴリごとの色設定 -- ミュートタグ -- 非表示タグ -- 埋め込み自動再生の可否 +1. **素材とタグの対応数** + - 現行は `tag_id` 一意で 1 タグ 1 素材に近い。将来もこれでよいか。 -タグカテゴリ色設定は、現状ハードコードされている色をユーザごとに調整できるようにする意図を持つ。 +2. **Theatre の制御主体** + - 今後サーバ主導へ移すか、現行のホストブラウザ主導を続けるか。 -## 通知機能 +3. **公開時の素材・iframe・preview の安全制限** + - どこまで許可し、どこから警告・拒否するか。 -通知機能の必要性自体は認識されているが、具体的に何をどの粒度で通知するかは **未定** である。 -上映会の在席通知や「誰かが見ている」通知と結びつく可能性があるため、将来の横断基盤として検討する。 - -## スマートフォン対応 - -計画仕様(確定)として、スマートフォン環境でも **PC 同等の快適さ** を要求する。 -単に閲覧できるだけでは足りず、主要な投稿・編集・検索操作が実用的である必要がある。 - -## お遊び機能 - -開発者ヒアリングでは、一般公開前の必須候補として **アキネータ風のお遊び機能** が挙げられている。 -これは中核機能ではないが、公開時の導入体験や話題性に寄与する可能性がある。 - -ただし、仕様詳細はまだ固まっていないため、現時点では **計画仕様(未確定)** とする。 - -## 計画仕様の要約 - -現行 BTRC Hub は「タグ付きリンク集 UI を持つ知識統合基盤」だが、計画仕様まで含めると、その目標像はもう少しはっきりする。 - -- 主役はタグ整理基盤 -- Wiki は主要タグへ説明を付す知識基盤 -- 履歴管理は保存だけでなく表示・差し戻しまで含める -- 公開は申請制から始め、履歴と運営装備の成熟に応じて広げる -- 埋め込み、プレビュー、上映会、お遊び機能は基盤の上に乗る周辺価値である - -したがって、BTRC Hub の将来像は単なるリンク集ではない。 -**タグ・Wiki・履歴・運営導線を中核に持つ、共同編集型の整理基盤** と捉えるのが最も正確である。 +9. **上位タグ管理の正式 UI** + - 投稿詳細 D&D を正式管理導線とするか、専用管理画面を別途作るか。 +10. **同定文字仕様** + - DB collation で勝手に同一視される範囲と、アプリ側で明示的に同定/禁止する範囲をどう分けるか。 # 付録 A: 現行 API 一覧 -## 投稿系 - -- GET /posts -- GET /posts/random -- GET /posts/changes -- GET /posts/:id -- POST /posts -- PUT /posts/:id -- POST /posts/:id/viewed -- DELETE /posts/:id/viewed - -## タグ系 - -- GET /tags -- GET /tags/autocomplete -- GET /tags/:id -- PUT /tags/:id -- GET /tags/:id/deerjikists -- GET /tags/name/:name -- GET /tags/name/:name/deerjikists -- POST /tags/:parent_id/children/:child_id -- DELETE /tags/:parent_id/children/:child_id +## 投稿 + +- `GET /posts` +- `GET /posts/random` +- `GET /posts/changes` +- `GET /posts/versions` +- `GET /posts/:id` +- `POST /posts` +- `PUT /posts/:id` +- `POST /posts/:id/viewed` +- `DELETE /posts/:id/viewed` + +## タグ + +- `GET /tags` +- `GET /tags/autocomplete` +- `GET /tags/with-depth` +- `GET /tags/:id` +- `PUT /tags/:id` +- `GET /tags/:id/deerjikists` +- `GET /tags/name/:name` +- `GET /tags/name/:name/deerjikists` +- `GET /tags/name/:name/materials` +- `POST /tags/:parent_id/children/:child_id` +- `DELETE /tags/:parent_id/children/:child_id` + +## ニコタグ + +- `GET /tags/nico` +- `PUT /tags/nico/:id` -## ニコタグ系 +## Wiki -- GET /tags/nico -- PUT /tags/nico/:id +- `GET /wiki` +- `GET /wiki/search` +- `GET /wiki/changes` +- `GET /wiki/:id` +- `PUT /wiki/:id` +- `GET /wiki/:id/exists` +- `GET /wiki/:id/diff` +- `GET /wiki/title/:title` +- `GET /wiki/title/:title/exists` +- `POST /wiki` -## Wiki 系 +## ユーザ -- GET /wiki -- GET /wiki/search -- GET /wiki/changes -- GET /wiki/:id -- PUT /wiki/:id -- GET /wiki/:id/exists -- GET /wiki/:id/diff -- GET /wiki/title/:title -- GET /wiki/title/:title/exists -- POST /wiki +- `POST /users` +- `POST /users/verify` +- `GET /users/me` +- `POST /users/code/renew` +- `PUT /users/:id` -## ユーザ系 +## ニジラー -- POST /users -- POST /users/verify -- GET /users/me -- POST /users/code/renew -- PUT /users/:id +- `GET /deerjikists/:platform/:code` +- `PUT /deerjikists/:platform/:code` +- `DELETE /deerjikists/:platform/:code` -## ニジラー系 +## プレビュー -- GET /deerjikists/:platform/:code -- PUT /deerjikists/:platform/:code -- DELETE /deerjikists/:platform/:code +- `GET /preview/title` +- `GET /preview/thumbnail` -## プレビュー系 +## 上映会 -- GET /preview/title -- GET /preview/thumbnail +- `GET /theatres/:id` +- `PUT /theatres/:id/watching` +- `PATCH /theatres/:id/next_post` +- `GET /theatres/:theatre_id/comments` +- `POST /theatres/:theatre_id/comments` -## 上映会系 +## 素材 -- GET /theatres/:id -- PUT /theatres/:id/watching -- PATCH /theatres/:id/next_post -- GET /theatres/:theatre_id/comments -- POST /theatres/:theatre_id/comments +- `GET /materials` +- `GET /materials/:id` +- `POST /materials` +- `PUT /materials/:id` +- `DELETE /materials/:id` # 付録 B: 主要永続データ一覧 -| テーブル | 役割 | -| --- | --- | -| posts | 投稿リンク本体 | -| tags | 実体タグ | -| tag_names | タグ文字列と別名管理 | -| tag_name_sanitisation_rules | タグ名置換ルール | -| post_tags | 投稿とタグの関係、履歴含む | -| tag_implications | 上位タグ | -| nico_tag_relations | ニコタグと内部タグの連携 | -| deerjikists | 外部人物識別子とタグの対応 | -| wiki_pages | Wiki ページ本体 | -| wiki_revisions | Wiki 改訂 | -| wiki_lines | 行単位本文ストア | -| wiki_revision_lines | 改訂と行の対応 | -| users | ユーザ | -| ip_addresses | IP 記録 | -| user_ips | ユーザと IP の紐づけ | -| user_post_views | 閲覧済管理 | -| post_similarities | 投稿類似度 | -| tag_similarities | タグ類似度 | -| theatres | 上映会場 | -| theatre_comments | 上映会コメント | -| theatre_watching_users | 在席ユーザ | -| settings | 将来の汎用設定用テーブル | +| テーブル | 役割 | 状態 | +| --- | --- | --- | +| `posts` | 投稿リンク本体 | 使用中 | +| `post_versions` | 投稿版スナップショット | 使用中 | +| `post_tags` | 投稿とタグの関係、タグ履歴 | 使用中 | +| `tags` | 実体タグ | 使用中 | +| `tag_names` | タグ名・別名 | 使用中 | +| `tag_implications` | 上位タグ | 使用中 | +| `tag_name_sanitisation_rules` | タグ名置換規則 | 内部使用 | +| `nico_tag_relations` | ニコタグと内部タグの連携 | 使用中 | +| `deerjikists` | 外部人物識別子とタグの対応 | 使用中 | +| `wiki_pages` | Wiki ページ | 使用中 | +| `wiki_revisions` | Wiki 改訂 | 使用中 | +| `wiki_lines` | Wiki 行本文 | 使用中 | +| `wiki_revision_lines` | 改訂と行の対応 | 使用中 | +| `wiki_assets` | Wiki 画像 | スキーマのみ | +| `materials` | 素材 | 使用中 | +| `material_versions` | 素材履歴 | スキーマのみ | +| `users` | ユーザ | 使用中 | +| `ip_addresses` | IP 記録 | 使用中、BAN 未強制 | +| `user_ips` | ユーザと IP の対応 | 使用中 | +| `user_post_views` | 閲覧済 | 使用中 | +| `settings` | 汎用設定 | 未接続 | +| `post_similarities` | 投稿類似度 | 使用中 | +| `tag_similarities` | タグ類似度 | 一部使用/表示薄い | +| `theatres` | 上映会場 | 使用中 | +| `theatre_comments` | 上映会コメント | 使用中 | +| `theatre_watching_users` | 上映会在席 | 使用中 | +| Active Storage 系 | 添付ファイル | 使用中 | # 付録 C: 参照した主要ソース -- backend/config/routes.rb -- backend/db/schema.rb -- backend/app/controllers/* -- backend/app/models/* -- backend/app/services/wiki/commit.rb -- backend/app/representations/* -- backend/lib/tasks/*.rake -- frontend/src/App.tsx -- frontend/src/pages/* -- frontend/src/components/PostEmbed.tsx -- frontend/src/components/TagSidebar.tsx -- frontend/src/components/TopNav.tsx -- frontend/src/lib/* \ No newline at end of file +- `backend/config/routes.rb` +- `backend/db/schema.rb` +- `backend/app/controllers/*` +- `backend/app/models/*` +- `backend/app/services/post_version_recorder.rb` +- `backend/app/services/wiki/commit.rb` +- `backend/app/representations/*` +- `backend/lib/tasks/*.rake` +- `backend/config/storage.yml` +- `backend/config/schedule.rb` +- `backend/config/environments/production.rb` +- `frontend/src/App.tsx` +- `frontend/src/types.ts` +- `frontend/src/lib/*` +- `frontend/src/pages/*` +- `frontend/src/components/*` +- `btrc-hub.wiki.zip` 内の開発 Wiki +- `btrc-hub.issue.sql` +- `BTRC_Hub_仕様書_2026-03-23.md` \ No newline at end of file