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>
356 行
14 KiB
Markdown
356 行
14 KiB
Markdown
# 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.2` from `backend/.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`, and `rubocop-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`:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
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:
|
|
|
|
```sh
|
|
cd backend
|
|
bundle exec rspec
|
|
```
|
|
|
|
### Frontend
|
|
|
|
The following npm scripts exist in `frontend/package.json`:
|
|
|
|
```sh
|
|
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 `%w` or `%i`.
|
|
- In Ruby, when an `if` condition 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:
|
|
|
|
```ts
|
|
const helper = (
|
|
{ value, flag }: { value: string
|
|
flag: boolean },
|
|
): Result => {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
- In TypeScript and TSX, put `switch` case block braces on their own lines
|
|
when a case needs a lexical block:
|
|
|
|
```ts
|
|
case 'yes':
|
|
case 'no':
|
|
{
|
|
const expected = valueFor (item)
|
|
return expected == null || expected === answer
|
|
}
|
|
```
|
|
|
|
- In TypeScript and TSX, use `value == null` and `value != null` as the
|
|
default nullish checks. Do not use `=== null`, `=== undefined`,
|
|
`!== null`, or `!== undefined`.
|
|
- If code appears to need a distinction between `null` and `undefined`, 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.
|
|
|
|
```ts
|
|
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 `if` statements, and do
|
|
not introduce immediately invoked functions just to avoid or reformat a
|
|
ternary expression.
|
|
- In TypeScript and TSX, do not write `let` followed by later `if` assignments
|
|
when the value can be expressed as a single `const` initializer. Prefer
|
|
`const` because 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 as `RAILS_ENV=test bundle exec rails db:migrate`.
|
|
- For API behavior changes, add or update request specs under
|
|
`backend/spec/requests` only when the user explicitly asks for tests.
|
|
- Prefer RSpec for new backend tests; existing minitest files under
|
|
`backend/test` do not make minitest the default for new coverage.
|
|
- Do not weaken authentication, BAN user checks, or IP BAN checks.
|
|
- Preserve the `X-Transfer-Code` user 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.rb` consistent when changing schema.
|
|
|
|
## Frontend rules
|
|
|
|
- Use `frontend/src/lib/api.ts` for 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/pages` and shared UI/feature code
|
|
under `frontend/src/components` unless 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>` with `target="_blank"`.
|
|
- When adding or changing Tailwind `bg-*` classes in TSX, pair them with an
|
|
explicit readable `text-*` color and dark-mode counterparts such as
|
|
`dark:bg-*`, `dark:text-*`, and `dark: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 `++i` or `--i` over `i += 1` or `i -= 1` for
|
|
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 `const` arrow functions for TypeScript/TSX component and helper declarations.
|
|
- Put two blank lines before and after top-level `const` function
|
|
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 `const` arrow
|
|
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`, or `for` bodies when the body is a
|
|
single physical line.
|
|
- Always add braces around `if`, `else`, or `for` bodies 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 with `void`.
|
|
|
|
Preferred:
|
|
|
|
```tsx
|
|
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:
|
|
|
|
```tsx
|
|
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`, and `storage` unless 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 build` and `npm run lint`. Run `npm run test:run`
|
|
only 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.
|