Skip to main content

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​

LayerLocationResponsibilityTauri / IPC Allowed?
Primitivespackages/ui/, packages/theme/Visual components, design tokens. Props in, DOM out.🚫 Never
Featuresapps/tauri/src/features/*Business logic, state, hooks, domain IPC. One per domain.✅ Yes
Sharedapps/tauri/src/shared/*Cross-feature orchestration. Coordinates 2+ features.✅ Yes
Shellapps/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 from features/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.ts file (import type { ... } from "@/features/editor/types").

2. No Upward Imports​

A lower layer must never import from a higher layer:

  • packages/* cannot import from apps/tauri/.
  • features/* cannot import from shared/ or app-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.ts for memory state and persistence.ts for 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.