Code Vocabulary and Style Authorities
Which authority governs each code decision, how paths and role words communicate responsibility, and what is enforced
Use this page when naming or placing a CodeModule, choosing a guarantee-bearing verb, reviewing comments, or deciding whether a finding is a rename, flattening, or deeper extraction. Product meanings and visible copy still come from the glossary and the domain pages it links.
Precedence and scope
The most specific authority wins. Do not copy an external authority into a second local rulebook; adopt only the proposition this repository needs.
| Subject | Strength | Authority and local position |
|---|---|---|
| Product meanings, ruled-out terms, visible copy | Local semantic authority | Glossary and linked domain docs. Product-term sweeps stay with the naming-coherence plan while it is active. |
| Frontend topology and public Interfaces | Framework requirement | Feature-Sliced Design: layers organise by influence, slices by domain, segments by technical purpose; imports use slice public Interfaces. Steiger checks the objective subset. |
| React components and Hooks | Framework requirement | Rules of React and custom Hook naming: components and Hooks follow React's purity and call rules; only Hooks use useX. |
| Route filenames and tokens | Framework requirement | TanStack Router file naming. Route tokens are not restyled locally. |
| Convex registration and paths | Framework requirement | Installed convex/_generated/ai/guidelines.md, the Convex rule, and Convex function docs. A registered export's file and name determine its generated path. |
| Formatting and mechanical ordinary quality | Local executable authority | biome.json. Biome decides formatting; prose does not duplicate it. |
| FSD dependency direction and public Interfaces | Local executable authority | steiger.config.ts, when invoked by the repository's Steiger command. |
| General TypeScript clarity | Advisory | Selected Google TypeScript guidance: descriptive names, useful comments, generated-code exemptions, and prospective adoption. Google formatting and snake_case filenames are not adopted. |
| Path-aware grammar, role words, comments | Local semantic authority | This page. |
| Styling mechanics | Local executable and semantic authority | Tailwind and the configured tokens/composition rules. |
| Deep extraction | Local workflow authority | The existing deepening-candidate workflow and locality decisions. |
Where authorities overlap, apply this order:
- The glossary governs product concepts and visible copy.
- A framework governs identifiers that it recognises.
- This page governs remaining implementation roles.
- If an implementation word would misstate a nearby product concept, use the product word or a clearer implementation word.
So a computed Field displays “computed”, not “derived”: the visible word would
collide with Names Are Projections.
Historical derive* identifiers are ordinary targeted renames, not copy defects.
Read a name as path plus basename
The complete path communicates scope + concept + role. A basename adds only
what the narrowest enclosing chapter, slice, or segment does not already say.
Do not repeat the concept merely to make a leaf name self-contained.
| Area | Grammar | Local convention |
|---|---|---|
| FSD frontend | layer / slice / segment / concern | Kebab-case owned files; colocated .stories.tsx and established test suffixes; index.ts is a public door only where an Interface is needed. |
| Domain package | concept chapter / deterministic rule or projection | Kebab-case, runtime-free. field/type-name-projection.ts does not repeat field in its basename. |
| Convex | concept / registered responsibility | Existing camelCase module convention. Renaming a module changes generated function paths and is a migration, not cosmetics. |
| Routes | framework route tree / route token | TanStack owns __root, $, _, -, index, directory, and route-token meanings. |
| Generated or upstream source | tool-owned path | Excluded from local cosmetic rules. Do not restyle generated names. |
Treat helpers, common, misc, broad utils, manager, processor,
service, and broad handler as review warnings, not bans. A precise existing
convention may justify one: src/shared/lib/utils.ts contains shadcn's cn()
utility and is the named exception.
For events, use onX for a callback supplied by a caller and handleX for a
local response to that event. Local handleX functions are not exported.
Reserve a *-handler module for a genuine external entrypoint; name domain
reasoning for the answer it produces.
Guarantee-bearing role words
Classify a name on two independent axes:
- Effect — can it persist or perform an external effect?
- Answer — what kind of result does it produce?
| Word | Effect guarantee | Kind of answer and contrast | Existing example |
|---|---|---|---|
resolve | Deterministic; does not persist | Chooses the effective answer after rules, defaults, or competing inputs. Unlike project, it settles what is true. | resolveEffectiveAccess |
parse | Deterministic; does not persist | Converts authored syntax or unknown input into a structured result and diagnostics. | parseDataFieldSource |
validate | Deterministic; does not persist | Accepts or refuses against a contract without becoming the transition itself. | validateProposedFieldSemantics |
transition | Deterministic; does not persist | Produces next domain state from current state plus an operation. Persistence is a caller's job. | transitionBlockFields |
format | Deterministic; does not persist | Produces a readable or serialised representation. Unlike resolve, it changes representation rather than deciding the underlying fact. | formatAboutFacet |
compile | Deterministic; does not persist by itself | Converts authored or declarative input into an executable or bound artifact, with failures made explicit. | compileMentionSource |
assemble | Deterministic; does not persist | Combines supplied parts into a composite domain artifact; it does not fetch the parts. | assembleAgenda |
read | Never persists; may access storage | Observes an existing value. It may be a structural read (readHeadingLevel) or a database read (readDocumentFieldCatalog); unlike resolve, it does not settle a competing rule. | readOrderPosition |
write | Promises persistence or an external write | Commits data through a storage/runtime boundary. Pure indexing or construction must not wear this name. | writeModule |
apply | Contextual: placement supplies the effect guarantee | Applies an accepted operation to a target. A domain apply* may return next state; a registered mutation or storage helper may persist. | applyOptimisticBlockContentEdit |
get, set and render are contextual, not banned: Convex registered reads
keep idiomatic get*, React keeps set* and same-closure render functions keep
render*. React also keeps useX — a plain deterministic function must not
borrow use. Elsewhere, prefer a guarantee-bearing word when it genuinely
predicts the answer.
project, build and derive are ruled out as code-role verbs — the
glossary owns that ruling, as it owns every other
forbidden word. Reaching for one of them means choosing a result-specific name
instead (format, resolve, compile, assemble), never one new catch-all.
adapt and guard are not reserved on promised future work: admit a word only
after two real uses prove a helpful seam.
Comments, reuse, and decomposition
A comment states a present-tense invariant, reason, error mode, surprising constraint, deliberate exception, or observable exit condition. History lives in Git or in a linked active plan or durable decision. Do not narrate ordinary syntax, preserve superseded rulings, or leave instructions for an agent. Keep it short: state the rule, not the conversation that produced it. At most name the decision record or date that settled it; never quote the owner's instruction.
Apply this prospectively and when a file is already being changed; it does not authorise a repository-wide comment deletion. A dated ruling ("Owner ruling 2026-08-14… StackDragHandle is superseded…") becomes the present contract plus, where the state is temporary, its exit condition:
// Presentational only: the caller owns drag state and interaction wiring.
// Keep the legacy handle export until the active drag migration replaces it.Deduplicate knowledge and behaviour that must change together, not merely similar syntax, and apply both tests below before extracting. Route findings accurately:
| Finding | Route |
|---|---|
| Misleading or ambiguous name | Small targeted rename batch |
| Harmless cosmetic inconsistency | Leave until adjacent work opens it |
| Deep nesting in one coherent closure | In-file flattening |
| Duplicated rule that must agree | One deeper CodeModule |
| Large cohesive file | Tolerated outlier or locality-preserving convenience partition |
| Mixed responsibilities with a real seam | Deepening-candidate workflow |
DraftingEditor's same-closure renderBlock and renderModule functions are
the flattening precedent: they improve navigation without manufacturing a
props-shaped Interface.
Enforcement and exceptions
Configured is not the same as enforced. A rule is a gate only when an ordinary repository command invokes it and a deliberately injected violation has been observed red.
| Level | Use |
|---|---|
| Error | Objective architecture, correctness, persistence, security, React/Hook, or controlled retired-term violation. |
| Warning | Likely drift such as a vague new filename, where a legitimate exception remains possible. |
| Guidance | Semantic verb choice, decomposition judgment, and legacy names outside current scope. |
An exception or suppression must state the current reason, why the tool's ordinary conclusion is misleading, and an observable exit condition. Generated and third-party code remain excluded. Naming checks inspect controlled code identifier scopes, not prose, CSS classes, or third-party source.
Product-term naming guard
Run bun run naming after changing a product-term identifier or the
glossary's machine-checked bans, which
are the guard's only data and state their own scopes. The session preflight
suggests this gate when those surfaces or the glossary change.
Agent checklist
Before naming or moving code, answer:
- Does the glossary, a framework, or this page own the name?
- What scope and concept does the path already supply?
- Does the basename promise the correct effect and kind of answer?
- Is a framework-controlled identifier being left to its framework?
- Is this a rename, in-file flattening, convenience partition, or deep extraction?
- Does duplicated knowledge disappear, or is only coincidental syntax shared?
- Does each comment state a current reason or invariant?
- Which single command checks the objective part, and has that check been mutation-proven?
One source of truth
A fact is stored once and every other view of it is derived. That is the
product's promise, and the code is built the same way — stable fld_/blk_
identities under renameable handles, label ?? humanize(handle) computed at
render, order.position derived from sequence, spans derived from block order.
Argued in Names Are Projections and
Stable IDs.
When a fix restores agreement between two things that must agree, remove the need for agreement instead. Two functions building one artifact, two types describing one row, a constant restated beside its own token — each is a copy-pasted party name and drifts for the same reason. Putting the missing key back is not a fix; it is the same bug, rearmed. Two copies that are currently identical are not a single source of truth, only one that has not drifted yet.
The two tests, before and after any extraction:
- Deletion. Remove the module in your head: if complexity vanishes it was a pass-through; if it reappears across callers it earned its Interface.
- Locality. Can a reader answer a question about this concern by reading one module? Two files that must stay byte-identical are one misplaced seam (Component File Partitions Must Preserve Locality).
Before applying a patch:
- Ask what made the divergence possible, not just what diverged — a missing
label:in one formatter is the symptom, two formatters is the defect. A field added to two of three payload builders once shipped a renamed field showing its bare handle and hid two further bugs, one of them silently deleting every collected answer in a block on reorder. - Prefer one function over the facts both callers hold to one function per caller shape. An adapter onto the canonical shape is cheap; a second implementation of the same rule is not.
- If you must leave the duplication, say so in the commit message and file it as a Backlog task. A "keep these in sync" comment is an invitation to the same failure, not a guard.
- The third copy is a filing trigger, exactly like a file crossing 500 lines — file the deepening candidate before writing the third copy, or in the same commit; it does not license refactoring on the spot, and the owner sequences the extraction. Mirroring the sibling is the trap: the second copy reads as precedent, and "followed the existing pattern" is duplication wearing reuse's clothes.
A ruling is a fact with one home too. A mid-session owner ruling supersedes whatever document stated the old position, and that document is amended in the same commit that implements the ruling. A ruling left in a code comment or task note is a party name filed in the attendance note instead of the contract.
This is not a licence to widen scope. Take the structural fix when it is the
same size as the patch or nearby; when it is genuinely larger, say what the
patch leaves standing, file it, and let the owner choose. Silently shipping a
patch and silently shipping a refactor are both failures — the first hides a
defect, the second spends someone else's time without asking. The vocabulary
(CodeModule, Interface, depth, seam, locality, leverage) and the survey worth
running before a refactor that touches more than one caller are in
improve-codebase-architecture.
In plain terms: if two clauses must be amended together to stay consistent, the fault is that there are two clauses.
Construction principles
The positive ladder the codebase is built by (owner-adopted). Apply it when
writing new code and when examining existing code; continue it rather than
inventing a parallel style. Worked example throughout: the folio card colours
(stack-palette.ts and its adapters, deepening candidate 20).
- Decompose along seams, not sizes. Split where a coherent concern already lives — a render branch, a decision tree, a measurement hook — never at a line count. The test is locality, above. (block-card.tsx → data body, content row, palette.)
- Then abstract: find the general rule under the copies. Where two or more places make the same class of decision in different words, state the rule once and make each place a thin adapter onto it. Extraction makes code legible; abstraction is the separate step that makes it reusable. (Three colour ladders → one tone × state table.)
- Prefer configuration over code — a
cva()variant table or lookup map rather than nested conditionals, because data can be read whole, diffed, and pinned cell-by-cell in a test. - Extend by adding a row, never by editing the ladder (open/closed). If a new case requires modifying shared logic rather than adding a variant, the abstraction is wrong — raise it, don't force it.
- Shared layers speak neutral vocabulary. Anything in
src/shared/is keyed by role words (content/data/structural,tone,state), never entity names (block,module); the entity maps onto the vocabulary in one line at its own layer. This keeps a product rename mechanical, not structural. - Pin the consolidation with a test so collapsing copies into one source
cannot silently change behaviour. (
stack-palette.node.test.tspins every cell to the legacy class strings.)
In plain terms: split the enormous precedent into a master document and schedules; move the rule three schedules restate into one defined-terms clause they all cite; write that clause as a table, not prose; let new matters add a row rather than amend it; keep the defined terms generic, so renaming a party is a find-and-replace; and keep a checklist proving the table still says what the schedules did.