πŸ“œ/skills/fuz-stack
  • docs
  • skills
  • fuz-stack
    • Async Patterns
    • Code Generation
    • Common Utilities
    • CSS Patterns
    • Dependency Injection
    • Documentation System
    • Rust Patterns for the Fuz Ecosystem
    • Rust Performance Patterns
    • Svelte 5 Patterns
    • Task Patterns
    • Testing Patterns
    • TSDoc Comment Style Guide
    • Type Utilities
    • WASM Patterns for the Fuz Ecosystem
    • Zod Schemas
  • grimoire
  • tools
  • hash

Fuz stack conventions

> Pre-alpha: Conventions are actively evolving. When code or a project's > CLAUDE.md conflicts with this skill, the code is ground truth. > > Γ€ la carte: Each project adopts only what serves it. Deep imports and > the flat namespace make this natural at the package level too.

> Skip for: Grimoire-only edits, third-party code review, simple > git/shell operations. Repo CLAUDE.md is authoritative for > project-specific patterns β€” this skill covers shared conventions across > TypeScript, Svelte, and Rust crates.

Why These Conventions

The Fuz stack is designed so the full software lifecycle β€” produce, deploy, operate β€” is accessible to anyone with intent and an AI partner. These conventions serve that goal: consistent, self-describing patterns that AI agents can learn once and apply everywhere. snake_case aligns TS, Rust, and SQL with zero renaming. Zod schemas are the single source of truth for shape, types, defaults, and validation. The Cell pattern gives every piece of state the same structure. When conventions are this consistent, AI can reliably bridge the gap between a person's intent and the stack's implementation.

The stack composes: fuz_util β†’ fuz_css β†’ fuz_ui β†’ apps, with fuz_app as the shared backend spine (auth, sessions, DB, SSE). zzz (the garage) and zap (machine-state convergence) build on the same primitives. Understanding one part transfers to understanding the others.

Package Ecosystem

@fuzdev/* packages draw from these conventions. Each package's CLAUDE.md is authoritative for what it actually uses.

| Package | Description | | -------------- | ------------------------------------------------------------------------ | | fuz_util | foundation utilities (zero deps) β€” hashing, async, schemas, types | | gro | task runner and toolkit extending SvelteKit (temporary, until fuz) | | fuz_css | CSS framework and design system β€” apps look good by default | | fuz_ui | Svelte 5 components β€” themes, layouts, overlays, auto-docs | | fuz_app | stack spine β€” auth, sessions, DB, SSE, route specs, CLI/daemon | | fuz_docs | experimental AI-generated docs and skills for Fuz | | fuz_template | a static web app and Node library template | | fuz_code | syntax styling utilities and components for TypeScript, Svelte, Markdown, and more | | fuz_blog | blog software from scratch with SvelteKit | | fuz_mastodon | Mastodon components and helpers for Svelte, SvelteKit, and Fuz | | fuz_gitops | a tool for managing many repos | | blake3 | BLAKE3 hashing compiled to WASM (@fuzdev/blake3_wasm + blake3_wasm_small) | | zzz | software garage β€” produce software with AI assistance | | zap | convergence β€” deploy and operate infrastructure |

gro is a temporary build tool, will be replaced by fuz.

Dependency flow: fuz_util -> gro + fuz_css -> fuz_ui -> fuz_app -> zzz, zap, apps

Coding Conventions

Naming - snake_case + PascalCase

// Functions and variables - snake_case // applies equally to function declarations and arrow function exports const format_bytes = (n: number): string => { ... }; export const git_current_branch_name = async (): Promise<GitBranch> => { ... }; export function create_context<T>(fallback?: () => T) { ... } const user_data: Record<string, unknown> = {}; // Types, classes, components - PascalCase type PackageJson = {}; class DocsLinks {} // file: src/lib/DocsLink.svelte

NOT camelCase for functions/variables. Intentional divergence:

- Cross-language alignment β€” same identifiers in TS, Rust, and SQL with zero renaming cost (keyed_hash, get_user_sessions, git_push). - Legibility β€” underscores as explicit word boundaries: package_json_load vs packageJsonLoad.

External APIs keep their native casing. .map(), addEventListener(), initSync β€” only identifiers you define follow snake_case.

// Constants - SCREAMING_SNAKE_CASE const DEFAULT_TIMEOUT = 5000; const GITOPS_CONFIG_PATH_DEFAULT = 'gitops.config.ts';

Naming Patterns

Two forms, chosen by disambiguation in the flat namespace:

Domain-prefix (domain_action) β€” when the bare action name would be ambiguous:

git_push(); // git_* cluster (fuz_util/git.ts) git_fetch(); // "push"/"fetch" alone are ambiguous time_format(); // time_* cluster (fuz_util/time.ts) contextmenu_open(); // contextmenu_* cluster (fuz_ui) package_json_load(); // package_json_* cluster (gro)

Action-first (action_domain) β€” when already self-descriptive:

truncate(); // standalone (fuz_util/string.ts) strip_start(); // action is the concept (fuz_util/string.ts) escape_js_string(); // action with domain qualifier (fuz_util/string.ts) should_exclude_path(); // predicate form (fuz_util/path.ts) to_file_path(); // conversion (fuz_util/path.ts)

| Pattern | Example | Use Case | | --------------------- | ---------------------- | ------------------------------- | | domain_action | git_push | Disambiguates in flat namespace | | domain_is_adjective | module_is_typescript | Boolean in a domain cluster | | to_target | to_file_path | Conversions | | format_target | format_number | Formatting | | action_domain | escape_js_string | Self-descriptive utilities | | create_domain | create_context | Factory functions |

Rule of thumb: domain-prefix when the bare name is ambiguous (git_push not push); action-first when self-descriptive (truncate, strip_start). File names often signal which: git.ts β†’ git_*, string.ts β†’ action-first.

Action verbs: parse, create, get, to, is, has, format, render, analyze, extract, load, save, escape, strip, ensure, validate, should

Flat Namespace - Fail Fast

All exported identifiers must have unique names across all modules:

- library.gen.ts uses library_throw_on_duplicates (from fuz_ui) to detect conflicts during gro gen β€” every project opts in via library_gen({on_duplicates: library_throw_on_duplicates}) - Error shows all conflicts with module paths and kinds - Resolution: rename one following the domain_action pattern, or add /** @nodocs */ to exclude from validation - Which side to rename β€” rename the side that is *not* the primary public API. @nodocs is the wrong tool when external consumers depend on the hidden symbol (it vanishes from docs and tomes). - Component is primary (class is a state/helper): suffix the class with State / Info. Example: DocsLink interface β†’ DocsLinkInfo when it conflicts with DocsLink.svelte. Precedent: ThemeState, AuthState, SidebarState. - Class is primary (stateful with methods/lifecycle, consumers instantiate it): suffix the component with View / Pane. Example: MusicPlayer class kept, component renamed to MusicPlayerView.svelte.

