561 行
16 KiB
Markdown
561 行
16 KiB
Markdown
# frontend/AGENTS.md
|
|
|
|
## Scope
|
|
|
|
These rules apply to work under `frontend/`.
|
|
|
|
This is a Vite + React + TypeScript app using TanStack Query, Tailwind CSS,
|
|
Framer Motion, Radix UI-style components, MDX, and Zustand.
|
|
|
|
## Commands
|
|
|
|
Use only scripts that exist in `package.json`:
|
|
|
|
```sh
|
|
npm run dev
|
|
npm run build
|
|
npm run lint
|
|
npm run preview
|
|
```
|
|
|
|
`npm run build` runs `tsc -b && vite build`, and `postbuild` runs
|
|
`node scripts/generate-sitemap.js`.
|
|
|
|
There is currently no `test` script in `package.json`. Do not run or report
|
|
`npm test` unless a test script is added.
|
|
|
|
After frontend changes, run:
|
|
|
|
```sh
|
|
npm run build
|
|
npm run lint
|
|
```
|
|
|
|
If either command cannot be run or fails, report the exact command and failure.
|
|
|
|
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.
|
|
|
|
## TypeScript
|
|
|
|
- TypeScript is strict. `tsconfig.app.json` enables `strict`,
|
|
`noUnusedLocals`, `noUnusedParameters`, `erasableSyntaxOnly`,
|
|
`noFallthroughCasesInSwitch`, and `noUncheckedSideEffectImports`.
|
|
- Keep types explicit at module boundaries, API helpers, and exported utilities.
|
|
- Use `import type` for type-only imports.
|
|
- Prefer existing shared types from `src/types.ts` before adding local duplicate types.
|
|
- Preserve the repository's existing spacing style in TypeScript, including
|
|
GNU-style spacing before call parentheses where it is already used.
|
|
- Prefer single quotes for strings unless interpolation or escaping makes double quotes better.
|
|
- Never write a TypeScript or TSX line longer than 99 characters.
|
|
- Aim to keep TypeScript and TSX lines within 79 characters where practical.
|
|
- Use 4-space logical indentation in TypeScript and TSX.
|
|
- 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.
|
|
- In TypeScript and TSX only, replace every leading run of 8 spaces with a tab
|
|
to reduce bytes.
|
|
- Treat one leading tab as exactly equivalent to 8 leading spaces.
|
|
- Use tabs only for leading indentation. Never replace spaces that occur after
|
|
a non-space character on the same line.
|
|
|
|
## React
|
|
|
|
- Use function components.
|
|
- Existing page components commonly export an anonymous function satisfying
|
|
`FC`; match nearby file style when editing.
|
|
- React hooks must be called unconditionally and at the top level of components or custom hooks.
|
|
- Gate editing and other privileged controls with shared permission helpers
|
|
such as `canEditContent`, instead of showing controls and relying only on a
|
|
later API failure.
|
|
- Keep page-level components under `src/pages`.
|
|
- Keep shared and feature components under `src/components`.
|
|
- Use `react-router-dom` route params and navigation patterns already present in `src/App.tsx`.
|
|
- Encode URL path-segment values with `encodeURIComponent`.
|
|
|
|
## TanStack Query
|
|
|
|
- Use `@tanstack/react-query` for server state.
|
|
- Query keys should come from `src/lib/queryKeys.ts`; add key builders there
|
|
instead of using ad hoc arrays in components.
|
|
- Fetch functions should live in domain helpers under `src/lib`, such as
|
|
`posts.ts`, `tags.ts`, or `wiki.ts`.
|
|
- Use `useQueryClient().invalidateQueries` with the shared root keys when
|
|
mutations affect cached lists or detail views.
|
|
- The app-wide `QueryClient` is configured in `src/main.tsx`; do not create
|
|
additional clients in feature code.
|
|
|
|
## API calls
|
|
|
|
- Use `src/lib/api.ts` for HTTP calls.
|
|
- The API wrapper attaches `X-Transfer-Code` from `localStorage` and converts
|
|
non-blob responses to camelCase.
|
|
- Send Rails snake_case params and request body keys where the backend expects them.
|
|
- Do not bypass the API wrapper unless there is a specific reason, such as a
|
|
third-party request outside the Rails API.
|
|
- For blob responses, pass `responseType: 'blob'` so the wrapper does not camelCase the body.
|
|
|
|
## Imports and aliases
|
|
|
|
- The `@` alias points to `frontend/src`.
|
|
- Prefer `@/...` imports for app code instead of long relative paths.
|
|
- Keep type imports separate with `import type`.
|
|
- Match existing import grouping: external packages, app modules, then type imports.
|
|
|
|
## Tailwind and UI
|
|
|
|
- Tailwind scans `src/**/*.{html,js,ts,jsx,tsx,mdx}`.
|
|
- Use `cn` from `src/lib/utils.ts` for conditional class names and class merging.
|
|
- Reuse components from `src/components/common`, `src/components/layout`, and
|
|
`src/components/ui` before adding new primitives.
|
|
- Keep Tailwind classes consistent with nearby components.
|
|
- Prefer restrained, content-first UI chrome: avoid adding card backgrounds,
|
|
heavy borders, or nested panel decoration unless the surrounding screen
|
|
already uses them.
|
|
- Keep operational screens dense and direct; trim explanatory copy and use
|
|
short Japanese labels that fit the control.
|
|
- Preserve existing Japanese tone and orthography in nearby UI text, including
|
|
old-kana wording where the file already uses it.
|
|
- When adding dynamic tag color classes, update `tailwind.config.js` safelist
|
|
if the class cannot be statically detected.
|
|
- Do not introduce new UI libraries or production dependencies without approval.
|
|
|
|
## TSX formatting
|
|
|
|
- Preserve compact TSX expression shapes such as inline ternary branches and
|
|
closing `</div>)` forms when nearby code uses them.
|
|
- Treat TSX formatting rules as hard constraints, not preferences. Before
|
|
finishing a TSX edit, inspect the edited hunks for closing `)`, `]`, and `}`
|
|
placement and fix violations instead of relying on formatter defaults.
|
|
- After every TSX edit, perform a style-only self-review of the edited hunks
|
|
before running verification or reporting completion. The task is not complete
|
|
while any edited TSX hunk violates these local formatting rules.
|
|
- The TSX self-review must classify every edited leading or trailing `)`, `]`,
|
|
and `}` by syntax role before deciding whether it is valid. Do not apply a
|
|
rule by glyph alone. A closing `)` for a function parameter list is different
|
|
from a closing `)` for a function call. A closing `}` for a block is different
|
|
from a closing `}` for an object, type literal, import list, or destructuring
|
|
pattern.
|
|
- The TSX self-review must confirm there are no common Prettier-style React
|
|
component declarations with a multi-line destructured parameter.
|
|
- The TSX self-review must confirm multi-line function declaration parameter
|
|
`)` placement follows the detailed parameter-list rules below.
|
|
- The TSX self-review must confirm call-expression `)` is never at the
|
|
beginning of a line.
|
|
- The TSX self-review must confirm object/type/import/destructuring `}` is not
|
|
at the beginning of a line.
|
|
- The TSX self-review must confirm multi-line function/lambda/callback/block
|
|
`}` is on its own line and never at the end of the previous line.
|
|
- The TSX self-review must confirm array `]` is not at the beginning of a line.
|
|
- The TSX self-review must confirm JSX closing markers and closing parentheses
|
|
keep the surrounding compact style.
|
|
- The TSX self-review must confirm leading indentation follows 4-space logical
|
|
indentation with tabs only as leading 8-space compression.
|
|
- For long Tailwind `className` strings, wrap across lines only when needed.
|
|
- Keep continuation indentation aligned with the 4-space logical indentation
|
|
rule, using tabs only as leading 8-space compression.
|
|
- In TypeScript and TSX function declarations, including `const` arrow
|
|
function declarations, classify the parameter list before placing the closing
|
|
`)`.
|
|
- If the parameter list itself is given its own multi-line block after the
|
|
function's opening `(`, put the closing parameter `)` at the beginning of its
|
|
own line before the return type or `=>`.
|
|
- If the only line break is inside a parameter's inline type, object type, or
|
|
destructuring shape, and the parameter list itself is not split as a separate
|
|
block, keep the closing parameter `)` on the same line as that parameter's
|
|
final `}`. In this case, moving `)` to a new line is wrong.
|
|
- Do not write React component declarations in the common Prettier form
|
|
`const Component = ({ ... }) => (` when the destructured parameter spans
|
|
multiple lines. Use the project form with the opening `(` after `=`, the
|
|
destructured object as the argument, and the closing parameter `)` on its own
|
|
line before `=>`.
|
|
- 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, type literals, import/export named bindings, destructuring
|
|
patterns, and other associative-array-style braces, do not place the closing
|
|
brace at the beginning of a line. Keep `}` on the same line as the final
|
|
property, binding, or specifier unless that would violate the line limit.
|
|
Function, lambda, callback, and block closing braces are exempt and should
|
|
stay on their own line when that fits the local style. When a function,
|
|
lambda, callback, or block body spans multiple lines, do not put its closing
|
|
`}` at the end of the previous line.
|
|
- For arrays and tuple-like lists, do not place the closing `]` at the
|
|
beginning of a line. Keep `]` on the same line as the final element unless
|
|
that would violate the line limit.
|
|
- For function and method calls, do not place the closing `)` at the beginning
|
|
of a line. The only TypeScript/TSX exception is the closing parameter `)` of a
|
|
multi-line function declaration.
|
|
- 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.
|
|
- Spell British English identifiers correctly when the feature name uses
|
|
British English. Use `Behaviour`, not `Behavior`, for new component and file
|
|
names unless editing an already established American-English API.
|
|
- Avoid reformatting unrelated JSX.
|
|
|
|
### Delimiter decision table
|
|
|
|
Use this table before accepting any edited TypeScript or TSX hunk. The table is
|
|
more authoritative than formatter habit.
|
|
|
|
#### Import and export named bindings
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
import {
|
|
Button,
|
|
Card,
|
|
} from '@/components/ui'
|
|
```
|
|
|
|
Good:
|
|
|
|
```ts
|
|
import { Button,
|
|
Card } from '@/components/ui'
|
|
```
|
|
|
|
Also acceptable when short:
|
|
|
|
```ts
|
|
import { Button, Card } from '@/components/ui'
|
|
```
|
|
|
|
Rule: named-binding `}` is associative-array-style syntax. It must not be alone
|
|
at the beginning of a line. Prefer keeping `{` with the first binding and `}`
|
|
with the final binding when this fits the line limit.
|
|
|
|
#### Type literals
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
type Props = {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
}
|
|
```
|
|
|
|
Good:
|
|
|
|
```ts
|
|
type Props = {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void }
|
|
```
|
|
|
|
Rule: type-literal `}` is not a block close. It must stay on the same line as
|
|
the final property unless that would break the hard line limit.
|
|
|
|
#### Object literals
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
const value = {
|
|
open,
|
|
activeScope,
|
|
}
|
|
```
|
|
|
|
Good:
|
|
|
|
```ts
|
|
const value = {
|
|
open,
|
|
activeScope }
|
|
```
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
const value = useMemo (() => ({
|
|
open,
|
|
activeScope,
|
|
}), [open, activeScope])
|
|
```
|
|
|
|
Good:
|
|
|
|
```ts
|
|
const value = useMemo (() => ({
|
|
open,
|
|
activeScope }), [open, activeScope])
|
|
```
|
|
|
|
Rule: object-literal `}` is associative-array-style syntax. It must not be on a
|
|
line by itself. Keep it with the final property, and keep call `)` off the
|
|
beginning of a line.
|
|
|
|
#### Destructuring parameters
|
|
|
|
Bad:
|
|
|
|
```tsx
|
|
const Component: FC<Props> = ({
|
|
open,
|
|
onOpenChange,
|
|
}) => {
|
|
return null
|
|
}
|
|
```
|
|
|
|
Good:
|
|
|
|
```tsx
|
|
const Component: FC<Props> = (
|
|
{ open,
|
|
onOpenChange },
|
|
) => {
|
|
return null
|
|
}
|
|
```
|
|
|
|
Rule: when a React component or helper takes a destructured object parameter
|
|
that spans multiple lines, do not use the Prettier-style `= ({ ... }) =>`
|
|
shape. Put the function parameter list on its own lines. The destructuring `}`
|
|
stays with the final binding. The parameter-list `)` is then allowed and
|
|
required at the beginning of its own line.
|
|
|
|
#### Inline typed destructuring parameter
|
|
|
|
Good:
|
|
|
|
```ts
|
|
const RouteTransitionWrapper = ({ user, setUser }: {
|
|
user: User | null
|
|
setUser: Dispatch<SetStateAction<User | null>> }) => {
|
|
return null
|
|
}
|
|
```
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
const RouteTransitionWrapper = ({ user, setUser }: {
|
|
user: User | null
|
|
setUser: Dispatch<SetStateAction<User | null>>
|
|
}) => {
|
|
return null
|
|
}
|
|
```
|
|
|
|
Rule: this is not a separately split parameter-list block. The line break is
|
|
inside the inline type. Keep the type-literal `}` and parameter-list `)` on the
|
|
same line as the final type property.
|
|
|
|
#### Multi-line normal parameter list
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
const updateDraft = <Key extends keyof Settings,> (
|
|
key: Key,
|
|
value: Settings[Key]) => {
|
|
return null
|
|
}
|
|
```
|
|
|
|
Good:
|
|
|
|
```ts
|
|
const updateDraft = <Key extends keyof Settings,> (
|
|
key: Key,
|
|
value: Settings[Key],
|
|
) => {
|
|
return null
|
|
}
|
|
```
|
|
|
|
Rule: when the parameter list itself is split across multiple parameter lines,
|
|
the closing parameter `)` goes at the beginning of its own line before `=>` or
|
|
the return type.
|
|
|
|
#### Function and callback blocks
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
const handleSave = () => { save ()
|
|
}
|
|
```
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
const handleSave = () => {
|
|
save () }
|
|
```
|
|
|
|
Good:
|
|
|
|
```ts
|
|
const handleSave = () => {
|
|
save ()
|
|
}
|
|
```
|
|
|
|
Rule: block `}` closes executable code, not associative data. Multi-line
|
|
function, lambda, callback, `if`, `for`, `switch`, and similar block braces
|
|
belong on their own line.
|
|
|
|
#### Function and method calls
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
const value = compute (
|
|
first,
|
|
second
|
|
)
|
|
```
|
|
|
|
Good:
|
|
|
|
```ts
|
|
const value = compute (
|
|
first,
|
|
second)
|
|
```
|
|
|
|
Rule: call-expression `)` must not be at the beginning of a line. The exception
|
|
for leading `)` applies only to function declaration parameter lists, never to
|
|
calls.
|
|
|
|
#### JSX closing markers
|
|
|
|
Bad:
|
|
|
|
```tsx
|
|
<Button
|
|
type="button"
|
|
onClick={handleSave}
|
|
>
|
|
保存
|
|
</Button>
|
|
)
|
|
```
|
|
|
|
Good:
|
|
|
|
```tsx
|
|
<Button
|
|
type="button"
|
|
onClick={handleSave}>
|
|
保存
|
|
</Button>)
|
|
```
|
|
|
|
Bad:
|
|
|
|
```tsx
|
|
<Input
|
|
value={value}
|
|
onChange={handleChange}
|
|
/>
|
|
```
|
|
|
|
Good:
|
|
|
|
```tsx
|
|
<Input
|
|
value={value}
|
|
onChange={handleChange}/>
|
|
```
|
|
|
|
Rule: keep `>` or `/>` with the final prop, and keep JSX closing parentheses in
|
|
the local compact form such as `</div>)`.
|
|
|
|
#### Arrays and tuples
|
|
|
|
Bad:
|
|
|
|
```ts
|
|
const items = [
|
|
first,
|
|
second,
|
|
]
|
|
```
|
|
|
|
Good:
|
|
|
|
```ts
|
|
const items = [
|
|
first,
|
|
second]
|
|
```
|
|
|
|
Good when short:
|
|
|
|
```ts
|
|
const items = [first, second]
|
|
```
|
|
|
|
Rule: array and tuple `]` must not be at the beginning of a line. Keep it with
|
|
the final element unless that would break the hard line limit.
|
|
|
|
#### Single-line braces
|
|
|
|
Good:
|
|
|
|
```ts
|
|
const next = { ...current, enabled: true }
|
|
const tag = { id, name }
|
|
```
|
|
|
|
Bad:
|
|
|
|
```tsx
|
|
<Component prop={ value }/>
|
|
```
|
|
|
|
Good:
|
|
|
|
```tsx
|
|
<Component prop={value}/>
|
|
```
|
|
|
|
Rule: JavaScript object braces on one line get one inner space. JSX expression
|
|
braces do not get inner spaces.
|
|
|
|
#### Final TSX self-review checklist
|
|
|
|
Before reporting completion after a TypeScript or TSX edit, check the edited
|
|
hunks line by line:
|
|
|
|
1. No import/export/type/object/destructuring `}` appears alone at the beginning
|
|
of a line.
|
|
2. No call-expression `)` appears at the beginning of a line.
|
|
3. Any leading `)` is definitely closing a function declaration parameter list,
|
|
not a call.
|
|
4. Any parameter-list `)` at the end of a line is valid because the parameter
|
|
list itself was not split, only an inline type or destructuring shape was.
|
|
5. No array or tuple `]` appears at the beginning of a line.
|
|
6. Multi-line executable block `}` is on its own line.
|
|
7. JSX `>` and `/>` stay with the final prop unless nearby code proves
|
|
otherwise.
|
|
8. JSX closing parentheses keep the compact local style.
|
|
9. Leading indentation is 4-space logical indentation with tabs used only as
|
|
leading 8-space compression.
|
|
10. No line has trailing whitespace.
|
|
|
|
## Lint and build constraints
|
|
|
|
- ESLint uses `@eslint/js`, `typescript-eslint`, `eslint-plugin-react-hooks`,
|
|
and `eslint-plugin-react-refresh`.
|
|
- The hooks rules are enforced; fix hook ordering instead of disabling the rule.
|
|
- `react-refresh/only-export-components` is enabled as a warning with `allowConstantExport`.
|
|
- Build failures from unused locals or unused parameters are TypeScript
|
|
errors, not lint-only issues.
|
|
|
|
## Files to avoid in routine work
|
|
|
|
- Do not edit `dist/` output directly.
|
|
- Do not inspect or modify `node_modules/` unless explicitly needed.
|
|
- Keep generated build artifacts out of source changes unless the user asks for them.
|