776dea87d9
開発環境では、**DB を壊さない前提で、migration → API → 画面 → 同期 → ZIP → 履歴**の順に見るのがよいです。今回の差分は素材管理全体に触っているので、単体でチョンチョン見るより、素材の一生を通すのが早いです。
## 0. 先に方針
**やらないこと:**
```sh id="snb3i6"
rails db:drop
rails db:reset
rails db:setup
DISABLE_DATABASE_ENVIRONMENT_CHECK=1 ...
```
これは禁止。
開発 DB に本番データを入れているなら、床板を剥がして耐震確認するようなものです。
---
## 1. migration 確認
まず現在の状態を見る。
```sh id="qsdfpt"
cd backend
RAILS_ENV=development bundle exec rails db:migrate:status
```
その後、通常 migration。
```sh id="a89fir"
RAILS_ENV=development bundle exec rails db:migrate
```
見るポイント:
```txt id="c1u57a"
materials に source_* / normalized_source_key / version_no がある
material_versions に event_type / file snapshot / source snapshot がある
material_export_items がある
material_sync_suppressions がある
material_sync_sources がある
既存 materials に material_versions version_no=1 create が backfill されている
```
確認用:
```sh id="g4vd4m"
RAILS_ENV=development bundle exec rails runner '
puts "materials=#{Material.count}"
puts "versions=#{MaterialVersion.count}"
puts "materials without versions=#{Material.left_joins(:material_versions).where(material_versions: { id: nil }).count}"
puts "sync suppressions table=#{ActiveRecord::Base.connection.table_exists?(:material_sync_suppressions)}"
'
```
ここで `materials without versions=0` になれば、backfill は通っています。
---
## 2. 既存素材一覧の画面確認
フロントを起動して `/materials` を見る。
```sh id="vl28jd"
cd frontend
npm run dev
```
見る観点:
```txt id="i2ovxw"
初期表示で素材が出る
初期表示ではグルーピングがオフ
タグなし素材も出る
カード表示でサムネまたは代替テキストが出る
一覧表示に切り替えられる
q / tag_state / media_kind / sort / direction が効く
```
ここでまず、普通の素材一覧が壊れていないことを確認します。
---
## 3. 左タグバーの確認
`/materials` を開いて左タグバーからタグを選ぶ。
見る観点:
```txt id="n6udul"
URL が tag_id=...&include_descendants=1&group_by=parent_tag になる
選択中タグが左バーで強調される
一覧上部に「選択中」の表示が出る
「タグ選択を解除」で通常表示に戻れる
解除後、tag_id / include_descendants / group_by / page が消える
子タグ・孫タグの素材も一覧に出る
親タググルーピングされる
```
特に重要なのはこれ。
```txt id="d33os0"
親タグ A を選択
A に直接紐づく素材
A > B に紐づく素材
A > B > C に紐づく素材
が同じ一覧に出ること
```
---
## 4. 選択タグから素材追加
左タグからタグを選択した状態で、一覧上部の **このタグに素材を追加** を押す。
見る観点:
```txt id="qdd5uw"
素材追加画面の tag 欄に選択中タグ名が初期入力されている
file または url を指定して保存できる
保存後 return_to で元のタグ選択済み一覧に戻る
戻った一覧に追加した素材が出る
material_versions に create が 1 件できる
```
Rails console でも確認できます。
```sh id="i07rrb"
RAILS_ENV=development bundle exec rails runner '
m = Material.order(id: :desc).first
puts({ id: m.id, tag: m.tag&.name, versions: m.material_versions.count, version_no: m.version_no }.inspect)
'
```
---
## 5. グループ見出しから素材追加
親タググルーピング表示中に、各グループ見出しの **このタグに素材を追加** を押す。
見る観点:
```txt id="fpx8w4"
グループタグ名が tag 欄に初期入力される
保存後、元の一覧に戻る
追加素材がそのグループ内に出る
```
ここは今回の導線の肝です。棚の見出しから直接その棚へ素材を置けるかを見る。
---
## 6. 素材更新と履歴
既存素材の詳細または編集導線から、タグ・URL・ファイル・export path を更新する。
見る観点:
```txt id="w36i61"
更新前 snapshot が無ければ create が補われる
更新後 update version ができる
file_blob_id / file_filename / file_sha256 が material_versions に入る
export_paths_json が履歴に残る
/materials/changes または /materials/versions で履歴が見える
```
console:
```sh id="a3okhv"
RAILS_ENV=development bundle exec rails runner '
m = Material.order(updated_at: :desc).first
puts m.material_versions.order(:version_no).map { |v|
[v.version_no, v.event_type, v.tag_name, v.file_filename, v.file_sha256, v.export_paths_hash]
}.inspect
'
```
---
## 7. サムネイル
画像素材を追加して、一覧にサムネイルが出るか確認。
動画素材があるなら、`ffmpeg` が入っている環境で backfill。
```sh id="qxm8fj"
cd backend
RAILS_ENV=development bundle exec rails materials:thumbnails:backfill
```
見る観点:
```txt id="s9a1vf"
画像は 180x180 のサムネが付く
動画はフレームからサムネが作られる
非対応ファイルは代替テキスト表示になる
ログに result が出る
```
---
## 8. ZIP export
export path がある素材を用意して、ブラウザで確認。
```txt id="aq7f7o"
/materials/download.zip?profile=legacy_drive
```
見る観点:
```txt id="h4rve5"
ZIP が落ちる
entry path が material_export_items.export_path になる
disabled な export item は入らない
ファイル実体が欠けている場合は 422 と missing_files が返る
```
---
## 9. 抑止
`/materials/suppressions` で path prefix 抑止を追加する。
見る観点:
```txt id="w80jbu"
member で作成できる
guest は forbidden / unauthorized
google_drive_path_prefix で既存素材が discard される
discard 履歴が material_versions に残る
同期時に同じ source_path 配下が再作成されない
```
---
## 10. Google Drive 同期
開発環境では、まず小さいフォルダでやるのがよいです。
いきなり本番素材集フォルダを食わせると、ログが藪になります 🌿
必要な ENV:
```sh id="kzbb7f"
GOOGLE_DRIVE_SERVICE_ACCOUNT_EMAIL=...
GOOGLE_DRIVE_PRIVATE_KEY_PATH=...
MATERIAL_SYNC_SOURCE_KIND=google_drive_path
MATERIAL_SYNC_SOURCE_FILE_ID=<folder_id>
MATERIAL_SYNC_SOURCE_NAME=dev-small-folder
MATERIAL_SYNC_SOURCE_PROFILE=legacy_drive
```
seed で source 作成、または console で作成。
```sh id="sec40c"
RAILS_ENV=development bundle exec rails db:seed
RAILS_ENV=development bundle exec rails materials:sync
```
見る観点:
```txt id="a5ltqu"
imported / updated / unchanged / suppressed / failed がログに出る
2 回目実行で unchanged が増える
tag は nil のままでも保存できる
人手で tag / url を付けた既存同期素材が、再同期で消えない
Google native file は skip される
download 後 sha256 block が効く
```
---
## 11. schema.rb は別途確認
これはテストというより merge gate です。
今回まだ怪しいので、差分に以下が混ざっていないことを確認します。
```txt id="s3k6da"
wiki_assets 削除
wiki_pages.next_asset_no 削除
素材管理と無関係な CHECK constraint 削除
素材管理と無関係な index order 消失
```
ここが残るなら、機能テストが通っても merge は止めた方がいいです。
---
## 最小テスト順
時間がないなら、この順で十分です。
```txt id="c1k2pw"
1. db:migrate
2. materials without versions = 0 を確認
3. /materials 初期表示
4. 左タグ選択 → 子孫込み表示 → グルーピング
5. タグ選択解除
6. 選択タグから素材追加 → return_to で戻る
7. グループ見出しから素材追加
8. 更新して material_versions を確認
9. ZIP export
10. 小さい Drive folder で materials:sync を 2 回
```
これで、今回の差分の主要な導線はほぼ踏めます。
Reviewed-on: #381
Co-authored-by: miteruzo <miteruzo@naver.com>
Co-committed-by: miteruzo <miteruzo@naver.com>
14 KiB
14 KiB
AGENTS.md
Project overview
BTRC Hub / タグ広場 is a split Rails API and React frontend repository.
- Backend: Rails API under
backend/. - Frontend: React + TypeScript + Vite under
frontend/. - Docs: lightweight command notes under
docs/. - There is no README or Makefile at the repository root as of this inspection.
Stack
- Backend: Ruby
3.2.2frombackend/.ruby-version, Rails~> 8.0.2. - Backend dependencies include
mysql2,sqlite3,rspec-rails,factory_bot_rails,rack-cors,jwt,discard,gollum,whenever,aws-sdk-s3,brakeman, andrubocop-rails-omakase. - Frontend: React
^19.1.0, TypeScript~5.8.3, Vite^6.3.5. - Frontend data/UI dependencies include Axios, TanStack Query, Tailwind CSS, Framer Motion, Radix UI components, lucide-react, MDX/Markdown tooling, and Zustand.
Main directories
backend/app/controllers: Rails API controllers.backend/app/models: Active Record models.backend/app/representations: API response representation classes.backend/app/services: domain services such as version recording, wiki commit, YouTube sync, and similarity calculation.backend/config/routes.rb: API routes.backend/db/migrate: migrations.backend/db/schema.rb: current schema snapshot.backend/lib/tasks: custom Rake tasks.backend/spec: RSpec tests.backend/test: Rails minitest files that still exist in the tree.frontend/src/App.tsx: frontend route definitions and initial user setup.frontend/src/pages: page-level React components.frontend/src/components: shared and feature components.frontend/src/lib: API client helpers, query keys, prefetchers, and domain helpers.frontend/src/stores: Zustand stores.docs/commands.md: command notes.
Commands
Only list commands that are backed by files inspected in this repository.
Backend
The following binstubs exist under backend/bin:
cd backend
bin/setup
bin/dev
bin/rails
bin/rake
bin/rubocop
bin/brakeman
bin/kamal
bin/thrust
Common Rails/Rake usage through existing binstubs:
cd backend
bin/rails db:prepare
bin/rails db:migrate
bin/rails routes
bin/rails server
bin/rake
bin/rubocop
bin/brakeman
RSpec is present in Gemfile and .rspec exists:
cd backend
bundle exec rspec
Frontend
The following npm scripts exist in frontend/package.json:
cd frontend
npm run dev
npm run build
npm run lint
npm run test
npm run test:run
npm run preview
npm run build runs tsc -b && vite build, then postbuild runs
node scripts/generate-sitemap.js.
npm run test runs Vitest in watch mode. Use npm run test:run for a non-watch frontend test run.
Coding style
- Prefer precise, minimal changes.
- Do not flatter or over-explain.
- Explain risks directly.
- Prefer single quotes for strings unless interpolation or escaping makes double quotes better.
- Ruby: never put a space before method-call parentheses.
- Ruby:
render系メソッド呼び出しでは、keyword 引数付きでも括弧を書かない。 - Ruby: never put a line break immediately before
). - Ruby: do not use
%wor%i. - In Ruby, when an
ifcondition is split across multiple lines and combines clauses with&&or||, wrap the whole condition in parentheses. - Ruby hashes are not blocks; keep
}on the same line as the final pair. - Ruby hashes keep the first pair on the same line as
{unless line length requires a break. - Short Ruby hashes may stay visually compact across two lines with the first pair kept on the opening line and aligned continuation pairs below it.
- Ruby blocks use separate
{ ... }rules from hashes, with 2-space body indentation. - For arrays, never put whitespace or a line break immediately before
]. - Keep the first element on the same line as
[by default. - If an array would exceed the line limit, break after
[and indent elements by 4 spaces. - TypeScript and Python: use GNU-style spacing before parentheses where syntactically valid.
- Never write Ruby, TypeScript, or TSX lines longer than 99 characters.
- Aim to keep Ruby, TypeScript, and TSX lines within 79 characters where practical.
- TypeScript and TSX use 4-space logical indentation.
- In TypeScript and TSX only, replace every leading run of 8 spaces with a tab.
- Tabs are only for leading indentation, never for spaces after non-space text.
- TypeScript and TSX imports may stay on one line if they remain within the line limit; do not expand short type-only imports mechanically.
- In TypeScript and TSX, when breaking a line at an operator, break before the operator and put the operator at the beginning of the next line. A trailing operator at end of line is unacceptable. This rule does not apply to Ruby, where it can change the syntactic structure.
- In TypeScript and TSX, when a function takes one destructured object argument plus an inline type, prefer this shape when it fits locally:
const helper = (
{ value, flag }: { value: string
flag: boolean },
): Result => {
// ...
}
- In TypeScript and TSX, put
switchcase block braces on their own lines when a case needs a lexical block:
case 'yes':
case 'no':
{
const expected = valueFor (item)
return expected == null || expected === answer
}
- In TypeScript and TSX, use
value == nullandvalue != nullas the default nullish checks. Do not use=== null,=== undefined,!== null, or!== undefined. - If code appears to need a distinction between
nullandundefined, treat that as a design smell and revise the logic to avoid the distinction. External library APIs that explicitly require distinguishing the two are the only exception. - In TypeScript and TSX, keep short arrays on one line when they fit under the line limit; break arrays only when readability or line length requires it.
- In TypeScript and TSX, when a ternary expression is split across multiple
lines, align
?and:with the condition expression. Do not indent?and:one extra level under the condition.
const value =
condition
? consequent
: alternate
- In TypeScript and TSX, keep short ternary expressions on one line when they fit cleanly under the line limit.
- In TypeScript and TSX, prefer ternary expressions for simple conditional
value selection. Do not replace a clear ternary with
ifstatements, and do not introduce immediately invoked functions just to avoid or reformat a ternary expression. - In TypeScript and TSX, do not write
letfollowed by laterifassignments when the value can be expressed as a singleconstinitializer. Preferconstbecause it prevents accidental later reassignment. - When fixing formatting, change formatting only. Do not change expression structure, control flow, or variable mutability unless the requested style explicitly requires it.
- Do not add production dependencies without explicit approval.
- Do not create, modify, or run tests unless the user explicitly asks for test work. When the user asks for tests, keep working and rerun them until they pass or the remaining failure is clearly blocked.
Backend rules
- Inspect existing routes, controllers, models, services, and specs before editing backend behavior.
- Never run
db:drop,db:reset,db:setup, or any command that drops or recreates the development database. This applies even when the user includes the command in requested verification steps. - Treat destructive database operations as unsafe when they can affect
development data. Ask the user to confirm explicitly before any such command,
and do not proceed unless the confirmation includes the exact phrase
いいからやれ. - Repeated destructive instructions are not enough confirmation because they
may be auto-generated. Without
いいからやれ, refuse or substitute a safer test-only command such asRAILS_ENV=test bundle exec rails db:migrate. - For API behavior changes, add or update request specs under
backend/spec/requestsonly when the user explicitly asks for tests. - Prefer RSpec for new backend tests; existing minitest files under
backend/testdo not make minitest the default for new coverage. - Do not weaken authentication, BAN user checks, or IP BAN checks.
- Preserve the
X-Transfer-Codeuser identification flow unless the task explicitly changes authentication. - Be careful with version tables,
version_no, optimistic concurrency, wiki revisions, and restore/diff behavior. - Be careful with tag names, tag normalization, implications, similarities, and discard behavior.
- Be sensitive to N+1 queries; avoid introducing them and proactively fix existing N+1 issues in the code path being edited.
- Keep migration files and
backend/db/schema.rbconsistent when changing schema.
Frontend rules
- Use
frontend/src/lib/api.tsfor API calls so headers and camelCase conversion stay consistent. - Add or reuse TanStack Query keys through
frontend/src/lib/queryKeys.ts; avoid ad hoc query key arrays. - Encode URL path-segment values with
encodeURIComponent. - React hooks must be called unconditionally.
- Keep page-level code under
frontend/src/pagesand shared UI/feature code underfrontend/src/componentsunless existing patterns point elsewhere. - Match existing Tailwind, component, and import alias conventions.
<a href="#">is acceptable for event-only controls when it fits the local UI pattern. Do not use<a>for internal navigation or other non-external links.- Internal links must use
PrefetchLink. - External links must use
<a>withtarget="_blank". - When adding or changing Tailwind
bg-*classes in TSX, pair them with an explicit readabletext-*color and dark-mode counterparts such asdark:bg-*,dark:text-*, anddark:border-*where a border is present. - Do not rely on inherited text color for light backgrounds. This is especially important for chips, cards, buttons, and panels that may inherit white text in dark mode.
- Mobile UI must be checked as a first-class layout. Avoid wide fixed content, make dense controls wrap or scroll intentionally, and keep tag/filter controls usable without horizontal page overflow.
- For mobile horizontal scrollers, make the scroll direction and item sizing explicit, and ensure chip text remains readable in both light and dark modes.
- In TypeScript and TSX, prefer direct comparison operators such as
===and!==over negating a comparison like!(a === b). - In TypeScript and TSX, prefer
++ior--ioveri += 1ori -= 1for simple unit-step counter updates. - For user-facing Japanese text, prefer modern kana usage and natural current phrasing over historical spellings or awkward literal wording.
- For user-facing Japanese ellipses, prefer
……over ASCII....
Frontend TSX style
- Preserve the local TSX formatting style.
- Do not normalize TSX to common Prettier-style React formatting unless explicitly asked.
- Prefer
constarrow functions for TypeScript/TSX component and helper declarations. - Put two blank lines before and after top-level
constfunction declarations, unless imports, exports, or file boundaries make that awkward. - In TSX, indent with 4-space logical indentation.
- In TypeScript and TSX, convert every leading run of 8 spaces to a tab character.
- A leading tab is exactly equivalent to 8 leading spaces.
- In TypeScript and TSX function declarations, including
constarrow function declarations, when the parameter list spans multiple lines, always put the closing parenthesis at the beginning of its own line before the return type or=>. - In TypeScript and TSX, never place a closing parenthesis at the beginning of a line except for a multi-line function declaration parameter list.
- Never place a closing square bracket at the beginning of a line.
- For object literals and other associative-array-style braces, do not place the closing brace at the beginning of a line. Function, lambda, callback, and block closing braces are exempt and should stay on their own line when that fits the local style.
- When writing braces on a single line in TypeScript or TSX JavaScript
context, put exactly one space inside the braces, as in
{ value }or{ key: value }. - Do not add inner spaces to React/JSX expression braces, as in
prop={value},{children}, or<Component>{{...props}}</Component>. - Keep a tag's closing marker on the same line as the final prop when the tag spans multiple lines.
- Do not put
/>or>on its own line unless the existing surrounding code does so. - Keep JSX closing parentheses in the existing compact style, for example
</div>)rather than moving)onto a separate line. - Do not add braces around
if,else, orforbodies when the body is a single physical line. - Always add braces around
if,else, orforbodies when the body spans two or more physical lines, even if it is one statement. - Do not use a leading semicolon for expression statements such as
;([...]).forEach(...); rewrite the expression to avoid ASI hazards explicitly, for example withvoid.
Preferred:
const PostFormTagsArea: FC<Props> = ({ tags, setTags, errors, ...rest }) => {
return (
<TextArea
{...rest}
ref={ref}
value={tags}
invalid={errors && errors.length > 0}
onChange={ev => setTags (ev.target.value)}/>)
}
Avoid:
function PostFormTagsArea ({ tags, setTags }: Props) {
return (
<TextArea
value={tags}
onChange={ev => setTags (ev.target.value)}
/>
)
}
Codex workflow
- First inspect existing patterns; do not invent new architecture when a local convention exists.
- Keep changes scoped to the requested issue.
- Do not scan or summarize dependency/generated/runtime directories such as
node_modules,dist,tmp,log, andstorageunless explicitly needed. - Before touching wiki, tag, versioning, BAN, IP BAN, or authentication behavior, inspect the related request specs and service objects.
- If frontend code changes, run only non-test verification commands that
apply, such as
npm run buildandnpm run lint. Runnpm run test:runonly when the user explicitly asks for tests. - If backend code changes, do not run RSpec unless the user explicitly asks for tests.
- If a verification command cannot be run or fails, report the exact command and failure.
Completion criteria
A task is complete only when:
- implementation is complete,
- relevant non-test verification commands pass, or failures are clearly explained,
- unrelated files are not changed,
- migrations and schema are consistent when schema changes are made,
- user-facing behavior is documented when needed.