> 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.
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.
@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
// 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:
- 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';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:
- 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.
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)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 helpersWhen 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)- 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.
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 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
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.
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.
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
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.js. 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 β 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 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.
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.
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.
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).
| 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 |
| 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.
| 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 |
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.
@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 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.
- 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