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: fuz_util → gro + fuz_css → mdz → fuz_ui → fuz_app → apps,
with fuz_app the shared backend spine (auth, sessions, DB, SSE) — a chain hop
for apps as well as the spine. 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 | 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 static web app template built with 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 |
gro is a durable web-focused dev tool; its internals progressively adopt Rust (tsv, then fuz crates), and it stays complementary to fuz and zap.
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 | 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
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. Example:
MusicPlayer class kept, component renamed to MusicPlayerView.svelte.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): `import {foo} from
'./bar.ts'. Cross-package @fuzdev/pkg/foo.ts` resolves via the package's
exports .js/.ts mirror; the build rewrites relative .ts→.js into
dist. .svelte component imports stay .svelte. Library code (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). See ./references/path-references §5./** ... */) = 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 (fuz_css; not
svelte-docinfo, whose example tests gate on npm run build +
npm run setup-examples instead — 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.
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
(also `` @mutates target `` for self-evident mutations)@nodocs: exclude from docs and flat namespace validationmdzTag order: description → @param → @returns → @mutates → @throws →
@example → @deprecated → @see → @since → @default → @nodocs
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.
$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.
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).
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 (double delimiters only; 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:
./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.
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.
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.
See ./references/task-patterns for the Task interface, Zod-based Args, TaskContext, error handling, override patterns, and task composition.
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, .color_a–.color_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 (a composite when one exactly
matches, else tokens/literals); literal flex classes are common, not a rare last
resort. The real cut points are semantic-vs-class and classes-vs-<style>. Don't
churn existing <style> blocks into long class strings (4–6 classes is the
comfortable ceiling). See css-patterns.md §Default styling is the baseline.
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.
style.css), design tokens as
CSS custom properties (theme.css), and per-project utility classes
(virtual:fuz.css, only used classes emitted). See css-patterns.md §Style
Variables (Design Tokens) and §Utility Classes..p_md, .color_a_50) map to variables,
composite classes (.box, .row; size composites xs–xl rescale a subtree)
are multi-property shortcuts, literal classes (.display:flex) are arbitrary
property:value. Static-extraction comment hints (// @fuz-classes …) are
rarely needed — see css-patterns.md §Comment hints for the dynamic cases.<style> — utility classes for your own and child elements;
<style> for hover/focus/responsive; inline style: only for runtime dynamic
values. Full matrix: css-patterns.md §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 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.
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). Five 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).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.