This chapter outlines GhostMsg's strict code standards, linting configuration via Ultracite (Biome), TypeScript compiler settings, and architectural anti-patterns.
1. Ultracite (Biome) Tooling (ADR 0004)
GhostMsg has standardized on Ultracite (powered by Biome) to replace legacy ESLint and Prettier setups:
- Sub-second execution: Checks 100+ files in under 200ms.
- Zero configuration drift: Consistent rules across developer machines, pre-commit hooks, and CI pipelines.
- Strict linting: Enforces accessibility (
a11y), hook dependencies, imports sorting, and anti-pattern bans.
# Verify formatting and linting
pnpm check
# Automatically fix linting and formatting
pnpm fixTypeScript is configured with strict compiler flags in tsconfig.json:
strict: truenoImplicitAny: truestrictNullChecks: true- Path aliases:
@/*mapped to./src/*
pnpm exec tsc --noEmit| Banned Practice | Enforced Alternative |
|---|---|
axios imports |
api (openapi-fetch) from @/data/api-client |
Untyped raw fetch() calls |
api.GET(), api.POST(), api.DELETE() |
Manual useState for API data |
@tanstack/react-query (useQuery, useMutation) |
Untyped any casting |
Strongly typed Zod schemas (z.infer<typeof schema>) |
| Unverified string endpoints | Strongly typed OpenAPI paths |
Array index as React key (key={index}) |
Stable unique IDs (key={item.id} or deterministic item attributes) |
4. Biome Suppression Elimination & Code Quality (ADR 0009)
GhostMsg enforces a zero-inline-suppression policy and strict linter rules for core application and feature code:
- No Array Index Keys (
suspicious.noArrayIndexKey: "error"): React list rendering must always use stable, deterministic keys (e.g. database_id, semantic names, or deterministic angle/coordinate formulas) rather than array indices. Array index keys cause subtle reconciliation bugs, state leakage across reordered items, and animation glitching with Motion /AnimatePresence. - No Interactive
<div>s: Interactive elements must use semantic<button type="button">witharia-labelor<input>to satisfya11yrules. - No
anyTypes: Generic utility hooks and NextAuth callbacks must useunknown, strongly typed interfaces, or generic parameters. - No Nested Ternaries: Branching logic must use early returns, guard clauses, or isolated resolver functions.
- Scoped UI Overrides: Framework conventions in vendored primitives (e.g.
src/components/elements/effects/,*.primitive.tsx) are configured centrally viaoverridesinbiome.jsoncrather than through ad-hoc inline comments.
| Setting | Scope | Purpose |
|---|---|---|
files.includes |
src/**, .storybook/**, emails/**, scripts/**, docs/**, root configs |
Lint and format targets (public/, .next/ excluded) |
performance.noBarrelFile: off |
global | Module index.ts public APIs are allowed. Tier-level barrels are intentionally absent. |
suspicious.noArrayIndexKey: error |
global | Stable React keys |
style.noRestrictedImports |
elements/**, patterns/**, blocks/** |
Tier DAG + bans data/logic modules (@tanstack/react-query, @/data/**, next-auth/react, next/navigation) + bans @/server/** |
style.noRestrictedImports |
pages/**, src/data/**, src/hooks/**, src/providers/**, src/content/**, src/mock/**, src/types/**, .storybook/** |
Bans @/server/** and server-only (ADR 0018) |
style.noRestrictedImports |
src/lib/** (not tests) |
Bans react, next/**, next-auth/**, @tanstack/**, @/data/**, @/components/**, @/server/** (ADR 0018) |
a11y.useSemanticElements: off |
elements/carousel/**, elements/input-otp/**, elements/breadcrumb/**, elements/field/**, elements/input-group/**, elements/item/** |
Upstream shadcn/Radix ARIA roles (carousel region, OTP separator, breadcrumb link, field and input-group group, item list) |
a11y.useFocusableInteractive, useKeyWithClickEvents, noNoninteractiveElementInteractions: off |
elements/breadcrumb/**, elements/input-group/** |
Upstream patterns: the current-page crumb is a non-focusable role="link"; InputGroupAddon forwards clicks to its input (ADR 0023) |
a11y.noNoninteractiveElementInteractions, noStaticElementInteractions: off |
elements/effects/**, elements/**/*.primitive.tsx |
Vendored motion primitives |
style.noExportedImports: off |
src/schemas/zod.ts |
Re-exported extended z |
- Regexes in stories and tests are top-level constants (
performance/useTopLevelRegex), e.g.const SAVE_REGEX = /save/i;. - Hooks in story
rendermust live in a named PascalCase component (useHookAtTopLevel). UseStatefulXwrappers orXHarnesscomponents, neverrender: function Render() { useState… }. - Empty callbacks use
noopfrom@/mock/noop, never() => {}(noEmptyBlockStatements). - No non-null assertions in stories (
noNonNullAssertion). Build the value from a fixture instead. - No inline suppressions, consistent with ADR 0009. Fix the code or add a scoped override with a reason.
noRestrictedImports overrides enforce the Elements ➔ Patterns ➔ Blocks ➔ Pages DAG and keep data/logic modules inside src/components/pages/ (ADR 0015). Each diagnostic message names the violated rule.
Proceed to Chapter 9: Deployment & Operations.