Layered Architecture
Basalt enforces a strict four-layer architectural model. Dependencies flow strictly downward—never upward, and never sideways between peer features.
┌────────────────────────────────────────────────────────┐
│ routes/ TanStack Router (exactly 1 route) │
│ main.tsx App entry, provider tree │
└───────────────────────────┬────────────────────────────┘
│ imports
┌───────────────────────────▼────────────────────────────┐
│ app-shell/ Layout composition │
│ Wires features into UI │
│ ONLY place cross-feature │
│ wiring happens │
├────────────────────────────────────────────────────────┤
│ shared/ Cross-feature orchestration │
│ Vault ↔ Tabs ↔ Editor wiring │
│ Commands that touch 2+ features │
└───────────────────────────┬────────────────────────────┘
│ imports
┌───────────────────────────▼────────────────────────────┐
│ features/ Business logic per domain │
│ ├── vault/tabs/editor/search/settings/graph │
│ ├── calendar/kanban/tasks/canvas/drawing │
│ └── plugins/templates/export/assets │
│ 🚫 NEVER import from another feature │
│ ✅ MAY import types from another feature's types.ts │
└───────────────────────────┬────────────────────────────┘
│ imports
┌───────────────────────────▼────────────────────────────┐
│ packages/ ui/, editor/, commands/, │
│ keybindings/, theme/, views/, │
│ graph/, benchmarks/ │
│ Primitives + registries │
│ 🚫 No Tauri, no business state, no IPC (ui/) │
└────────────────────────────────────────────────────────┘
The Four Layers
| Layer | Location | Responsibility | Tauri / IPC Allowed? |
|---|---|---|---|
| Primitives | packages/ui/, packages/theme/ | Visual components, design tokens. Props in, DOM out. | 🚫 Never |
| Features | apps/tauri/src/features/* | Business logic, state, hooks, domain IPC. One per domain. | ✅ Yes |
| Shared | apps/tauri/src/shared/* | Cross-feature orchestration. Coordinates 2+ features. | ✅ Yes |
| Shell | apps/tauri/src/app-shell/* | Layout composition, workbench grid, dock mounting. Thin glue. | ✅ Yes |
The Layer Litmus Test
When deciding where a new file or component belongs, ask:
Can this render in an empty index.html with zero backend?
- Yes →
packages/ui/ - No, and it belongs to one domain →
apps/tauri/src/features/<domain>/ - No, and it coordinates two or more features →
apps/tauri/src/shared/ - It is window chrome, docks, or workbench layout →
apps/tauri/src/app-shell/
Architectural Hard Rules
1. No Sideways Feature Imports
Features must remain strictly decoupled:
features/tasks/must never import components, hooks, or stores fromfeatures/editor/.- Cross-feature communication happens either via
shared/orchestration or through global registries. - Exception: A feature MAY import pure TypeScript types from another feature's
types.tsfile (import type { ... } from "@/features/editor/types").
2. No Upward Imports
A lower layer must never import from a higher layer:
packages/*cannot import fromapps/tauri/.features/*cannot import fromshared/orapp-shell/.
3. Public Feature API via index.ts
Every feature directory exports its public interface through a single index.ts. External layers must import from @/features/<name>, never from internal paths like @/features/<name>/hooks/useInternal.ts.
4. File and Store Budgets
To prevent monolithic sprawl:
- Max 2 stores per feature:
core.tsfor memory state andpersistence.tsfor disk sync. - Max 4 hooks per feature: Prohibits unnecessary wrapper hooks that just spread sub-hooks.
Machine Enforcement
These rules are not mere guidelines—they are machine-enforced on every commit and build via custom Oxlint plugins:
bun run lint
Any illegal cross-feature import, upward dependency, or barrel leak triggers an immediate lint failure and blocks CI.