# Repository Guidelines ## Project Structure Svelte 5 + Tailwind CSS 4 UI component library monorepo on Bun workspaces. - Root is a Bun workspace (`"private": true`). Source in `packages/*`: - `blocks`: Svelte UI components - `primitives`: Atomic UI components - `components`: complex UI widgets built on top of the primitives - `table`: Data table (sorting, filtering, grouping, selection, keyboard-nav, virtualization, column reorder, remote mode, live updates) - `docs`: Reusable documentation UI components - `docs-gen`: Documentation generator (TypeScript CLI, extracts props/variants from AST) - `i18n`: Localization (Svelte 5 runes-based); also ships a data-level translation audit and a dev-only `@urbicon-ui/i18n/audit` source scanner (unused / used-but-undefined keys, hardcoded strings), fronted by `urbicon i18n` and `bun run i18n:check` (which scans `blocks`, `table` and `docs` — `auth` keeps its own locale system, and its oracle is `translations.parity.test.ts` plus `satisfies AuthLocale`) - `shared-types`: docs-tooling types (playground, docs-config, navigation) — a peer of `@urbicon-ui/docs` and a dependency of `docs-gen`, nothing else; `blocks` and `table` reference its `globals` augmentation only while type-checking their own sources - `sveltekit-utils`: SvelteKit helper utilities (`createCronRunner`, URL-state runes) - `design`: the `urbicon` CLI (`@urbicon-ui/design`) — local design-loop enforcement (validate/hook/context/record-decision/sync-manifest/i18n/verb), ships the design skill + templates - `urbicon`: the unscoped bin name — forwards in-process to `@urbicon-ui/design` (`workspace:*`, lockstep) so `bunx urbicon` resolves everywhere; `init` still needs design installed - `sv`: Svelte-CLI community add-on (`@urbicon-ui/sv`, beta) — `sv add @urbicon-ui` installs blocks + design, wires the Tailwind stylesheet after Tailwind's own, then hands over to `urbicon init --hook`. **The only SvelteKit-bound consumer path** (`unsupported('Requires SvelteKit')`) — not a library limit but a wiring one, see the comment in `src/index.ts` - `design-content`: versioned design knowledge bundle (`@urbicon-ui/design-content`) consumed by the `urbicon` CLI; `content/` is a git-ignored build artifact emitted by docs-gen - `design-engine`: zero-dep design linter / manifest parser / rubric (`@urbicon-ui/design-engine`), subpath exports `./linter` `./manifest` `./rubric` - `auth`: Authentication & user management (JWT sessions, refresh-token rotation, passkeys/WebAuthn, notifications, email) - Zero runtime dependencies — Web Crypto API for JWT, PBKDF2, WebAuthn (CBOR, ECDSA, RSA), Web Push (RFC 8291/8292) - Server: handler factories, handle hook, adapter pattern (Prisma adapter included). Client: Svelte 5 Runes stores, blocks-based UI, own i18n (EN/DE) - Apps in `apps/docs` (documentation site) and `apps/artifact-studio` - Build artifacts: `dist/`, `.svelte-kit/` (git-ignored) ## Key Architecture Decisions - **Styling**: Custom `tv()` variant engine (`packages/blocks/src/lib/utils/variants.ts`, zero-dep replacement for `tailwind-variants`) - **Design Tokens**: OKLCH color system with 3-layer architecture (foundation → semantic → interaction) in `blocks/src/lib/style/` - **Dark Mode**: Semantic tokens handle dark mode automatically via the CSS `light-dark()` function (follows `color-scheme` / the user's `prefers-color-scheme`); no manual `dark:` overrides - **Focus**: `focus-visible:` everywhere (not `focus:`), for keyboard-only focus rings - **Z-Index**: CSS custom property tokens (`--z-modal`, `--z-dropdown`, etc.) via `z-[var(--z-*)]` - **Components**: All support `unstyled` + `slotClasses` + `preset` props for style overrides; `BlocksProvider` additionally accepts prop-conditional `overrides` (style only a specific variant/intent/state, e.g. the `outlined` variant) - **Internal core layer**: Public blocks components never import each other for trivial embedded controls (close ×, loading spinner, icon-only nav button) — they use the never-exported cores in `src/lib/internal/core/` (`ls` it for the roster). Essential compositions (ConfirmDialog→Dialog) stay direct imports but need a justified allowlist entry in `packages/blocks/scripts/imports-lint.ts`; `imports:lint` errors on unknown edges AND stale entries. Canon: [ARCHITECTURE.md § The internal core layer](docs/ARCHITECTURE.md#the-internal-core-layer). - **A gate is the last resort, not the first**: before adding a lint, ask whether the state it would report can be made _unrepresentable_ instead — a value derived from one source cannot disagree with itself, and a gate that keeps two hand-written copies of one list agreeing is paying rent on the duplication rather than removing it (#146, #147, #148). When a gate genuinely is the answer, it must ask a real system — the compiler, Tailwind, the bundler, axe, a generator plus `--check` (`a2ui:axes:check` is the model). For full details see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). ## Commands `bun run` lists every script. Non-obvious ones: - Single package: `bun --filter='@urbicon-ui/blocks' run