924 行
29 KiB
Markdown
924 行
29 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 2-space block indentation in TypeScript and TSX.
|
|
- Use 4-space continuation indentation for wrapped expressions, arguments,
|
|
ternary branches, method chains, object pairs, arrays, and JSX attributes.
|
|
- Treat the user's `PostImportSourcePage.tsx` and
|
|
`PostImportReviewPage.tsx` formatting as the local reference shape.
|
|
- 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, use tabs for leading 8-column compression only.
|
|
- A tab does not represent one indentation level.
|
|
- Determine visible indentation with 2-space block indentation and 4-space
|
|
continuation indentation first, then compress only complete leading runs of
|
|
8 spaces into tabs.
|
|
- 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.
|
|
- Keep residual leading 2, 4, or 6 spaces after any tab compression.
|
|
- Examples: 2 columns = 2 spaces, 4 columns = 4 spaces, 6 columns = 6
|
|
spaces, 8 columns = 1 tab, 10 columns = 1 tab + 2 spaces, 12 columns = 1
|
|
tab + 4 spaces.
|
|
|
|
## 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.
|
|
|
|
## Dialogues
|
|
|
|
- Dialogue work follows the shared-frontend reuse rules below.
|
|
|
|
## 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`.
|
|
- Do not mix runtime values and `type` specifiers in one named import
|
|
declaration.
|
|
- Do not write `import { value, type TypeName } from ...`.
|
|
- Keep short value imports from one module on one line when they fit within
|
|
99 characters.
|
|
- Order imports as four groups with a blank line between groups: external
|
|
value imports, `@/...` value imports, external type imports, `@/...`
|
|
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 colour classes, update `tailwind.config.js` safelist
|
|
if the class cannot be statically detected.
|
|
- Do not introduce new UI libraries or production dependencies without approval.
|
|
|
|
## TypeScript and TSX formatting
|
|
|
|
- The delimiter-placement and line-breaking rules in this section apply to
|
|
both plain TypeScript `.ts` and TSX `.tsx`, unless a bullet explicitly says
|
|
it is JSX- or React-specific.
|
|
- Preserve compact TSX expression shapes such as inline ternary branches and
|
|
closing `</div>)` forms when nearby code uses them.
|
|
- Block bodies for components, functions, callbacks, `if`, `try`, `catch`,
|
|
`finally`, loops, and JSX nesting use 2 spaces per level.
|
|
- Wrapped expressions, arguments, ternary branches, method chains, and object
|
|
pairs use 4-space continuation indentation relative to the owning
|
|
expression. Do not confuse this with 2-space block indentation.
|
|
- Tabs are leading 8-column compression only. They do not represent one
|
|
nesting level. Decide visible indentation first, then compress only
|
|
complete leading runs of 8 spaces into tabs.
|
|
- Do not add braces around a single-line `if` body merely for formatting.
|
|
- Use braces for multi-line `if`, `else`, and loop bodies.
|
|
- Multi-stage ternary expressions must use explicit parentheses for each
|
|
condition group and nested branch. Do not rely on indentation alone to show
|
|
`?` / `:` pairing.
|
|
- Keep short inline props types local when they remain readable and within the
|
|
line limit; do not mechanically extract a named type with no reuse benefit.
|
|
- In multi-line object literals, keep the opening `{` with the first pair when
|
|
the line length allows it; do not mechanically explode short objects into
|
|
Prettier-style vertical blocks.
|
|
- Method chains should align as a continuation under the receiver expression;
|
|
do not indent chains more deeply than the normal continuation depth.
|
|
- `PostImportSourcePage.tsx` and `PostImportReviewPage.tsx` are the current
|
|
canonical examples for block indentation, continuation indentation, import
|
|
grouping, ternary grouping, method-chain placement, and local inline props
|
|
types.
|
|
- Treat TypeScript and TSX formatting rules as hard constraints, not
|
|
preferences. Before finishing a TypeScript or TSX edit, inspect the edited
|
|
hunks for closing `)`, `]`, and `}` placement and fix violations instead of
|
|
relying on formatter defaults.
|
|
- After every TypeScript or 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 TypeScript or TSX hunk violates these local
|
|
formatting rules.
|
|
- The TypeScript/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-specific self-review must confirm there are no common Prettier-style
|
|
React component declarations with a multi-line destructured parameter.
|
|
- The TypeScript/TSX self-review must confirm multi-line function declaration
|
|
parameter `)` placement follows the detailed parameter-list rules below.
|
|
- The TypeScript/TSX self-review must confirm call-expression `)` is never at
|
|
the beginning of a line.
|
|
- The TypeScript/TSX self-review must confirm
|
|
object/type/import/destructuring `}` is not at the beginning of a line.
|
|
- The TypeScript/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 TypeScript/TSX self-review must confirm array `]` is not at the
|
|
beginning of a line.
|
|
- The TSX-specific self-review must confirm JSX closing markers and closing
|
|
parentheses keep the surrounding compact style.
|
|
- The TypeScript/TSX self-review must confirm leading block indentation uses
|
|
2 spaces per level, wrapped continuations use the repository's 4-space
|
|
continuation alignment, and complete leading runs of 8 spaces may be
|
|
compressed to tabs.
|
|
- For long Tailwind `className` strings, wrap across lines only when needed.
|
|
- Keep continuation indentation aligned with the repository's 4-space
|
|
continuation rule while keeping block indentation at 2 spaces.
|
|
- Keep short value imports from one module on one line when they fit within
|
|
99 characters.
|
|
- In TypeScript and TSX function declarations, including `const` arrow
|
|
function declarations, classify the parameter list before placing the closing
|
|
`)`.
|
|
- Block indentation example:
|
|
|
|
```ts
|
|
const Component = () => {
|
|
const value = loadValue ()
|
|
|
|
useEffect (() => {
|
|
if (value != null)
|
|
useValue (value)
|
|
}, [value])
|
|
}
|
|
```
|
|
|
|
- Continuation indentation example:
|
|
|
|
```ts
|
|
const editingRow =
|
|
Number.isFinite (editingSourceRow)
|
|
? rows.find (_1 => _1.sourceRow === editingSourceRow) ?? null
|
|
: null
|
|
```
|
|
|
|
- Import grouping example:
|
|
|
|
```ts
|
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
|
|
|
import { loadPostImportSession } from '@/lib/postImportSession'
|
|
|
|
import type { FC } from 'react'
|
|
|
|
import type { PostImportRow } from '@/lib/postImportSession'
|
|
```
|
|
|
|
- Inline props type example:
|
|
|
|
```ts
|
|
const Footer = (
|
|
{ loading,
|
|
onSubmit }: { loading: boolean
|
|
onSubmit: () => void },
|
|
) => null
|
|
```
|
|
|
|
- Ternary grouping example:
|
|
|
|
```ts
|
|
const rows =
|
|
repairMode === 'failed'
|
|
? (
|
|
[...source].sort ((a, b) => {
|
|
const aFailed = a.failed ? 0 : 1
|
|
const bFailed = b.failed ? 0 : 1
|
|
return aFailed - bFailed
|
|
}))
|
|
: source
|
|
```
|
|
|
|
- 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.
|
|
- Use correct British English spelling for new identifiers, filenames,
|
|
component names, helper names, comments, and developer-facing prose unless
|
|
editing an already established American-English API that must keep its
|
|
existing spelling for compatibility.
|
|
- Prefer British English spellings such as `behaviour`, `colour`, `realise`,
|
|
`theatre`, `centre`, `favourite`, `optimise`, and `catalogue`.
|
|
- Avoid American or Canadian spellings such as `behavior`, `color`, `realize`,
|
|
`theater`, `center`, `favorite`, `optimize`, and `catalog`.
|
|
- Even when an external library or API uses the wrong spelling, prefer to
|
|
correct it at the local boundary and use proper British English names in this
|
|
frontend codebase. For example, prefer
|
|
`import { color: colour } from '@external-lib'` over spreading `color`
|
|
through local code.
|
|
- Apply the same boundary correction to object destructuring, wrapper helpers,
|
|
adapter layers, and local variable names when doing so does not break the
|
|
external contract.
|
|
- In this frontend, prefer names such as `BehaviourSettingsSection.tsx`, not
|
|
`BehaviorSettingsSection.tsx`.
|
|
- Avoid reformatting unrelated JSX.
|
|
|
|
## Shared frontend systems
|
|
|
|
Before creating a new component, hook, helper, store, context, or other
|
|
frontend abstraction, search at least:
|
|
|
|
- `src/components/common`
|
|
- `src/components/layout`
|
|
- `src/components/ui`
|
|
- `src/components/dialogues`
|
|
- `src/lib`
|
|
- `src/lib/dialogues`
|
|
- `src/stores`
|
|
- `src/types.ts`
|
|
|
|
Also inspect the existing pages and components in the same feature.
|
|
|
|
Search by responsibility, not by filename alone. Check display, interaction,
|
|
state, communication, validation, and permission behaviour before deciding that
|
|
an existing implementation is unsuitable.
|
|
|
|
### Component placement and reuse order
|
|
|
|
When adding UI, use this order:
|
|
|
|
1. reuse an existing feature component
|
|
2. reuse an existing component from `components/common`
|
|
3. reuse an existing layout component from `components/layout`
|
|
4. use an existing primitive from `components/ui` through the established
|
|
common API
|
|
5. extend an existing component minimally
|
|
6. add a feature-local component in the feature area
|
|
7. add a new common component only when multiple features clearly share a
|
|
stable visual contract
|
|
|
|
Do not place a one-screen component in a common directory merely because its
|
|
name starts with `Common`.
|
|
|
|
### Low-level primitives
|
|
|
|
Treat `components/ui` as low-level primitives. If a higher-level common API
|
|
already exists for dialogues, toast, form validation, navigation, or similar
|
|
behaviour, feature code must use that API instead of assembling primitives
|
|
directly.
|
|
|
|
Examples of existing preferred entrypoints include:
|
|
|
|
- dialogue: `@/lib/dialogues/useDialogue`
|
|
- toast: the existing toast API
|
|
- internal navigation: `PrefetchLink`
|
|
- form errors: `FieldError`, `FieldWarning`, `FormField`
|
|
- buttons: `Button`
|
|
- conditional class merge: `cn`
|
|
|
|
Do not evade the rule with aliases or thin wrappers around the low-level
|
|
primitive.
|
|
|
|
### Dialogues
|
|
|
|
Feature-facing dialogue work must use `@/lib/dialogues/useDialogue`.
|
|
|
|
Reuse the existing common dialogue API and common dialogue component. Do not
|
|
import `@/components/ui/dialog` directly in feature code to assemble bespoke
|
|
dialogue shells, and do not evade this rule with aliases such as
|
|
`Dialog as Dialogue`.
|
|
|
|
Do not reimplement overlay, portal, close button, header, footer, focus
|
|
handling, Escape handling, outside-click handling, or confirmation flow in
|
|
feature code.
|
|
|
|
Keep business-specific form content in feature code, and keep the visual and
|
|
behavioural dialogue shell in common code.
|
|
|
|
Use British spelling `Dialogue` for project-defined dialogue identifiers. Keep
|
|
exact third-party spellings only at the external boundary where compatibility
|
|
requires them.
|
|
|
|
### API calls
|
|
|
|
Rails API calls must use `src/lib/api.ts`.
|
|
|
|
Do not create feature-local Axios instances, fetch wrappers, header injectors,
|
|
camelCase converters, or generic error converters. If blob or other special
|
|
transport behaviour is already supported by the common API, use the existing
|
|
options instead of bypassing the wrapper.
|
|
|
|
### Query keys, server state, and prefetch
|
|
|
|
Before adding query state, inspect:
|
|
|
|
- `src/lib/queryKeys.ts`
|
|
- existing domain helpers
|
|
- existing prefetchers
|
|
- the root query-key hierarchy
|
|
- current mutation invalidation patterns
|
|
- the app-wide `QueryClient`
|
|
|
|
Do not write ad hoc query-key arrays in feature code. Do not duplicate fetcher,
|
|
prefetcher, or invalidation helpers for the same resource.
|
|
|
|
### Domain helpers
|
|
|
|
For posts, tags, wiki, materials, and other domain work, inspect the existing
|
|
helpers in `src/lib/*.ts` before adding logic to a page component.
|
|
|
|
Do not accumulate these in page components when an existing helper layer should
|
|
own them:
|
|
|
|
- API request construction
|
|
- response-shape conversion
|
|
- query-key construction
|
|
- canonical URL generation
|
|
- permission calculation
|
|
- storage serialisation
|
|
- domain-specific parsing
|
|
|
|
Keep purely local one-screen display shaping local when that is the clearest
|
|
place for it.
|
|
|
|
### Permission helpers
|
|
|
|
Use the existing permission helpers such as `src/lib/users.ts` when deciding
|
|
editability, role checks, admin/member visibility, and similar UI behaviour.
|
|
|
|
Do not scatter `user?.role`, numeric role comparisons, or string comparisons
|
|
through components. Frontend visibility control should be consistent even though
|
|
backend authorization remains the final gate.
|
|
|
|
### Validation errors
|
|
|
|
Before adding feature-local validation-error handling, inspect:
|
|
|
|
- `useValidationErrors`
|
|
- `apiErrors`
|
|
- `FieldError`
|
|
- `FieldWarning`
|
|
- `FormField`
|
|
- `inputClass`
|
|
|
|
Do not create a new generic hook, field-error state shape, or error-rendering
|
|
component for a pattern the shared error stack already covers. Keep only
|
|
genuinely feature-specific business errors local.
|
|
|
|
### Forms and fields
|
|
|
|
Before creating a new input, textarea, date/time field, tag input, label, or
|
|
error layout, inspect at least:
|
|
|
|
- `Form`
|
|
- `FormField`
|
|
- `FieldError`
|
|
- `FieldWarning`
|
|
- `DateTimeField`
|
|
- `TagInput`
|
|
- `TextArea`
|
|
- `Label`
|
|
- `Button`
|
|
|
|
Do not create a same-function field component merely because the spacing or
|
|
surface styling is slightly different. Prefer feature-level composition over
|
|
bloated common-field option lists.
|
|
|
|
### Navigation and prefetch
|
|
|
|
Use `PrefetchLink` and existing router helpers for internal navigation. Do not
|
|
introduce feature-local `<a>`, `window.location`, or custom prefetch logic for
|
|
internal routes. Keep path-segment encoding aligned with the existing rules.
|
|
|
|
### State management
|
|
|
|
Before adding state, decide whether the source of truth should be:
|
|
|
|
- component-local state
|
|
- URL search params
|
|
- TanStack Query server state
|
|
- an existing Zustand store
|
|
- an existing event bus
|
|
- an existing storage helper
|
|
|
|
Do not create a new global store, context, or event bus for one screen when
|
|
local state or an existing mechanism is enough. Do not create a second store
|
|
for the same responsibility.
|
|
|
|
### Storage and settings
|
|
|
|
When touching localStorage, sessionStorage, or user settings, inspect existing
|
|
settings helpers, storage helpers, expiry handling, versioning, and sanitisers.
|
|
|
|
Do not reimplement per-component key naming, JSON parsing and serialisation,
|
|
expiry, or schema checks when a shared helper already owns the pattern.
|
|
|
|
### Hooks
|
|
|
|
Before creating a custom hook, search existing `src/lib/use*.ts` and
|
|
`src/lib/use*.tsx`.
|
|
|
|
Hooks are for shared stateful behaviour or React lifecycle integration. Do not
|
|
turn a pure function, one-off helper, or mere re-export shim into `useFoo`.
|
|
|
|
### Stores, contexts, and event buses
|
|
|
|
Add a new store, context, or event bus only when the current mechanisms cannot
|
|
express the requirement and there are multiple genuinely separate consumers.
|
|
|
|
Do not hold the same information redundantly across URL state, query cache,
|
|
local component state, Zustand, and an event bus. Keep one source of truth.
|
|
|
|
### Types
|
|
|
|
If a domain type already exists in `src/types.ts` or a domain helper, reuse it
|
|
instead of redefining the same shape in a feature file.
|
|
|
|
Small local props and draft types may stay local. Do not create giant
|
|
catch-all type files such as `CommonTypes.ts`.
|
|
|
|
### Styling utilities
|
|
|
|
Use existing styling utilities such as `cn` and `inputClass`.
|
|
|
|
Do not add feature-local class-merge helpers, generic status-colour mappers, or
|
|
responsive wrapper helpers when a shared utility already exists. Keep common
|
|
tone names visual only; feature-specific state names stay in feature code.
|
|
|
|
### Layout
|
|
|
|
Before adding page shells, padding rules, viewport-height handling, sidebar
|
|
offsets, or footer offsets, inspect existing layout components such as
|
|
`MainArea`, top navigation, sidebar, page title, and section-title patterns.
|
|
|
|
Do not create a second layout shell before checking whether the current layout
|
|
can be reused or minimally extended.
|
|
|
|
### Delimiter decision table
|
|
|
|
Use this table before accepting any edited TypeScript or TSX hunk. The table is
|
|
more authoritative than formatter habit. Unless a subsection explicitly
|
|
mentions JSX, it applies equally to `.ts` and `.tsx`.
|
|
|
|
#### 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 block indentation uses 2 spaces per level, wrapped continuations
|
|
use the repository's 4-space continuation alignment, and complete leading
|
|
runs of 8 spaces may be compressed to tabs.
|
|
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.
|