File Organization

src/ β”œβ”€β”€ lib/ # exportable library code β”‚ β”œβ”€β”€ *.svelte # UI components (PascalCase.svelte) β”‚ β”œβ”€β”€ *.ts # TypeScript utilities β”‚ β”œβ”€β”€ *.svelte.ts # Svelte 5 runes and reactive code β”‚ β”œβ”€β”€ *.gen.ts # generated files (by Gro gen tasks) β”‚ └── domain/ # domain subdirectories (see below) β”‚ └── *.ts β”œβ”€β”€ test/ # tests (NOT co-located with source) β”‚ └── *.test.ts # mirrors lib/ structure └── routes/ # SvelteKit routes (if applicable)

Domain subdirectories

When a domain grows beyond a single file, group related modules in a subdirectory under lib/. Each file is a distinct concern β€” no barrel/index files.

src/lib/ β”œβ”€β”€ env/ # environment variable handling β”‚ β”œβ”€β”€ load.ts # schema-based env loading + validation β”‚ β”œβ”€β”€ resolve.ts # $$VAR$$ reference resolution β”‚ β”œβ”€β”€ dotenv.ts # .env file parsing β”‚ └── mask.ts # secret value display masking β”œβ”€β”€ auth/ # authentication domain (~34 files) β”‚ β”œβ”€β”€ keyring.ts # crypto: HMAC-SHA256 cookie signing β”‚ β”œβ”€β”€ password.ts # crypto: password hashing interface β”‚ β”œβ”€β”€ account_schema.ts # types + Zod schemas β”‚ β”œβ”€β”€ account_queries.ts # database queries β”‚ β”œβ”€β”€ session_middleware.ts # Hono middleware β”‚ └── account_routes.ts # route spec factories β”œβ”€β”€ http/ # generic HTTP framework β”œβ”€β”€ db/ # database infrastructure β”œβ”€β”€ server/ # backend lifecycle + assembly β”œβ”€β”€ runtime/ # composable runtime deps + implementations β”œβ”€β”€ cli/ # CLI infrastructure β”œβ”€β”€ actions/ # action spec system β”œβ”€β”€ realtime/ # SSE and pub/sub β”œβ”€β”€ testing/ # test utilities (shared across consumers) β”œβ”€β”€ ui/ # frontend components and state └── dev/ # dev workflow helpers

