From 1adde765e519031777ed6d7517536d68d4d6808d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BF=E3=81=A6=E3=82=8B=E3=81=9E?= Date: Fri, 8 May 2026 18:24:09 +0900 Subject: [PATCH] --- %E8%AA%B2%E9%A1%8C%E6%95%B4%E7%90%86.md | 1524 +++++++++++++++++++++++ 1 file changed, 1524 insertions(+) create mode 100644 %E8%AA%B2%E9%A1%8C%E6%95%B4%E7%90%86.md diff --git a/%E8%AA%B2%E9%A1%8C%E6%95%B4%E7%90%86.md b/%E8%AA%B2%E9%A1%8C%E6%95%B4%E7%90%86.md new file mode 100644 index 0000000..ed9927e --- /dev/null +++ b/%E8%AA%B2%E9%A1%8C%E6%95%B4%E7%90%86.md @@ -0,0 +1,1524 @@ +# タグ広場 今後の Gitea Issue 草案 + +- レビュー日: 2026-04-27 +- 対象: `btrc-hub-feature_317.zip` 静的レビュー、仕様書、既存 Gitea issue dump +- 注意: この環境では `bundle` と `node_modules` が無く、RSpec / frontend build は実行できていない。よって「コード上の疑い」と「実装提案」を含む。 + +## 優先度の目安 + +- P0: 公開前に塞がないと危険 +- P1: 中核機能・データ整合性・運用上かなり重要 +- P2: 早めに直すべき改善・機能 +- P3: 余力で育てるゼロイチ・快適化 + +## 既存 issue 棚卸しメモ + +- `#164 タグ検索機能作成` と `#171 OR/NOT 検索` は、現ソースでは概ね実装済みに見える。close するか、残作業に title/body を更新する。 +- `#165 管理者用別名タグ作成画面`、`#169/#170 ユーザ一覧/詳細`、`#137 設定画面反映`、`#150 Wiki画像アップロード`、`#151 Wiki検索自動補完`、`#180 タグサイドバー見切れ`、`#237 同定文字` はまだ有効な核として扱う。 +- `bot操作` 削減系は似た issue が多いので、バッチ仕様 issue に統合してから残タスクへ分解した方がよい。 + +## Issue 草案一覧(90 件) + + +### 2. [P0] preview API の SSRF 対策を入れる + +**種別**: security + +**対象** +- `backend/app/controllers/preview_controller.rb` +- `backend/lib/screenshot.js` + +**背景 / 問題** +/preview/title と /preview/thumbnail が任意 URL を外部取得している。private IP、localhost、メタデータ IP、file:// 相当、巨大レスポンス、リダイレクト経由を遮断しないと SSRF の入口になる。 + +**完了条件** +- [ ] http/https 以外を拒否する +- [ ] private / loopback / link-local / metadata IP を拒否する +- [ ] DNS 解決後 IP でも検査する +- [ ] リダイレクト先も検査する +- [ ] サイズ上限・タイムアウト・content-type 検査を追加する +- [ ] 異常時に内部例外メッセージを返さない + +### 3. [P0] Material 作成 API を member 以上に制限する + +**種別**: security / bug + +**対象** +- `backend/app/controllers/materials_controller.rb` + +**背景 / 問題** +MaterialsController#create は current_user があれば guest でも素材を作成できる。update/destroy は member 以上なので権限ポリシーが食い違っている。公開後はスパム素材の直通穴になる。 + +**完了条件** +- [ ] create も current_user.gte_member? を要求する +- [ ] guest 作成が 403 になる request spec を追加する +- [ ] 既存 UI 側も権限に応じて投稿導線を隠す + +### 4. [P0] PATCH /tags/:id でシステムタグ名を変更できる抜け道を塞ぐ + +**種別**: bug + +**対象** +- `backend/app/controllers/tags_controller.rb` + +**背景 / 問題** +TagsController#update_all には system_tag_name? の保護があるが、PATCH 側の update には同等の保護がない。PUT/PATCH の差で「タグ希望」「bot操作」などのシステムタグを壊せる。 + +**完了条件** +- [ ] PATCH update でも system_tag_name? を検査する +- [ ] システムタグの category 変更可否も仕様化する +- [ ] PUT/PATCH 両方の request spec を追加する + +### 5. [P0] GET /users/me を query code 依存から current_user 依存に直す + +**種別**: security / design + +**対象** +- `backend/app/controllers/users_controller.rb` +- `frontend/src/App.tsx` +- `frontend/src/lib/api.ts` + +**背景 / 問題** +UsersController#me が params[:code] でユーザを探し、inheritance_code まで返している。認証ヘッダを使う設計と不一致で、コードが URL やログに載る導線を残している。 + +**完了条件** +- [ ] GET /users/me は X-Transfer-Code 由来の current_user を使う +- [ ] query param の code を廃止する +- [ ] 返却に inheritance_code を含める必要があるか再検討する +- [ ] フロントの取得処理を合わせる + +### 6. [P0] 一覧・履歴・コメント系 API に limit 上限を設ける + +**種別**: performance / security + +**対象** +- `backend/app/controllers/posts_controller.rb` +- `backend/app/controllers/tags_controller.rb` +- `backend/app/controllers/nico_tags_controller.rb` +- `backend/app/controllers/materials_controller.rb` +- `backend/app/controllers/theatre_comments_controller.rb` + +**背景 / 問題** +複数 API が limit をほぼ無制限に受け付ける。小さいサイトでも、公開直後に巨大 limit 一発で DB と JSON 生成が詰まる。 + +**完了条件** +- [ ] 全 index 系 API に MAX_LIMIT を設定する +- [ ] 既定値・最大値を API 仕様に明記する +- [ ] limit 過大時は clamp か 400 のどちらかに統一する +- [ ] request spec を追加する + +### 7. [P0] 外部 iframe / Markdown リンクに CSP と sandbox 方針を入れる + +**種別**: security + +**対象** +- `frontend/src/components/PostEmbed.tsx` +- `frontend/src/components/WikiBody.tsx` +- `backend/config/initializers/*` + +**背景 / 問題** +投稿埋め込みや Wiki Markdown は外部 URL と強く結びつく。公開するなら CSP、iframe sandbox、許可ドメイン、クリックジャック対策を明示しないと XSS 周辺の事故面積が広い。 + +**完了条件** +- [ ] CSP を設定する +- [ ] iframe sandbox 属性を設計する +- [ ] 許可する埋め込み先を定義する +- [ ] 未知サイト iframe は警告または opt-in にする + +### 8. [P1] WikiAsset 系のスキーマだけ存在する状態を解消する + +**種別**: bug / schema drift + +**対象** +- `backend/db/schema.rb` +- `backend/db/migrate` +- `backend/app/models` +- `backend/app/controllers` +- `frontend/src/pages/wiki/*` + +**背景 / 問題** +schema.rb に wiki_assets と wiki_pages.next_asset_no があるが、対応 migration / model / controller / frontend 導線が見当たらない。スキーマ幽霊は開発者の判断を腐らせる。 + +**完了条件** +- [ ] 必要なら migration/model/controller/UI を追加する +- [ ] 不要なら schema から消す migration を作る +- [ ] Wiki 画像アップロード issue と統合するか決める +- [ ] schema:load と migrate の差分が出ないことを CI で確認する + +### 9. [P1] material_versions を実際に記録するか、削除するか決める + +**種別**: bug / design debt + +**対象** +- `backend/db/migrate/20260329034700_create_materials.rb` +- `backend/db/schema.rb` +- `backend/app/models/material.rb` +- `backend/app/controllers/materials_controller.rb` + +**背景 / 問題** +material_versions テーブルはあるが、MaterialVersion モデルや recorder が見当たらない。履歴があるように見えて実際は空のままになる設計は危険。 + +**完了条件** +- [ ] MaterialVersion モデルと記録処理を作る、またはテーブルを撤去する +- [ ] create/update/destroy で履歴が残る +- [ ] 素材復元の要否を決める + +### 10. [P1] Wiki の新旧履歴系統を統合する + +**種別**: design / bug + +**対象** +- `backend/app/controllers/wiki_pages_controller.rb` +- `backend/app/models/wiki_revision.rb` +- `backend/app/models/wiki_version.rb` +- `frontend/src/pages/wiki/WikiHistoryPage.tsx` + +**背景 / 問題** +wiki_revisions と wiki_versions が並存しており、/wiki/changes は WikiRevision を見ている。一方で 2026-04-26 時点の設計は wiki_versions を新履歴としている。二重履歴は必ずズレる。 + +**完了条件** +- [ ] どちらを公開履歴 API とするか決める +- [ ] 履歴一覧・差分・復元が同じ履歴系統を参照する +- [ ] 旧系統を残す場合は責務を明文化する + +### 11. [P1] Wiki 編集で base_revision_id を送信し、競合検出を有効化する + +**種別**: bug / UX + +**対象** +- `frontend/src/pages/wiki/WikiEditPage.tsx` +- `backend/app/controllers/wiki_pages_controller.rb` +- `backend/app/services/wiki/commit.rb` + +**背景 / 問題** +バックエンド側は base_revision_id を受け取れるが、フロントが送っていないため実質的な編集競合検出になっていない。複数人編集では後勝ち上書きが起こる。 + +**完了条件** +- [ ] GET /wiki が revision_id を返し、編集フォームが保持する +- [ ] PUT /wiki/:id が base_revision_id を受け取る +- [ ] 競合時 409 を返す +- [ ] フロントで差分確認または再読み込み導線を出す + +### 12. [P1] Wiki 検索の「本文」欄を実装するか UI から消す + +**種別**: bug / UX + +**対象** +- `frontend/src/pages/wiki/WikiSearchPage.tsx` +- `backend/app/controllers/wiki_pages_controller.rb` + +**背景 / 問題** +フロントには本文検索っぽい state があるが、バックエンドはタイトル部分一致しか見ていない。検索欄が嘘をついている状態。 + +**完了条件** +- [ ] 本文検索を実装する、または UI から本文欄を消す +- [ ] MySQL FULLTEXT / LIKE / 外部検索のどれを使うか決める +- [ ] 検索結果に一致箇所スニペットを出す + +### 13. [P1] Wiki タイトル表示を tag alias と canonical に連動させる + +**種別**: feature / correctness + +**対象** +- `backend/app/controllers/wiki_pages_controller.rb` +- `backend/app/models/tag_name.rb` +- `frontend/src/pages/wiki/WikiDetailPage.tsx` + +**背景 / 問題** +タグ検索・補完は別名を吸収する一方で、Wiki の title API は exact match 寄り。別名タグから正規 Wiki に自然遷移できないと、タグ基盤と Wiki 基盤が割れる。 + +**完了条件** +- [ ] 別名タイトルアクセス時は canonical Wiki へリダイレクトする +- [ ] 別名解除・変更時の Wiki 参照先を仕様化する +- [ ] TagLink / WikiLink の URL 生成を統一する + +### 14. [P1] WikiHistoryPage の null user クラッシュを防ぐ + +**種別**: bug + +**対象** +- `frontend/src/pages/wiki/WikiHistoryPage.tsx` +- `backend/app/controllers/wiki_pages_controller.rb` + +**背景 / 問題** +バックエンドは user が nil の履歴を返しうるが、フロントは change.user.id を直参照している。bot 操作や古い移行データで落ちる。 + +**完了条件** +- [ ] user が nil の場合は「bot操作」等を表示する +- [ ] 型定義を User | null にする +- [ ] 回帰テストまたは Story を追加する + +### 15. [P1] React Hooks の条件付き呼び出しを修正する + +**種別**: bug / frontend + +**対象** +- `frontend/src/pages/posts/PostHistoryPage.tsx` +- `frontend/src/pages/wiki/WikiNewPage.tsx` +- `frontend/src/pages/wiki/WikiEditPage.tsx` + +**背景 / 問題** +tagId ? useQuery(...) : ... のような条件付き hook と、権限分岐 return の前後で hook 数が変わる箇所がある。React の基本契約違反で、状態破壊の地雷。 + +**完了条件** +- [ ] すべての hook を無条件に呼ぶ +- [ ] enabled オプションで条件制御する +- [ ] eslint-plugin-react-hooks を CI で必須化する + +### 16. [P1] タグ補完コンポーネントの重複実装を統合する + +**種別**: refactor / UX + +**対象** +- `frontend/src/components/TagSearch.tsx` +- `frontend/src/components/common/TagInput.tsx` +- `frontend/src/components/PostFormTagsArea.tsx` + +**背景 / 問題** +同系統のタグ補完処理がコピペで 3 箇所に分散している。stale suggestions バグも横展開されている。タグ広場の中核 UI なので、ここは部品化すべき。 + +**完了条件** +- [ ] 共通 hook useTagAutocomplete を作る +- [ ] 共通候補ポップアップを作る +- [ ] キーボード操作・クリック・IME 入力を同じ仕様にする +- [ ] 既存 3 箇所を置換する + +### 17. [P1] タグ補完の stale state バグを直す + +**種別**: bug / UX + +**対象** +- `frontend/src/components/TagSearch.tsx` +- `frontend/src/components/common/TagInput.tsx` +- `frontend/src/components/PostFormTagsArea.tsx` + +**背景 / 問題** +setSuggestions(data) の直後に古い suggestions.length を見て visible を決める箇所がある。初回補完が表示されない等の微妙な挙動になる。 + +**完了条件** +- [ ] visible 判定を data.length で行う +- [ ] 候補 0 件時の表示を統一する +- [ ] タグ入力の手動テストケースを追加する + +### 18. [P1] タグ名を URL に入れる箇所で encodeURIComponent を徹底する + +**種別**: bug + +**対象** +- `frontend/src/lib/tags.ts` +- `frontend/src/components/TagLink.tsx` +- `frontend/src/lib/prefetchers.ts` +- `frontend/src/components/TopNav.tsx` + +**背景 / 問題** +fetchTagByName などでタグ名をそのまま URL に入れている。スラッシュ、空白、?、#、日本語正規化絡みで route が壊れる。 + +**完了条件** +- [ ] タグ名 path segment は必ず encodeURIComponent する +- [ ] サーバ側も decode 後の扱いを統一する +- [ ] 特殊文字タグの request/frontend テストを追加する + +### 19. [P1] TanStack Query key の id/name 衝突を分離する + +**種別**: bug / frontend + +**対象** +- `frontend/src/lib/queryKeys.ts` +- `frontend/src/lib/tags.ts` +- `frontend/src/lib/wiki.ts` + +**背景 / 問題** +tagsKeys.show が ID と名前の両方に使われている。名前が数字のタグや、Wiki の id/title でもキャッシュ衝突が起こりうる。 + +**完了条件** +- [ ] showById と showByName の query key を分ける +- [ ] Wiki も showById / showByTitle を分ける +- [ ] prefetcher と各ページを追従させる + +### 20. [P1] TagsController#index の count で Relation 全体をロードしない + +**種別**: performance + +**対象** +- `backend/app/controllers/tags_controller.rb` + +**背景 / 問題** +count に q.size を使うと、includes 済み relation のロード状況次第で全件メモリ展開になる。タグ数が増えるほど鈍い毒になる。 + +**完了条件** +- [ ] count は q.except(:limit, :offset, :order).count 等にする +- [ ] includes と count を分離する +- [ ] 大量タグでの性能 spec または benchmark を残す + +### 21. [P1] PostsController#changes を DB ページングにする + +**種別**: performance + +**対象** +- `backend/app/controllers/posts_controller.rb` + +**背景 / 問題** +post_tags 履歴を Ruby 配列に変換してから sort/slice している。履歴が増えると API 一発で全履歴を舐める。 + +**完了条件** +- [ ] 追加・削除イベントを SQL で union する、または専用 history table/view を作る +- [ ] page/limit を DB 側で効かせる +- [ ] count も DB で取る + +### 22. [P1] ORDER RAND() を避けるランダム取得方式に変える + +**種別**: performance + +**対象** +- `backend/app/controllers/posts_controller.rb` +- `backend/app/controllers/theatres_controller.rb` + +**背景 / 問題** +/posts/random と theatre next_post が ORDER RAND() を使う。件数が増えるほど DB を丸く焼く。 + +**完了条件** +- [ ] 候補 count + offset、id 範囲乱択、事前シャッフルキュー等へ置換する +- [ ] フィルタ条件つきランダムでも破綻しない +- [ ] RAND 使用を消す + +### 23. [P1] LIKE 検索の % / _ ワイルドカードをエスケープする + +**種別**: bug / correctness + +**対象** +- `backend/app/controllers/posts_controller.rb` +- `backend/app/controllers/tags_controller.rb` + +**背景 / 問題** +title/url/name/autocomplete 等で LIKE にユーザ入力を渡しているが、sanitize_sql_like が徹底されていない。SQL injection ではないが、検索語の % や _ がワイルドカードとして暴れる。 + +**完了条件** +- [ ] LIKE 入力は ActiveRecord::Base.sanitize_sql_like を通す +- [ ] 特殊文字検索の spec を追加する +- [ ] UI の検索説明も合わせる + +### 24. [P1] match=any と not: の意味論を再設計する + +**種別**: design / bug + +**対象** +- `backend/app/controllers/posts_controller.rb` +- `frontend/src/pages/posts/PostSearchPage.tsx` + +**背景 / 問題** +OR 検索で not: を混ぜると「A OR not:B」型になり、ほぼ全件に近い結果を返しうる。仕様として正しくても直感的ではない。 + +**完了条件** +- [ ] OR/AND/NOT の真理値表を仕様化する +- [ ] UI に説明を出すか、not 条件だけ常に AND とするか決める +- [ ] 複合検索の request spec を追加する + +### 25. [P1] 閲覧済み API を冪等にする + +**種別**: bug + +**対象** +- `backend/app/controllers/posts_controller.rb` +- `backend/app/models/user_post_view.rb` + +**背景 / 問題** +POST /posts/:id/viewed が current_user.viewed_posts << Post.find で追加しており、二重押しやリトライで重複例外を出しうる。 + +**完了条件** +- [ ] create_or_find_by! にする +- [ ] 既に閲覧済みでも 204/200 を返す +- [ ] DELETE も未閲覧時に成功扱いにするか仕様化する + +### 26. [P2] 投稿編集で URL とサムネイルを扱えるようにするか、非対応を UI に明記する + +**種別**: feature / UX + +**対象** +- `backend/app/controllers/posts_controller.rb` +- `frontend/src/components/PostEditForm.tsx` + +**背景 / 問題** +投稿作成では URL/thumbnail を扱うが、更新では URL/thumbnail 更新がない。実運用ではリンク修正とサムネ更新が必ず出る。 + +**完了条件** +- [ ] URL 更新可否を決める +- [ ] thumbnail / thumbnail_base 更新可否を決める +- [ ] 履歴に URL/thumbnail 変更を残す +- [ ] UI の項目と API を一致させる + +### 27. [P1] タグ上位関係の循環を DB/モデル/API で禁止する + +**種別**: bug / data integrity + +**対象** +- `backend/app/models/tag_implication.rb` +- `backend/app/models/tag.rb` +- `backend/app/controllers/tags_controller.rb` + +**背景 / 問題** +自己参照は禁止されているが、多段循環 A→B→C→A の防止が弱い。expand_parent_tags は seen で無限ループは避けるが、データ構造として壊れている。 + +**完了条件** +- [ ] 追加時に循環検出する +- [ ] 既存データ監査 rake task を作る +- [ ] 循環時は 422 を返す +- [ ] request spec を追加する + +### 28. [P1] タグの別名・上位タグ変更権限を整理する + +**種別**: security / design + +**対象** +- `backend/app/controllers/tags_controller.rb` +- `frontend/src/pages/tags/TagDetailPage.tsx` + +**背景 / 問題** +上位タグ専用 route は admin なのに、PUT /tags/:id の update_all では member が aliases / parent_tags を変更できる。権限モデルが矛盾している。 + +**完了条件** +- [ ] 別名変更・上位タグ変更を member/admin どちらに許すか決める +- [ ] API ごとの権限を統一する +- [ ] UI も role で表示制御する +- [ ] request spec を追加する + +### 29. [P1] Tag.normalise_tags が既存タグ category を暗黙変更する挙動を見直す + +**種別**: data integrity + +**対象** +- `backend/app/models/tag.rb` + +**背景 / 問題** +プレフィクス付き入力で既存 tag の category を update! している。投稿時のタグ入力だけで既存タグの分類が変わるのは危険。 + +**完了条件** +- [ ] 既存タグの category は暗黙変更しない +- [ ] 変更が必要な場合は専用 API に寄せる +- [ ] 既存挙動を残すなら警告と履歴を必須にする + +### 30. [P1] TagNameSanitisationRule の正規表現 DoS と管理導線を対策する + +**種別**: security / ops + +**対象** +- `backend/app/models/tag_name_sanitisation_rule.rb` +- `backend/db/migrate/20260309123200_create_tag_name_sanitisation_rules.rb` + +**背景 / 問題** +サニタイズ規則は強力だが、正規表現を DB に保存して一括適用する。管理 UI/API を作るなら ReDoS と誤爆一括変更への安全柵が必須。 + +**完了条件** +- [ ] 鯖缶専用にする +- [ ] 適用前 dry-run を出す +- [ ] 変更件数と影響タグを表示する +- [ ] 危険 regex を検出またはタイムアウトする + +### 31. [P2] NicoTagsController の cursor / id エラーを 400/404 にする + +**種別**: bug + +**対象** +- `backend/app/controllers/nico_tags_controller.rb` + +**背景 / 問題** +cursor に不正 ISO8601 が来た場合や存在しない id の更新で 500 系になりうる。外部入力なので明示的に 400/404 を返すべき。 + +**完了条件** +- [ ] 不正 cursor は 400 +- [ ] 存在しない nico tag は 404 +- [ ] エラー JSON を統一する + +### 32. [P1] Material 更新時に file 未指定で既存ファイルを purge しない + +**種別**: bug + +**対象** +- `backend/app/controllers/materials_controller.rb` + +**背景 / 問題** +MaterialsController#update は file が無いと既存 file を purge する。タグ名や URL だけ編集したつもりでファイルが消える。 + +**完了条件** +- [ ] file 未指定なら既存ファイル維持 +- [ ] 削除は remove_file=true 等の明示操作にする +- [ ] URL-only / file-only / 両方ありの spec を追加する + +### 33. [P2] Material 作成時の nil attach を避ける + +**種別**: bug + +**対象** +- `backend/app/controllers/materials_controller.rb` + +**背景 / 問題** +URL のみ素材でも material.file.attach(file) が呼ばれる。nil attach が環境差で例外化する可能性がある。 + +**完了条件** +- [ ] file.present? の時だけ attach する +- [ ] URL-only create の request spec を追加する + +### 34. [P1] Material の tag_id 一意制約を discarded_at と整合させる + +**種別**: data integrity + +**対象** +- `backend/app/models/material.rb` +- `backend/db/migrate/20260329034700_create_materials.rb` + +**背景 / 問題** +Material は MyDiscard を使うが tag_id uniqueness が破棄済みも含んでしまう。削除後に同じタグの素材を作れない。 + +**完了条件** +- [ ] 有効レコードだけ一意にする生成列/部分相当 index を作る +- [ ] model validation も kept に合わせる +- [ ] discard 後の再作成 spec を追加する + +### 35. [P1] Theatre の opens_at / closes_at を API で強制する + +**種別**: bug / moderation + +**対象** +- `backend/app/controllers/theatres_controller.rb` +- `backend/app/controllers/theatre_comments_controller.rb` + +**背景 / 問題** +show/watching/next_post/comments が会場の開催期間をほぼ見ていない。閉場後も入室・コメント・進行できると運用上混乱する。 + +**完了条件** +- [ ] 未開場・閉場後の挙動を決める +- [ ] watching/comment/next_post で期間チェックする +- [ ] 閉場後は閲覧だけ可など仕様化する + +### 36. [P1] Theatre コメントに件数上限・長さ上限・削除を入れる + +**種別**: moderation / performance + +**対象** +- `backend/app/controllers/theatre_comments_controller.rb` +- `backend/app/models/theatre_comment.rb` +- `frontend/src/pages/theatres/TheatreDetailPage.tsx` + +**背景 / 問題** +コメント index は no_gt 以降を上限なしで返し、投稿内容の長さ制限も弱い。荒らしと巨大レスポンスに弱い。 + +**完了条件** +- [ ] index に limit 上限を入れる +- [ ] content 最大長を定義する +- [ ] コメント削除/非表示 API を追加する +- [ ] 削除履歴を残す + +### 37. [P2] Theatre next_post の候補抽出を URL 部分一致からサイト別判定へ変える + +**種別**: bug / quality + +**対象** +- `backend/app/controllers/theatres_controller.rb` +- `frontend/src/components/PostEmbed.tsx` + +**背景 / 問題** +next_post は nicovideo.jp / youtube.com を含む URL だけで候補にしている。watch URL 以外や youtu.be などの漏れ/誤爆が出る。 + +**完了条件** +- [ ] 投稿 URL から embed 対応種別を判定する service を作る +- [ ] next_post は embedable な投稿だけ選ぶ +- [ ] youtu.be 等にも対応する + +### 38. [P2] Theatre のホストブラウザ依存進行をサーバ主導へ寄せる + +**種別**: architecture / feature + +**対象** +- `backend/app/controllers/theatres_controller.rb` +- `frontend/src/pages/theatres/TheatreDetailPage.tsx` + +**背景 / 問題** +現状の自動進行はホスト側ブラウザのタイマーに依存する。ホスト端末がスリープすると会場が止まる。実験機能としてはよいが、一般公開で推すなら弱い。 + +**完了条件** +- [ ] サーバ側で現在投稿の終了判定を持つか決める +- [ ] 定期 job / polling / manual next の責務を整理する +- [ ] ホスト不在時の移譲ルールを明記する + +### 39. [P2] Theatre 一覧・作成・編集 API/UI を追加する + +**種別**: feature + +**対象** +- `backend/config/routes.rb` +- `backend/app/controllers/theatres_controller.rb` +- `frontend/src/pages/theatres/*` +- `frontend/src/components/TopNav.tsx` + +**背景 / 問題** +現在のトップメニューは /theatres/1 固定。複数会場や運用にはならない。 + +**完了条件** +- [ ] GET /theatres を作る +- [ ] POST/PATCH /theatres を作る +- [ ] 会場一覧・作成・編集画面を作る +- [ ] 権限と公開期間を設定できる + +### 40. [P2] 存在しない /users/:id 導線を実装するかリンクを消す + +**種別**: bug / UX + +**対象** +- `frontend/src/pages/wiki/WikiHistoryPage.tsx` +- `frontend/src/pages/posts/PostHistoryPage.tsx` +- `frontend/src/pages/tags/TagHistoryPage.tsx` +- `frontend/src/components/TagDetailSidebar.tsx` +- `backend/config/routes.rb` + +**背景 / 問題** +複数画面が /users/:id へリンクしているが、対応 route/page/API がない。クリックした瞬間に迷子になる。 + +**完了条件** +- [ ] ユーザ詳細 API/UI を実装する、またはリンクをやめる +- [ ] bot/null user の表示を統一する +- [ ] 既存 #169/#170 と統合する + +### 41. [P1] ユーザ一覧・詳細・ロール変更の管理 UI を作る + +**種別**: feature / admin + +**対象** +- `backend/app/controllers/users_controller.rb` +- `frontend/src/pages/users/*` + +**背景 / 問題** +member/admin 昇格、BAN、編集履歴確認の導線がない。一般公開前の運用装備として必須。 + +**完了条件** +- [ ] admin 向け users index/show を作る +- [ ] role 変更 API を作る +- [ ] BAN/解除 API を作る +- [ ] 操作履歴を残す + +### 42. [P2] settings テーブルを実利用する API/UI を作る + +**種別**: feature + +**対象** +- `backend/db/migrate/20250227212500_create_settings.rb` +- `backend/app/models/user.rb` +- `frontend/src/pages/users/SettingPage.tsx` + +**背景 / 問題** +settings テーブルはあるが、表示名/引継ぎコード以外のユーザ設定に接続されていない。 + +**完了条件** +- [ ] GET/PUT /settings または /users/settings を作る +- [ ] テーマ、カテゴリ色、ミュートタグ、非表示タグ、埋め込み自動再生を段階的に実装する +- [ ] 設定 key の型とバリデーションを決める + +### 43. [P1] タグ別名の作成・解除・履歴表示 UI を専用化する + +**種別**: feature + +**対象** +- `frontend/src/pages/tags/TagDetailPage.tsx` +- `backend/app/controllers/tags_controller.rb` +- `backend/app/models/tag_name.rb` + +**背景 / 問題** +現状は TagDetailPage のテキスト欄に空白区切りで aliases を突っ込む形。誤操作の破壊力が大きい。 + +**完了条件** +- [ ] 別名追加/解除を個別操作にする +- [ ] 別名が既存タグ/Wiki を持つ場合のエラーを丁寧に出す +- [ ] 変更履歴を見える化する +- [ ] 既存 #165 と統合する + +### 44. [P2] タグ統合 UI/API を作る + +**種別**: feature + +**対象** +- `backend/app/models/tag.rb` +- `backend/app/controllers/tags_controller.rb` +- `frontend/src/pages/tags/*` + +**背景 / 問題** +Tag.merge_tags! は存在するが、運用 UI/API がない。タグ整理基盤を名乗るなら、統合は中核作業になる。 + +**完了条件** +- [ ] 統合元/統合先を確認する UI を作る +- [ ] 影響投稿数・Wiki 有無・nico 制約を事前表示する +- [ ] dry-run と実行を分ける +- [ ] 統合履歴を残す + +### 45. [P2] タグ上位関係の管理画面を作る + +**種別**: feature + +**対象** +- `backend/app/controllers/tags_controller.rb` +- `frontend/src/pages/tags/*` + +**背景 / 問題** +上位タグ API はあるが管理 UI が薄い。タグ体系の骨格なのに手操作が危険。 + +**完了条件** +- [ ] 親候補補完つき UI を作る +- [ ] 循環検出結果を表示する +- [ ] 追加/削除を個別履歴として残す + +### 46. [P2] タグ詳細ページの責務を決める + +**種別**: design + +**対象** +- `frontend/src/pages/tags/TagDetailPage.tsx` +- `frontend/src/components/TagDetailSidebar.tsx` +- `frontend/src/pages/wiki/WikiDetailPage.tsx` + +**背景 / 問題** +タグ詳細を管理中心にするのか、Wiki を詳細ページにするのかが曖昧。両方を育てると導線が割れる。 + +**完了条件** +- [ ] タグ詳細と Wiki の責務表を作る +- [ ] 通常ユーザ導線と管理者導線を分ける +- [ ] TagLink の遷移先を統一する + +### 47. [P2] 投稿から別投稿へのタグ・インポート機能を作る + +**種別**: feature + +**対象** +- `frontend/src/components/PostEditForm.tsx` +- `backend/app/controllers/posts_controller.rb` + +**背景 / 問題** +類似投稿やシリーズ投稿では、既存投稿のタグをコピーして微修正したい。既存 #292 の実装候補。 + +**完了条件** +- [ ] 投稿 ID/URL からタグを読み込める +- [ ] nico タグと手入力タグの扱いを分ける +- [ ] 差分確認してから反映する + +### 48. [P2] 投稿編集時のタグ推薦を実装する + +**種別**: feature / ML-lite + +**対象** +- `backend/app/models/post_similarity.rb` +- `backend/app/models/tag_similarity.rb` +- `frontend/src/components/PostEditForm.tsx` + +**背景 / 問題** +post_similarities / tag_similarities があるなら、編集時に「付け忘れ候補」を出せる。既存 #168 と相性がよい。 + +**完了条件** +- [ ] 現在タグと類似投稿から候補タグを返す API を作る +- [ ] 候補理由を表示する +- [ ] ワンクリック追加できる + +### 49. [P2] 類似度計算を差分更新できるようにする + +**種別**: performance / ops + +**対象** +- `backend/lib/tasks/post_similarity.rake` +- `backend/lib/tasks/tag_similarity.rake` +- `backend/app/models/post_similarity.rb` +- `backend/app/models/tag_similarity.rb` + +**背景 / 問題** +類似度が全再計算前提だと、データ増加後にバッチが重くなる。タグ変更時の差分更新か、夜間全再計算の運用設計が必要。 + +**完了条件** +- [ ] 全再計算の所要時間を測る +- [ ] 差分更新対象を定義する +- [ ] 失敗時の再実行手順を README に書く + +### 50. [P1] post_versions / tag_versions / wiki_versions の復元専用 API を作る + +**種別**: feature / audit + +**対象** +- `backend/app/controllers/post_versions_controller.rb` +- `backend/app/controllers/tag_versions_controller.rb` +- `backend/app/controllers/wiki_pages_controller.rb` +- `frontend/src/pages/posts/PostHistoryPage.tsx` +- `frontend/src/pages/tags/TagHistoryPage.tsx` + +**背景 / 問題** +現状の復元は通常 update に過去値を投げる形。復元操作としての意味が履歴上で曖昧になる。 + +**完了条件** +- [ ] POST /post_versions/:id/restore 等を作る +- [ ] event_type restore を記録する +- [ ] 復元者と復元元 version_no を保存する +- [ ] UI に「この版へ戻す」を出す + +### 51. [P1] 投稿・タグ・Wiki の削除/復元導線を整理する + +**種別**: moderation / feature + +**対象** +- `backend/app/models/my_discard.rb` +- `backend/app/controllers/*` +- `frontend/src/pages/*` + +**背景 / 問題** +Discard は複数モデルに入っているが、公開 UI/API と復元導線が揃っていない。荒らし対応には削除より復元可能な非表示が重要。 + +**完了条件** +- [ ] 対象モデルごとの discard/restore API を決める +- [ ] 管理画面から復元できる +- [ ] 削除履歴が残る +- [ ] 一般ユーザには非表示になる + +### 52. [P1] 御意見番フォームを実装する + +**種別**: feature / ops + +**対象** +- `frontend/src/pages/*` +- `backend/app/controllers/*` +- `backend/db/migrate` + +**背景 / 問題** +公開後の不具合報告導線として、操作ログ・画面 URL・環境情報・スクリーンショット添付を持つフォームが必要。単なる問い合わせより再現性を重視する。 + +**完了条件** +- [ ] 意見テーブルを作る +- [ ] URL/画面名/直前操作ログ/UA を保存する +- [ ] スクリーンショット添付の有無を決める +- [ ] 管理画面で一覧・対応済みにできる + +### 53. [P1] 利用規約・プライバシーポリシーを公開導線に載せる + +**種別**: legal / release blocker + +**対象** +- `frontend/src/App.tsx` +- `frontend/src/pages/*` + +**背景 / 問題** +IP、引継ぎコード、編集履歴、外部埋め込み通信を扱うため、公開前に規約とプライバシーポリシーが必要。 + +**完了条件** +- [ ] /tos と /privacy を作る +- [ ] 初回利用時・フッタ・設定画面から到達できる +- [ ] IP と外部埋め込みについて明記する + +### 54. [P1] IP アドレス保存期間と利用目的を設計する + +**種別**: privacy / ops + +**対象** +- `backend/app/models/ip_address.rb` +- `backend/app/models/user_ip.rb` +- `backend/app/controllers/users_controller.rb` + +**背景 / 問題** +user_ips は監査に有用だが、保存期間と利用目的を決めずに溜めるとプライバシー負債になる。 + +**完了条件** +- [ ] 保存期間を決める +- [ ] 削除/匿名化 rake task を作る +- [ ] BAN 用に残す情報とログ用情報を分ける +- [ ] プライバシーポリシーへ反映する + +### 55. [P2] 全 API のエラー JSON 形式を統一する + +**種別**: DX / UX + +**対象** +- `backend/app/controllers/application_controller.rb` +- `backend/app/controllers/*` +- `frontend/src/lib/api.ts` + +**背景 / 問題** +head だけ、{ errors: ... }、例外メッセージ直返しなどが混在している。フロントが失敗理由を出しにくい。 + +**完了条件** +- [ ] 標準エラー形式を決める +- [ ] validation / unauthorized / forbidden / not_found / conflict を統一する +- [ ] フロント toast が message を表示できる + +### 56. [P2] 日付パラメータの不正入力を 400 にする + +**種別**: bug + +**対象** +- `backend/app/controllers/posts_controller.rb` +- `backend/app/controllers/tags_controller.rb` +- `backend/app/controllers/nico_tags_controller.rb` + +**背景 / 問題** +日付・cursor 系パラメータで parse 例外が出ると 500 になりがち。ユーザ入力なので 400 に落とすべき。 + +**完了条件** +- [ ] parse helper を作る +- [ ] 不正日付は 400 + error code +- [ ] 各検索 API に spec を追加する + +### 57. [P1] アップロードファイルの content-type / サイズ制限を入れる + +**種別**: security / ops + +**対象** +- `backend/app/controllers/posts_controller.rb` +- `backend/app/controllers/materials_controller.rb` +- `backend/app/models/material.rb` + +**背景 / 問題** +thumbnail / material file は ActiveStorage に入るが、サイズや種類の上限が明確でない。R2/ストレージ費用と危険ファイル対策の両面で必要。 + +**完了条件** +- [ ] 用途ごとの最大サイズを決める +- [ ] 許可 MIME type を決める +- [ ] 画像なら寸法上限も見る +- [ ] エラーを UI に出す + +### 58. [P2] PostRepr / MaterialRepr の URL 生成を環境設定で統一する + +**種別**: ops / bug + +**対象** +- `backend/app/representations/post_repr.rb` +- `backend/app/representations/material_repr.rb` +- `backend/config/environments/production.rb` + +**背景 / 問題** +ActiveStorage URL の host 指定や request.base_url 依存が散らばると、本番/開発/プロキシ配下で URL が壊れる。 + +**完了条件** +- [ ] default_url_options を環境変数で設定する +- [ ] representation に host を渡す箇所を統一する +- [ ] プロキシ配下の URL spec を追加する + +### 59. [P2] フロントの role guard を共通化する + +**種別**: frontend / refactor + +**対象** +- `frontend/src/pages/wiki/WikiNewPage.tsx` +- `frontend/src/pages/wiki/WikiEditPage.tsx` +- `frontend/src/pages/tags/TagDetailPage.tsx` +- `frontend/src/components/*` + +**背景 / 問題** +各ページで user?.role を直接見て分岐している。権限表示、hook 順序、guest 体験がばらつく。 + +**完了条件** +- [ ] RequireRole コンポーネントまたは useCan helper を作る +- [ ] server 権限と同じ表を使う +- [ ] 権限不足時の表示を統一する + +### 60. [P2] TypeScript の型を API 実態に合わせて null 許容を増やす + +**種別**: bug / frontend + +**対象** +- `frontend/src/types.ts` +- `frontend/src/pages/*` +- `frontend/src/components/*` + +**背景 / 問題** +uploadedUser / createdByUser / Wiki history user など、bot 操作や移行データで null になりうる値がある。型が嘘をつくと UI が落ちる。 + +**完了条件** +- [ ] API レスポンスを洗い出す +- [ ] null 許容型に直す +- [ ] 表示側に fallback を入れる + +### 61. [P3] タグカテゴリ色・表示名をフロント固定から設定可能にする + +**種別**: feature / settings + +**対象** +- `frontend/src/consts.ts` +- `frontend/src/components/TagLink.tsx` +- `backend/app/models/setting.rb` + +**背景 / 問題** +カテゴリ色はユーザ好みやアクセシビリティに直結する。settings 実装後の候補として切り出す価値がある。 + +**完了条件** +- [ ] 既定テーマを維持する +- [ ] ユーザ設定で上書きできる +- [ ] コントラスト不足を警告する + +### 62. [P3] ミュートタグ / 非表示タグを実装する + +**種別**: feature + +**対象** +- `backend/app/models/setting.rb` +- `backend/app/controllers/posts_controller.rb` +- `frontend/src/pages/users/SettingPage.tsx` + +**背景 / 問題** +コンテンツ量が増えるほど、ユーザごとの見たくないタグを制御したくなる。公開後の快適性に効く。 + +**完了条件** +- [ ] ミュートと完全非表示の違いを決める +- [ ] 投稿一覧・ランダム・上映会候補に反映するか決める +- [ ] 設定画面から管理できる + +### 63. [P3] 埋め込み自動再生のユーザ設定を追加する + +**種別**: feature / accessibility + +**対象** +- `frontend/src/components/PostEmbed.tsx` +- `frontend/src/pages/theatres/TheatreDetailPage.tsx` +- `backend/app/models/setting.rb` + +**背景 / 問題** +上映会・投稿詳細で動画埋め込みが増えると、自動再生の好みと端末負荷の差が出る。設定化すべき。 + +**完了条件** +- [ ] 自動再生 on/off 設定を作る +- [ ] Theatre と通常投稿で挙動を分けるか決める +- [ ] モバイルでは既定 off などを検討する + +### 64. [P2] PostEmbed の対応サイトを site parser として分離する + +**種別**: refactor / feature + +**対象** +- `frontend/src/components/PostEmbed.tsx` +- `backend/app/models/post.rb` + +**背景 / 問題** +ニコニコ/YouTube/X/Twitter/その他の判定が UI に寄っている。プレビュー、上映会、検索候補でも同じ判定を使いたい。 + +**完了条件** +- [ ] URL から site_type / embedable / canonical_url を返す関数を作る +- [ ] youtu.be, pixiv, Bluesky, ニジカ投稿局を追加しやすくする +- [ ] 単体テストを追加する + +### 65. [P3] preview API を OpenGraph / oEmbed 対応に寄せる + +**種別**: feature + +**対象** +- `backend/app/controllers/preview_controller.rb` + +**背景 / 問題** +現在は title と screenshot 中心の暫定実装。外部サイトごとのタイトル/サムネ取得は OG/oEmbed をまず見る方が安定する。 + +**完了条件** +- [ ] og:title / og:image / twitter card を読む +- [ ] 既知サイトの抽出器を追加できる構造にする +- [ ] スクリーンショットは最後の手段にする + +### 66. [P2] nico:sync の bot操作 付与条件をさらに絞る + +**種別**: ops / data quality + +**対象** +- `backend/lib/tasks/sync_nico.rake` + +**背景 / 問題** +既存 issue に bot操作 削減が大量にある。同期バッチが通常編集履歴やタグ一覧を汚さないよう、条件を明確化したい。 + +**完了条件** +- [ ] bot操作 を付ける変更種別を表にする +- [ ] タイトル/サムネだけの更新では付けない等を決める +- [ ] 既存 open issue を統合・整理する + +### 67. [P2] nico:sync の外部取得に retry / timeout / 障害通知を入れる + +**種別**: ops + +**対象** +- `backend/lib/tasks/sync_nico.rake` +- `backend/lib/get_videos.py` + +**背景 / 問題** +外部 DB / ネットワーク / サムネ取得に依存するバッチは、失敗時の再実行性が命。ログだけでは運用が辛い。 + +**完了条件** +- [ ] 外部取得に timeout と retry を設定する +- [ ] 部分失敗を記録する +- [ ] 異常終了時に通知する +- [ ] 二重実行ロックを入れる + +### 68. [P2] バッチの二重起動防止ロックを入れる + +**種別**: ops + +**対象** +- `backend/lib/tasks/*.rake` +- `config/schedule.rb` + +**背景 / 問題** +nico:sync や類似度計算が重なって走ると、post_count や履歴に悪影響が出る可能性がある。 + +**完了条件** +- [ ] DB advisory lock か lock file を使う +- [ ] 既に実行中なら安全に終了する +- [ ] ログにスキップ理由を出す + +### 69. [P1] CI で backend spec / frontend lint / typecheck / build を回す + +**種別**: test / ops + +**対象** +- `.gitea/workflows/*` +- `backend` +- `frontend` + +**背景 / 問題** +今回の静的レビューでも hook ルール違反やスキーマドリフトが見える。CI がないと再発する。 + +**完了条件** +- [ ] bundle exec rspec を CI で実行する +- [ ] npm run lint / typecheck / build を CI で実行する +- [ ] migration と schema の一致を確認する +- [ ] 失敗時に merge できない + +### 70. [P1] eslint-plugin-react-hooks を導入し、hook 事故を機械検出する + +**種別**: test / frontend + +**対象** +- `frontend/package.json` +- `frontend/eslint.config.*` +- `frontend/src` + +**背景 / 問題** +条件付き hook は人間レビューで見落としやすい。React アプリなら機械で落とすべき。 + +**完了条件** +- [ ] react-hooks/rules-of-hooks を error にする +- [ ] react-hooks/exhaustive-deps を少なくとも warn にする +- [ ] 既存違反を直す + +### 71. [P1] RSpec にセキュリティ回帰テスト群を追加する + +**種別**: test + +**対象** +- `backend/spec/requests/*` + +**背景 / 問題** +BAN、SSRF、権限、limit、system tag 保護は一度壊れると公開事故になる。仕様より spec で縛るべき。 + +**完了条件** +- [ ] BAN/IP BAN spec +- [ ] preview SSRF rejection spec +- [ ] guest material create forbidden spec +- [ ] system tag PATCH forbidden spec +- [ ] limit clamp spec を追加する + +### 72. [P1] schema.rb と migration の差分検出を CI に入れる + +**種別**: ops / db + +**対象** +- `backend/db/schema.rb` +- `backend/db/migrate` +- `.gitea/workflows/*` + +**背景 / 問題** +wiki_assets のような「schema にはあるが migration がない」状態が発生している。これは将来の環境再構築を壊す。 + +**完了条件** +- [ ] db:drop db:create db:migrate 後の schema diff を CI で見る +- [ ] 差分が出たら失敗にする +- [ ] schema-only 変更を禁止する + +### 73. [P3] Gemfile の未使用 gem を棚卸しする + +**種別**: maintenance + +**対象** +- `backend/Gemfile` +- `backend/Gemfile.lock` + +**背景 / 問題** +jwt や gollum 等、現行コードで使っているか怪しい gem がある。依存は攻撃面積と更新負荷になる。 + +**完了条件** +- [ ] 未使用 gem を rg で確認する +- [ ] 不要なら削除する +- [ ] 必要なら用途を README に書く + +### 74. [P2] フォント assets のライセンスを確認する + +**種別**: legal / release blocker + +**対象** +- `frontend/src/assets/fonts/*` +- `frontend/src/styles/*` + +**背景 / 問題** +フロントにフォントファイルが同梱されている。一般公開・配布前にライセンス確認が必要。ここを雑にすると公開後に面倒になる。 + +**完了条件** +- [ ] 同梱フォントのライセンスと再配布可否を確認する +- [ ] NOTICE または credits に記載する +- [ ] 不明なら Web font/代替フォントへ切り替える + +### 75. [P2] ユーザ引継ぎコードを localStorage 保管のままにするリスクを評価する + +**種別**: security / design + +**対象** +- `frontend/src/App.tsx` +- `frontend/src/lib/api.ts` +- `backend/app/controllers/application_controller.rb` + +**背景 / 問題** +localStorage の引継ぎコードは実装が簡単だが、XSS で即アカウント奪取される。パスワード制にしないとしても、期限・再発行・スコープ制限は検討が必要。 + +**完了条件** +- [ ] 現方式の脅威モデルを書く +- [ ] コード再発行時に旧コードを即失効するか決める +- [ ] 管理者/member だけ追加防御するか検討する + +### 76. [P2] Post / Tag / Wiki の操作ログを共通 AuditLog に寄せる + +**種別**: architecture / audit + +**対象** +- `backend/app/models/*_version.rb` +- `backend/app/controllers/*` + +**背景 / 問題** +各 version table は便利だが、管理画面で「このユーザが何をしたか」を横断表示するには共通ログが欲しい。 + +**完了条件** +- [ ] AuditLog テーブルの要否を判断する +- [ ] 対象種別・対象 ID・操作・ユーザ・IP を保存する +- [ ] 管理画面で横断検索できる + +### 77. [P1] 管理画面の最小構成を作る + +**種別**: feature / admin + +**対象** +- `frontend/src/pages/admin/*` +- `backend/app/controllers/admin/*` + +**背景 / 問題** +公開前に必要なのは派手な機能ではなく、BAN、履歴確認、差し戻し、通報確認を 1 箇所で触れること。今は運用導線が分散/不足している。 + +**完了条件** +- [ ] admin namespace を作る +- [ ] ユーザ/BAN/履歴/御意見番の最小画面を作る +- [ ] admin 以外アクセス不可にする + +### 78. [P2] 既存 open issue の「実装済み疑い」を整理する + +**種別**: project management + +**対象** +- `btrc-hub.issue.sql` +- `Gitea issues` + +**背景 / 問題** +#164 タグ検索機能作成、#171 OR/NOT 検索など、現ソースでは既に実装済みに見える open issue がある。課題表が腐ると優先順位判断が狂う。 + +**完了条件** +- [ ] 実装済み issue を close する +- [ ] 残作業があるものは title/body を更新する +- [ ] 巨大 issue は小 issue に分割する + +### 79. [P1] 公開前チェックリストをリポジトリに置く + +**種別**: release / ops + +**対象** +- `README.md` +- `docs/release-checklist.md` + +**背景 / 問題** +タグ広場は機能量が増えており、「何が揃ったら公開できるか」が頭の中に寄っている。公開判断を文書化しないとスコープが永遠に膨らむ。 + +**完了条件** +- [ ] 法務、BAN、管理画面、履歴復元、Wiki 検索、バックアップ、監視のチェックリストを作る +- [ ] P0/P1 issue と対応付ける +- [ ] 公開しない理由と公開できる条件を分ける + +### 80. [P1] バックアップ / リストア手順を文書化し、定期検証する + +**種別**: ops / disaster recovery + +**対象** +- `docs/ops/*` +- `backend/db` +- `ActiveStorage/R2` + +**背景 / 問題** +共同編集型の知識基盤は DB と添付ファイルを失うと終わる。バックアップよりリストア検証が本体。 + +**完了条件** +- [ ] DB dump 手順を書く +- [ ] R2/ActiveStorage のバックアップ方針を書く +- [ ] ステージングへリストアする手順を作る +- [ ] 月 1 回など検証周期を決める + +### 81. [P2] 監視とログ確認の最低線を作る + +**種別**: ops + +**対象** +- `config/environments/production.rb` +- `server config` +- `docs/ops/*` + +**背景 / 問題** +公開後は「落ちてから気づく」だと遅い。cron 失敗、500 増加、容量増加、R2 エラー程度は見たい。 + +**完了条件** +- [ ] HTTP 死活監視を入れる +- [ ] cron/batch 失敗通知を入れる +- [ ] 500 ログ集計を見る +- [ ] DB/R2 容量の確認手順を書く + +### 82. [P2] レスポンシブ編集 UI の実機確認チェックを作る + +**種別**: UX / mobile + +**対象** +- `frontend/src/pages/posts/*` +- `frontend/src/pages/tags/*` +- `frontend/src/pages/wiki/*` +- `frontend/src/components/TopNav.tsx` + +**背景 / 問題** +スマホで PC 同等の快適さを求めるなら、閲覧だけでなく投稿・タグ編集・Wiki 編集の実機確認が必要。 + +**完了条件** +- [ ] 主要画面のスマホ確認表を作る +- [ ] タグ補完・Markdown 編集・TopNav を重点確認する +- [ ] 横幅崩れとキーボード表示時の問題を直す + +### 83. [P2] タグサイドバーの長い半角文字折り返しを修正する + +**種別**: UX / existing issue + +**対象** +- `frontend/src/components/TagSidebar.tsx` +- `frontend/src/components/TagLink.tsx` + +**背景 / 問題** +既存 #180。長い半角タグ名が見切れる問題はタグ基盤として地味に痛い。 + +**完了条件** +- [ ] break-all / overflow-wrap / min-width を調整する +- [ ] 長い URL 風タグ・英数字タグで確認する +- [ ] PC/スマホ両方で崩れない + +### 84. [P3] アキネータ風お遊び機能の最小仕様を切る + +**種別**: feature / product + +**対象** +- `docs/spec/*` +- `frontend/src/pages/*` +- `backend/app/controllers/*` + +**背景 / 問題** +公開時の話題性として候補に上がっているが、仕様がまだ霧。ゼロイチ系としては小さく切らないと沼になる。 + +**完了条件** +- [ ] 対象をキャラ/ミーム/投稿のどれにするか決める +- [ ] 質問生成をタグ階層ベースにするか決める +- [ ] MVP を 5〜10 問で当てる程度に制限する + +### 85. [P2] 「主要 Wiki 5000 ページ」計画を生成・レビュー・公開の工程に分ける + +**種別**: content ops + +**対象** +- `wiki data` +- `docs/content-plan.md` + +**背景 / 問題** +5000 ページ目標は野心として正しいが、そのままだと精神論になる。1 行説明でよいなら、タグ優先度と作業単位に落とすべき。 + +**完了条件** +- [ ] 主要タグ抽出クエリを作る +- [ ] 優先度 A/B/C に分ける +- [ ] 1 行説明テンプレを作る +- [ ] 未整備タグ一覧ページを作る + +### 86. [P3] Wiki 下書き保存を実装する + +**種別**: feature / UX + +**対象** +- `frontend/src/pages/wiki/WikiEditPage.tsx` +- `backend/app/controllers/wiki_pages_controller.rb` + +**背景 / 問題** +Wiki 編集が長文化すると、ブラウザ落ちや競合で書きかけが消える。公開後の編集体験に効く。 + +**完了条件** +- [ ] local draft と server draft のどちらにするか決める +- [ ] ページ/ユーザ単位で下書きを保存する +- [ ] 復元 UI を出す + +### 87. [P2] Wiki 編集メッセージ入力欄を追加する + +**種別**: feature / audit + +**対象** +- `frontend/src/pages/wiki/WikiNewPage.tsx` +- `frontend/src/pages/wiki/WikiEditPage.tsx` +- `backend/app/controllers/wiki_pages_controller.rb` + +**背景 / 問題** +バックエンドは message を受けられるが、フロントに入力欄がない。履歴を見る時に「なぜ変更したか」が残らない。 + +**完了条件** +- [ ] 新規/編集フォームに message 欄を追加する +- [ ] 空欄可にする +- [ ] 履歴画面で表示する + +### 88. [P2] WikiDiffPage の query 変更時再取得と key 不足を直す + +**種別**: bug / frontend + +**対象** +- `frontend/src/pages/wiki/WikiDiffPage.tsx` + +**背景 / 問題** +diff 取得 useEffect の依存配列が空で、id/from/to が変わっても再取得されない。map の key も不足している。 + +**完了条件** +- [ ] 依存配列に id/from/to を入れる +- [ ] diff 行に安定 key を付ける +- [ ] ローディング/エラー状態を出す + +### 89. [P2] Front/back の camelCase / snake_case 変換境界を明文化する + +**種別**: DX / refactor + +**対象** +- `frontend/src/lib/api.ts` +- `backend/app/representations/*` +- `frontend/src/types.ts` + +**背景 / 問題** +レスポンスは camelize しているが、送信 params は手動で snake_case を混ぜている。API が増えるほどズレる。 + +**完了条件** +- [ ] 送信も decamelize するか、手動 snake_case 方針にするか決める +- [ ] FormData の扱いを明記する +- [ ] 型と representation を対応させる + +### 90. [P1] Tag / Wiki / Post の title/name collation 問題を仕様化する + +**種別**: data integrity / existing issue + +**対象** +- `backend/db/schema.rb` +- `backend/app/models/tag_name.rb` +- `backend/app/models/wiki_page.rb` + +**背景 / 問題** +DB collation が utf8mb4_0900_ai_ci のため、ひらがな/カタカナ/濁点/大文字小文字などの同一視が起こりうる。既存 #237 と絡む根深い問題。 + +**完了条件** +- [ ] 同一視される文字の実例をテストする +- [ ] binary collation にするか、アプリ側で同定文字を制定するか決める +- [ ] 作成前に衝突警告を出す \ No newline at end of file