> 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, Rust projects (use repo CLAUDE.md), > third-party code review, simple git/shell operations.
@fuzdev/* packages draw from these conventions. Each package's CLAUDE.md
is authoritative for what it actually uses.
| Package | Description |
| -------------- | ------------------------------------------------------------------------ |
| fuz_util | utility belt for JS |
| gro | task runner and toolkit extending SvelteKit |
| fuz_css | CSS framework and design system for semantic HTML |
| fuz_ui | Svelte UI library |
| fuz_app | shared backend library (auth, sessions, DB, SSE, route specs, CLI) |
| 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 | local-first forge for power users and devs |
gro is a temporary build tool, will be replaced by fuz.
Dependency flow: fuz_util -> gro + fuz_css -> fuz_ui -> fuz_* 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
- Example: DocsLink interface -> DocsLinkInfo when it conflicts with
DocsLink.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 auditing.
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() for reactive state, $derived for computed values, $effect for
side effects. $state.raw() for data replaced wholesale (API responses).
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.
Tests live in src/test/ (NOT co-located). Core repos (fuz_app, fuz_ui,
fuz_util) prefer assert from vitest — choose methods for TypeScript type
narrowing, not semantic precision. Some repos (gro, zzz, fuz_css, fuz_gitops)
use expect — follow existing convention per repo.
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.
| 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 | Style tag | Utility class | Inline style | | ---------------------- | --------- | ------------- | ------------ | | Style own elements | Best | OK | OK | | Style child components | No | Yes | Limited | | Hover/focus/responsive | Yes | Yes | No | | Runtime dynamic values | No | No | Yes | | IDE autocomplete | Yes | No | 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