When to create a subdirectory: 3+ closely related files sharing a domain concept. A single file stays at lib/ root. Don't create subdirectories preemptively.

Consumers import individual modules by full path β€” the subdirectory is part of the import path, not hidden behind re-exports:

import {load_env} from '@fuzdev/fuz_app/env/load.js'; import {resolve_env_vars} from '@fuzdev/fuz_app/env/resolve.js'; import {create_app_backend} from '@fuzdev/fuz_app/server/app_backend.js';

Tests mirror the subdirectory structure in src/test/:

src/test/ β”œβ”€β”€ env/ β”‚ β”œβ”€β”€ load.test.ts β”‚ β”œβ”€β”€ resolve.test.ts β”‚ β”œβ”€β”€ dotenv.test.ts β”‚ └── mask.test.ts β”œβ”€β”€ auth/ β”‚ β”œβ”€β”€ keyring.test.ts β”‚ └── account_queries.db.test.ts # .db.test.ts suffix for PGlite tests └── server/ └── env.test.ts # server-specific env (BaseServerEnv, validate_server_env)

Code Style

- TypeScript: Strict mode, explicit types - Svelte: Svelte 5 with runes API ($state, $derived, $effect) - Formatting: Prettier with tabs, 100 char width - Extensions: Always include .js in imports (even for .ts files): import {foo} from './bar.js' (for a bar.ts file) - Comments: - JSDoc (/** ... */) = proper sentences with periods - Inline (//) = fragments, no capital or period - No barrel exports: Import by exact file path, no index.ts. Package exports use wildcard patterns ("./*.js") so every module is importable. - No backwards compatibility: Delete unused code, rename directly, no deprecated stubs or shims. Document breaking changes in changesets.

Gro Commands (Temporary Build Tool)

IMPORTANT: Gro is installed globally β€” always run gro directly, never npx gro.

Development:

gro test # run vitest tests gro gen # run code generators (*.gen.ts files) gro format # format with Prettier gro lint # run ESLint gro typecheck # run TypeScript type checking

Production:

gro build # production build (runs plugin lifecycle) gro check # ALL checks: test + gen --check + format --check + lint + typecheck gro publish # version with Changesets, publish to npm, push to git gro deploy # build and force push to deploy branch gro release # combined publish + deploy workflow

Utilities: gro sync (gen + update exports), gro run file.ts (execute TS), gro changeset (create changeset). SKIP_EXAMPLE_TESTS=1 gro test to skip slow tests.

Key behaviors: gro check is the CI command. gro gen --check verifies no drift. Tasks are overridable: local src/lib/foo.task.ts overrides gro/dist/foo.task.js; call builtin with gro gro/foo.

Never run gro dev or npm run dev β€” user manages the dev server.

Code Generation

Gen files (*.gen.ts) export a gen function, discovered by the .gen. pattern in filenames. Naming: foo.gen.ts β†’ foo.ts, foo.gen.css.ts β†’ foo.css. Return string, {content, filename?, format?}, Array, or null.

Common gen patterns: library.gen.ts (library metadata for docs), fuz.gen.css.ts (bundled fuz_css for a project), theme.gen.css.ts (theme CSS from style variables).

See ./references/code-generation for the full API, dependencies, and examples.

TSDoc/JSDoc Conventions

See ./references/tsdoc-comments for the full tag guide, documentation patterns, and drift-detection guidance.

Key rules:

- Main description: complete sentences ending in a period - @param name - description: hyphen separator; single-sentence: lowercase, no period; multi-sentence: capitalize, end with period - @returns (not @return): same single/multi-sentence rule as @param - @module: complex modules get a module-level doc comment with @module at end - @mutates target - description: document parameter/state mutations (also `` @mutates target `` for self-evident mutations) - @nodocs: exclude from docs and flat namespace validation - Wrap identifier references in backticks for auto-linking via mdz

Tag order: description β†’ @param β†’ @returns β†’ @mutates β†’ @throws β†’ @example β†’ @deprecated β†’ @see β†’ @since β†’ @default β†’ @nodocs

Svelte 5 Patterns

See ./references/svelte-patterns for $state.raw(), $derived.by(), reactive collections (SvelteMap/SvelteSet), schema-driven reactive classes, snippets, effects, attachments, props, event handling, component composition, and legacy features to avoid.

Runes API

$state.raw() by default for all reactive state. $state() only for arrays/objects mutated in place (push, splice, index assignment). $derived for computed values, $effect for side effects.

Context Pattern

Standardized via create_context<T>() from @fuzdev/fuz_ui/context_helpers.js. Common contexts: theme_state_context (theme), library_context (package API metadata), tome_context (current doc page).

Documentation System

Projects use tomes (not "stories") with auto-generated API docs.

Pipeline: source files β†’ library_generate() β†’ library.json + library.ts β†’ Library class β†’ Tome pages + API routes.

See ./references/documentation-system for setup, the full pipeline, Tome system, layout architecture, and component reference. TSDoc authoring conventions: ./references/tsdoc-comments.

mdz - Minimal Markdown Dialect

mdz is fuz_ui's markdown dialect for documentation (@fuzdev/fuz_ui/mdz.ts).

| Feature | Syntax | | ---------------------- | ----------------------------------------------------------------------------------- | | Code | "code" | | Bold / italic / strike | **bold**, _italic_, ~strike~ | | Links | auto-detected URLs, /internal/path, [text](url) | | Headings | # Heading (column 0 required, gets lowercase slugified id for fragment links) | | Code blocks | fenced with language hints | | Components | <Alert status="warning">content</Alert> (registered via mdz_components_context) |

<Mdz content="Some **bold** and `code` text." />

Backticked identifiers auto-link to API docs in TSDoc rendering.

Path references in documentation

Three forms, each with its own typography. The distinction is whether the target is a navigable file (bare path) or a code-tree identifier (backticked, no leading ./).

1. Navigational paths (bare, no backticks). Use for docs, READMEs, external repos, and any reference that points to a file by location rather than by code identity:

- ./foo and ../foo β€” relative to the file's directory; mdz auto-linkifies these when preceded by whitespace - ~/dev/foo β€” anchored at the workspace root; reads cleanly at any nesting depth - grimoire/foo β€” anchored at the workspace root; preferred over deep ../../grimoire/foo from nested files

2. src/lib module references (backticked, written relative to src/lib without a leading ./ or ../). Marks the target as a code-like identifier β€” a module name, not a navigable filesystem path.

> Rule: when a path inside src/lib is wrapped in backticks, it MUST be > src/lib-relative β€” never ../foo.ts, never ./foo.ts, never > src/lib/foo.ts from a file already inside src/lib. The backticks frame > the token as a module identifier; relative traversal contradicts that > framing. Bare paths are the only place ./ and ../ belong.

- From any file inside src/lib: "auth/account_schema.ts" refers to src/lib/auth/account_schema.ts. Prefer this over both "../auth/account_schema.ts" (backticked with prefix β€” defeats the identifier framing) and ../auth/account_schema.ts (bare β€” reads as filesystem path) - From files outside src/lib (root CLAUDE.md, docs/, src/test/): include the src/lib/ prefix β€” "src/lib/auth/CLAUDE.md". The path-relative-to-src/lib form ("auth/CLAUDE.md") is also acceptable from src/test/, but the full-prefix form is unambiguous at any depth - Applies to any file under src/lib, including subsystem CLAUDE.mds: "auth/CLAUDE.md", "http/CLAUDE.md" - Section refs follow: "auth/CLAUDE.md" Β§Middleware (backticks wrap the module, Β§Heading follows outside the backticks) - Examples: - βœ… "server/upload_route.ts" β€” from src/lib/server/CLAUDE.md - βœ… "fuz_app/db/fact_store.ts" β€” from src/lib/fuz_util/CLAUDE.md - ❌ "../fuz_app/db/fact_store.ts" β€” backticked but traversal-relative - ❌ "./classroom_service.ts" β€” backticked but self-relative

3. Code-shaped things outside src/lib (backticks for code, not paths):

- CLI commands: gro check, deno task scry - Top-level project files: package.json, gitops.config.ts, tsconfig.json - System/config identifiers: ~/.fuz/, ~/.mg/config.json

Each file's relative paths assume the reader is in the file's parent directory. For ~/dev/CLAUDE.md, project paths are ./project/. For ~/dev/grimoire/CLAUDE.md, sibling grimoire files use ./lore/ and repo references use ../fuz_util/. From deeply nested files like grimoire/lore/fuz/design/foo.md, prefer grimoire/quests/bar.md over ../../../quests/bar.md.

Web-rendered caveat: in files published via mdz on a website (this SKILL.md on fuz_docs), ./foo and ../foo examples must be backticked to prevent mdz from rendering them as broken <a> tags. ~/dev/foo and grimoire/foo are safe bare in web context β€” mdz doesn't auto-linkify those prefixes.

Anti-patterns (linkifier won't fire, costing tokens and navigability):

- Mixing the two forms: backticks + a leading ./ or ../ is the wrong-of-both-worlds case. Pick a form. "./foo.md" should be either bare ./foo.md (navigational) or β€” for src/lib β€” "subsystem/foo.ts" (module-form, drop the relative prefix). - Backticking a navigable target: "~/dev/fuz_util" reads as a code identifier when it's actually a path. Use bare ~/dev/fuz_util. - Wrapping a path in markdown-link syntax when target equals visible text: [../README.md](../README.md) is redundant; bare ../README.md already auto-links. Same for [~/dev/foo](~/dev/foo) β€” collapse to bare ~/dev/foo. Reserve [text](url) for cases where the visible token *isn't* the path β€” e.g. a package-name-as-link: [@fuzdev/fuz_app](../../fuz_app).

Formatter cautions (Prettier in particular β€” these have bitten real docs):

- A line wrapping after + becomes a sublist. cell + fact followed by Prettier wrapping to + cell_history reflows as a bullet. Rephrase (cell, fact, and cell_history) or keep the + mid-line. - Bare _ in inline prose mixed with backticked identifiers can be parsed as italic delimiters and mangle text β€” eating spaces and swapping characters. Backtick identifiers like scope_id or cell_* even when the surrounding sentence isn't otherwise code-heavy. When several _-bearing identifiers appear in one sentence, restructure as a bullet list so each lands at end-of-line away from prose interactions.

Testing

Tests live in src/test/ (NOT co-located). Use assert from vitest β€” choose methods for TypeScript type narrowing, not semantic precision. assert(x instanceof Error) narrows the type; expect(x).toBeInstanceOf(Error) does not. Name custom assertion helpers assert_* (not expect_*).

Use describe blocks to organize tests β€” one or two levels deep is typical. Use test() (not it()).

Split large suites with dot-separated aspects: {module}.{aspect}.test.ts (e.g., csp.core.test.ts, csp.security.test.ts). Database tests use .db.test.ts suffix to opt into shared PGlite WASM via vitest projects (see ./references/testing-patterns).

For parsers and transformers, use fixture-based testing: input files in src/test/fixtures/<feature>/<case>/, regenerate expected.json via gro src/test/fixtures/<feature>/update. Never manually edit expected.json β€” always regenerate via task.

See ./references/testing-patterns for file organization, test helpers, shared test factories, mock factories, fixture workflow, database testing, environment flags, and test structure.

TODOs

Leave copious // TODO: comments in code β€” they're expected and encouraged for visibility into known future work, not debt to hide.

For multi-session work, create TODO_*.md files in the project root with status, next steps, and decisions. Delete when complete. Update before ending a session.

Custom Tasks

See ./references/task-patterns for the Task interface, Zod-based Args, TaskContext, error handling, override patterns, and task composition.

fuz_css

See ./references/css-patterns for setup, variables, composites, modifiers, extraction, and dynamic theming.

Semantic HTML first: fuz_css styles HTML elements by default β€” buttons, inputs, headings, links, lists, code, tables, <aside>, <blockquote>, <details>, <small>, <kbd>, etc. all get sensible defaults via :where() selectors. About half of fuz_ui's components have no <style> block at all.

Layered styling ladder: Stop at the first rung that suffices β€” 1. Semantic HTML (right element, no class needed) 2. Built-in class conventions (.selected, .color_a–.color_j on buttons, .inline, .unstyled, .sm/.md) 3. Composite classes (box, row, column, panel, chip, ellipsis) 4. Token classes (p_md, gap_lg, color_a_50) 5. Literal classes (display:flex, hover:opacity:80%) 6. <style> block with design tokens β€” last resort

See css-patterns.md Β§The Default Path and Β§Component Styling Philosophy.

Class naming: fuz_css tokens use snake_case (p_md, gap_lg). Component-local classes use kebab-case (site-header, nav-links).

3-Layer Architecture

| Layer | File | Purpose | | ------------------ | ----------- | --------------------------------------------------------- | | 1. Semantic styles | style.css | Reset + element defaults (buttons, inputs, forms, tables) | | 2. Style variables | theme.css | 600+ design tokens as CSS custom properties | | 3. Utility classes | fuz.css | Optional, generated per-project with only used classes |

CSS Classes

| Type | Example | Purpose | | --------------------- | ------------------------------------- | ---------------------------- | | Token classes | .p_md, .color_a_50, .gap_lg | Map to style variables | | Composite classes | .box, .row, .ellipsis | Multi-property shortcuts | | Literal classes | .display:flex, .hover:opacity:80% | Arbitrary CSS property:value |

Comment hints for static extraction: // @fuz-classes box row p_md, // @fuz-elements button input, // @fuz-variables shade_40 text_50.

When to Use Classes vs Styles

| Need | Utility class | Style tag | Inline style | | ---------------------- | ------------- | --------- | ------------ | | Style own elements | Preferred | Complex cases | OK | | Style child components | Yes | No | Limited | | Hover/focus/responsive | Yes | Yes | No | | Runtime dynamic values | No | No | Yes | | IDE autocomplete | No | Yes | Partial |

Dependency Injection

Small standalone *Deps interfaces, composed bottom-up. Leaf functions import small interfaces directly (not Pick<Composite>).

- Three suffixes β€” *Deps (capabilities/functions, fresh mock factories per test), *Options (data/config values, literal objects), *Context (scoped world for a callback/handler). No *Config suffix β€” use *Options. - Grouped deps β€” composite interface by domain. fuz_css uses deps.ts + deps_defaults.ts; fuz_gitops uses operations.ts + operations_defaults.ts. - AppDeps β€” stateless capabilities bundle for server code (fuz_app auth/deps.ts). - RuntimeDeps β€” composable small *Deps interfaces for runtime operations (env, fs, commands), with platform-specific factories (Deno, Node, mock). - Design principles β€” single options object params, Result returns (never throw), null for not-found, plain object mocks (no mocking libs), stateless capabilities, runtime agnosticism.

See ./references/dependency-injection for the full pattern guide, naming conventions, consumption patterns, RuntimeDeps, and mock factories.

Common Utilities

@fuzdev/fuz_util provides shared utilities:

- Result type β€” Result<TValue, TError> discriminated union for error handling without exceptions. Properties go directly on the result object via intersection: ({ok: true} & TValue) | ({ok: false} & TError). - Logger β€” hierarchical logging via new Logger('module'), controlled by PUBLIC_LOG_LEVEL env var - Timings β€” performance measurement via timings.start('operation') - DAG execution β€” run_dag() for concurrent dependency graphs - Async concurrency β€” each_concurrent, map_concurrent, map_concurrent_settled, AsyncSemaphore, Deferred - Type utilities β€” Flavored/Branded nominal typing, OmitStrict, PickUnion, selective partials

See ./references/common-utilities for Result patterns, Logger configuration, and Timings usage. See ./references/async-patterns for concurrency primitives. See ./references/type-utilities for the full type API.

Zod Schemas

Zod schemas are source of truth for JSON shape, TypeScript type, defaults, metadata, CLI help text, and serialization. Schema changes cascade through the stack; treat them as critical review points.

- z.strictObject() β€” default for all object schemas. z.looseObject() or z.object() for external/third-party data with a comment explaining why. - PascalCase naming β€” schema and type share the same name, no suffix: const Foo = z.strictObject({...}); type Foo = z.infer<typeof Foo>; - .meta({description: '...'}) β€” not .describe(). Both work in Zod 4 but .meta() is the convention and supports additional keys. - .brand() for validated nominal types β€” Uuid, Datetime, DiskfilePath - safeParse at boundaries β€” graceful errors for external input. parse for internal assertions.

See ./references/zod-schemas for branded types, transform pipelines, discriminated unions, route specs, schemas as runtime data, instance schemas (zzz Cell), and introspection.

Quick Reference

- gro check to validate (never run dev server) - snake_case for functions, PascalCase for types/components - Tests in src/test/, not co-located - Domain-prefix when ambiguous (git_push); action-first when self-descriptive (truncate) - TSDoc conventions: ./references/tsdoc-comments - Copious // TODO: comments; TODO_*.md for multi-session work - Token classes for design system values, literal classes for arbitrary CSS - z.strictObject() default, PascalCase naming, .meta() for descriptions - Breaking changes acceptable β€” delete unused code, don't shim - Never manually edit expected.json β€” regenerate via task