Goldie Blocks
Coding Rules

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.

SubjectStrengthAuthority and local position
Product meanings, ruled-out terms, visible copyLocal semantic authorityGlossary and linked domain docs. Product-term sweeps stay with the naming-coherence plan while it is active.
Frontend topology and public InterfacesFramework requirementFeature-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 HooksFramework requirementRules of React and custom Hook naming: components and Hooks follow React's purity and call rules; only Hooks use useX.
Route filenames and tokensFramework requirementTanStack Router file naming. Route tokens are not restyled locally.
Convex registration and pathsFramework requirementInstalled 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 qualityLocal executable authoritybiome.json. Biome decides formatting; prose does not duplicate it.
FSD dependency direction and public InterfacesLocal executable authoritysteiger.config.ts, when invoked by the repository's Steiger command.
General TypeScript clarityAdvisorySelected 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, commentsLocal semantic authorityThis page.
Styling mechanicsLocal executable and semantic authorityTailwind and the configured tokens/composition rules.
Deep extractionLocal workflow authorityThe existing deepening-candidate workflow and locality decisions.

Where authorities overlap, apply this order:

  1. The glossary governs product concepts and visible copy.
  2. A framework governs identifiers that it recognises.
  3. This page governs remaining implementation roles.
  4. 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.

AreaGrammarLocal convention
FSD frontendlayer / slice / segment / concernKebab-case owned files; colocated .stories.tsx and established test suffixes; index.ts is a public door only where an Interface is needed.
Domain packageconcept chapter / deterministic rule or projectionKebab-case, runtime-free. field/type-name-projection.ts does not repeat field in its basename.
Convexconcept / registered responsibilityExisting camelCase module convention. Renaming a module changes generated function paths and is a migration, not cosmetics.
Routesframework route tree / route tokenTanStack owns __root, $, _, -, index, directory, and route-token meanings.
Generated or upstream sourcetool-owned pathExcluded 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?
WordEffect guaranteeKind of answer and contrastExisting example
resolveDeterministic; does not persistChooses the effective answer after rules, defaults, or competing inputs. Unlike project, it settles what is true.resolveEffectiveAccess
parseDeterministic; does not persistConverts authored syntax or unknown input into a structured result and diagnostics.parseDataFieldSource
validateDeterministic; does not persistAccepts or refuses against a contract without becoming the transition itself.validateProposedFieldSemantics
transitionDeterministic; does not persistProduces next domain state from current state plus an operation. Persistence is a caller's job.transitionBlockFields
formatDeterministic; does not persistProduces a readable or serialised representation. Unlike resolve, it changes representation rather than deciding the underlying fact.formatAboutFacet
compileDeterministic; does not persist by itselfConverts authored or declarative input into an executable or bound artifact, with failures made explicit.compileMentionSource
assembleDeterministic; does not persistCombines supplied parts into a composite domain artifact; it does not fetch the parts.assembleAgenda
readNever persists; may access storageObserves 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
writePromises persistence or an external writeCommits data through a storage/runtime boundary. Pure indexing or construction must not wear this name.writeModule
applyContextual: placement supplies the effect guaranteeApplies 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:

FindingRoute
Misleading or ambiguous nameSmall targeted rename batch
Harmless cosmetic inconsistencyLeave until adjacent work opens it
Deep nesting in one coherent closureIn-file flattening
Duplicated rule that must agreeOne deeper CodeModule
Large cohesive fileTolerated outlier or locality-preserving convenience partition
Mixed responsibilities with a real seamDeepening-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.

LevelUse
ErrorObjective architecture, correctness, persistence, security, React/Hook, or controlled retired-term violation.
WarningLikely drift such as a vague new filename, where a legitimate exception remains possible.
GuidanceSemantic 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:

  1. Does the glossary, a framework, or this page own the name?
  2. What scope and concept does the path already supply?
  3. Does the basename promise the correct effect and kind of answer?
  4. Is a framework-controlled identifier being left to its framework?
  5. Is this a rename, in-file flattening, convenience partition, or deep extraction?
  6. Does duplicated knowledge disappear, or is only coincidental syntax shared?
  7. Does each comment state a current reason or invariant?
  8. 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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).

  1. 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.)
  2. 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.)
  3. 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.
  4. 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.
  5. 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.
  6. Pin the consolidation with a test so collapsing copies into one source cannot silently change behaviour. (stack-palette.node.test.ts pins 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.

On this page