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: planning/lore-only edits, third-party code review, simple git/shell operations. Repo
CLAUDE.mdis authoritative for project-specific patterns — this skill covers shared conventions across TypeScript, Svelte, and Rust crates.
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 bottom-up (see Dependency flow below), with fuz_app 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.
@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 (web-dev surface; internals adopting Rust) |
fuz_css | semantic-first CSS framework and design system — apps look good by default |
mdz | minimal markdown dialect — parser, renderer, Svelte preprocessor |
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 web app template with TypeScript + SvelteKit + optional Rust for the fuz-stack |
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 |
Dependency flow: fuz_util → gro + fuz_css → mdz → fuz_ui → fuz_app → zzz, apps
(zap sits beside this chain: its site/authoring surface builds on fuz_ui, and
its Rust engine is spine-free — it consumes neither fuz_app nor the spine crates)
Adding deps: prefer the approved allowlists (./references/npm-dependencies, ./references/rust-dependencies). Adding or upgrading needs approval; removing an unused dep is pre-authorized.
// 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.svelteNOT camelCase for functions/variables. Intentional divergence:
keyed_hash, get_user_sessions, git_push).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';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 | git_workspace_is_clean | 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
All exported identifiers must have unique names across all modules:
svelte-docinfo analysis detects duplicate export names across modules
in the flat namespace/** @nodocs */ to exclude from validation@nodocs is the wrong tool when external consumers depend
on the hidden symbol (it vanishes from docs and tomes).State / Info. Example: DocsLink interface → DocsLinkInfo when
it conflicts with DocsLink.svelte. Precedent: ThemeState,
AuthState, SidebarState.View / Pane. Precedent:
zzz's Cell classes keep their names (Chat, Turn) and their
components take View (ChatView.svelte, TurnView.svelte); mdz's
MdzNodeView.svelte renders the MdzNode type.src/lib/ — exportable library code: PascalCase.svelte components,
*.ts utilities, *.svelte.ts runes/reactive code, *.gen.ts generated filessrc/test/ — tests (NOT co-located), mirroring lib/ structuresrc/routes/ — SvelteKit routes (if applicable)@fuzdev/fuz_app/env/load.ts);
package exports use wildcards so each module is importablelib/domain/ at 3+ related files;
a lone file stays at lib/ root. Tests mirror the subdir structure.See ./references/file-organization for the full tree, domain examples, and import/test-mirroring details.
.ts /
.svelte.ts, not the old .js-for-a-.ts-file form. See
./references/path-references §5 for the full rules:import {foo} from './bar.ts'; .svelte component imports
stay .svelte.@fuzdev/pkg/foo.ts resolves via the package's exports
.js/.ts mirror; the build rewrites relative .ts→.js into dist.src/lib) imports relative; everything else
(src/routes, src/test) uses the #lib/#routes package.json subpath
imports with the .ts extension (#lib/db/db.ts)./** ... */) = proper sentences with periods//) = fragments, no capital or periodIMPORTANT: 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 tsv
gro lint # run ESLint
gro typecheck # run TypeScript type checkingProduction:
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 workflowUtilities: gro sync (gen + update exports), gro run file.ts (execute
TS), gro changeset (create changeset). SKIP_EXAMPLE_TESTS=1 gro test
skips slow example tests in repos that support the flag (see
./references/testing-patterns).
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.
Custom tasks: see ./references/task-patterns for the Task interface, Zod-based Args, TaskContext, error handling, override patterns, and task composition.
Never run gro dev or npm run dev — user manages the dev server.
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 pattern: theme.gen.css.ts (theme CSS from style variables).
Two outputs that used to be gen tasks no longer are: fuz_css utility classes
come from the vite_plugin_fuz_css Vite plugin (the virtual:fuz.css module),
and library/API metadata comes from the svelte-docinfo Vite plugin — so most
projects run gro gen rarely, if ever.
See ./references/code-generation for the full API, dependencies, and examples.
See ./references/tsdoc-comments for the full tag guide, documentation patterns, and drift-detection guidance.
Key rules:
@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. The
description carries the tag — name the columns, cascades, or side channels a
reader wouldn't guess. When the method name already says it, omit the tag
rather than writing a bare `` @mutates target ``@internal: marks a symbol as not-stable API — stays fully documented
(consumers can badge or filter); use @nodocs to remove from docs entirely@nodocs: exclude from docs and flat namespace validationmdzTag order: description → @param → @returns → @mutates → @throws →
@example → @deprecated → @see → @since → @default → @internal →
@nodocs
Projects use tomes (not "stories") with auto-generated API docs.
Pipeline: source files → svelte-docinfo Vite plugin →
virtual:svelte-docinfo → library_json_from_modules() → 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 (@fuzdev/mdz/mdz.ts) is the Fuz markdown dialect — a small, unambiguous
grammar, not a CommonMark/GFM superset (ambiguous input stays literal text).
fuz_ui renders TSDoc prose through it, injecting DocsLink (inline code) and
fuz_code's Code (code blocks) via its rendering seam; backticked identifiers
that resolve to API symbols become links.
Supports code, bold/italic/strike (**/~~ doubled, italic single _ at word
boundaries; intraword _ stays literal so snake_case renders verbatim),
links, headings, lists, blockquotes,
code blocks, tables, horizontal rules, and registered components/elements.
<Mdz content="Some **bold** and `code` text." />Registration and rendering happen through getter contexts in
@fuzdev/mdz/mdz_contexts.ts (mdz_components_context, mdz_elements_context,
mdz_code_context, mdz_codeblock_context). The full per-feature syntax table,
dialect surface, injection seam, backtick autolinking, and the
svelte_preprocess_mdz build-time preprocessor: ./references/mdz.
Forms by typography (mdz auto-linkifies bare .//../ paths in rendered
docs, so the typography carries meaning):
./foo, ../foo, ~/dev/foo)
for files referenced by location; mdz auto-linkifies .//../ after whitespace.
A bare path is a promise it resolves on disk — backtick an illustrative or
conceptual path (`` ./build/ ``) as the escape hatch./, ../, or redundant src/lib/ prefix (e.g. "auth/account_schema.ts");
the backticks frame a module identifier, so traversal/prefix contradicts the framing../other-repo/... for navigation, or the
@scope/pkg/foo.ts import specifier for a module's identity; the backticked
src/lib form is same-repo only, and TSDoc must not point outside its own repogro check),
top-level files (package.json), and config identifiers (~/.fuz/)See ./references/path-references for all forms in full, the web-rendered caveat, anti-patterns, and formatter cautions.
See ./references/svelte-patterns for $derived.by(), reactive collections
(SvelteMap/SvelteSet), schema-driven reactive classes, snippets, keyed each
blocks, effects, attachments, props, event handling, component composition,
debugging reactivity, and legacy features to avoid.
$state() for all reactive state — it proxies objects and arrays so in-place
mutation (push, splice, property writes, bind: on object properties) triggers
updates. $state.raw() is a performance opt-out for large wholesale-replaced
values, not a default. $derived for computed values, $effect for side
effects.
Standardized via create_context<T>() from
@fuzdev/fuz_ui/context_helpers.ts. Common contexts: theme_state_context
(theme), library_context (package API metadata), tome_context (current
doc page).
See ./references/css-patterns for setup, variables, composites, modifiers, extraction, and dynamic theming.
Default styling is the baseline — justify every deviation. fuz_css styles
semantic HTML by default (buttons, inputs, headings, links, lists, code, tables,
<aside>, <blockquote>, <details>, <small>, <kbd>, …) via
low-specificity :where() selectors, and block elements space themselves via
the flow-margin system — so most content needs zero classes. The most common
mistake is hand-adding mb_*/gap_*/p_* where flow margin already spaces, or
re-declaring color/font the element already carries. Before any class or
<style>, ask what specific gap in the defaults it closes — most app files have
no <style> block at all.
<!-- BAD: these classes fight defaults the elements already have -->
<section>
<h2 class="mb_md">{title}</h2>
<!-- headings already carry flow margin -->
<p class="mb_md">{body}</p>
<!-- so do paragraphs -->
</section>
<!-- GOOD: correct vertical rhythm with zero classes -->
<section>
<h2>{title}</h2>
<p>{body}</p>
</section>Styling ladder — stop at the first rung that suffices:
.selected, .palette_a–.palette_j, .inline, .unstyled)row, column, box, panel, chip, ellipsis)p_md, gap_lg, color_a_50) — spacing tokens are the most-used familydisplay:flex, width:100%, hover:opacity:80%)<style> block with design tokensRungs 3–5 are one tier in practice — mix freely; the real cut points are
semantic-vs-class and classes-vs-<style>. Don't churn existing <style>
blocks into long class strings. See ./references/css-patterns §The Styling
Ladder.
Class naming: fuz_css tokens use snake_case (p_md, gap_lg);
component-local classes use kebab-case (site-header) — the target convention,
adopted in zzz and fuz_ui.
Architecture — the three layers (semantic defaults, design tokens, utility
classes), the class families, and the classes-vs-<style> matrix: see
./references/css-patterns §Style Variables (Design Tokens), §Utility
Classes, and §When to Use Classes vs Styles.
Small standalone *Deps interfaces, composed bottom-up. Leaf functions
import small interfaces directly (not Pick<Composite>).
*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. *Deps
names the injected bundle; single-capability service interfaces keep pure-noun
names (Keyring, FactStore).deps.ts + deps_defaults.ts + test-side mock_deps.ts
(fuz_css is the cleanest exemplar). fuz_gitops's *Operations spelling is
legacy, migrating to *Deps — never author new *Operations.*Deps interfaces for runtime operations
(env, fs, commands), with platform-specific factories (Deno, Node, mock).
Browser/UI DI is Svelte context, not *Deps params.options object params in L1 domain deps,
Result returns with typed error kinds (L0 platform shims mirror the
platform and throw), plain object mocks (no mocking libs), throwing stubs
over silent no-ops, stateless capabilities, runtime agnosticism.See ./references/dependency-injection for the full pattern guide, naming conventions, consumption patterns, RuntimeDeps, and mock factories.
@fuzdev/fuz_util provides shared utilities:
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).to_error_message — to_error_message(value, fallback?) from
@fuzdev/fuz_util/error.ts normalizes an unknown caught value to a string
(value.message for Error, else fallback ?? String(value))new Logger('module'), controlled by
PUBLIC_LOG_LEVEL env vartimings.start('operation')run_dag() for concurrent dependency graphseach_concurrent, map_concurrent,
map_concurrent_settled, AsyncSemaphore, DeferredFlavored/Branded nominal typing, OmitStrict,
PickUnion, selective partialsSee ./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 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, client-consumed
response/error schemas, and protocol shapes the other side may extend —
with a comment explaining why.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, DiskfilePathsafeParse 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.
One query module per table (query_<table>_<verb>, deps: QueryDeps first,
assert_row on INSERT … RETURNING). Every read projects through the
table's exported *_COLUMNS const — never SELECT * — rendered at the
site via columns_sql / qualify_columns / omit_columns, and each const
is drift-guarded against the live schema (assert_columns_match_live).
The Rust twin uses the same identifiers with positional decode in const
order. See ./references/db-patterns.
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.base.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.
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.
The ecosystem's Rust workspaces (the fuz/fuzd CLI + daemon, the spine
crates consumed by zzz_server/fuz_forge_server, the zap convergence
engine, the blake3/tsv bindings) share a distinct set of conventions from
the TS/Svelte side. snake_case carries over for cross-language alignment, but
Rust solves with the type system + crate graph what TS solves with *Deps
injection. These references own conventions and patterns — adoptable by any
Rust workspace, including new/external ones, with ecosystem repos as
exemplars; each repo's CLAUDE.md owns its inventory (crates, commands, env
vars). Six references, loaded on demand:
unsafe_code = "forbid", pedantic + nursery + restriction lints;
the crate-override re-declare trap), release profile, thiserror error
taxonomy + .hint()/.exit_code() helpers and classifiers, graceful
shutdown, the DI escalation ladder
(*Options/boxed-closure-factories/capability-traits/enum-dispatch-before-dyn/RPITIT), the
make-impossible-states-unrepresentable idiom (zap_types is the reference),
CLI/exit-code patterns, and shared patterns (sandboxed eval, transactional
state files, CAS, bounded reads, type state, secret masking).*_COLUMNS const + fuz_db::qualify_columns projection, positional
decode with compile-time name→index (fuz_db::col!), and the
tests/columns.rs drift guard (TS twin in the same doc).run_app, RunAppOptions, the testing_* sibling binary),
the fuz_http JSON-RPC envelope, env loading, daemon lifecycle by
transport, and fuz_audit check-release + crate-layering rules.bumpalo in tsv),
lock hygiene, hot-path idioms, the unsafe escape hatch, and what's out
of scope.WASM, C-FFI, and N-API binding crates additionally follow
./references/wasm-patterns. Each Rust repo's CLAUDE.md is authoritative
for project-specific conventions; these cover the shared patterns across
workspaces.