architecture · diff
git:20260103.9f62587 to git:20260309.80c3cb2
1 added, 198 removed. Audit A to B.
---
description: Core architecture rules - layer separation, dependencies, and anti-patterns
globs: src/core/**/*
alwaysApply: false
---
# Architecture Rules
- Rules for maintaining the layered architecture in `src/core/`. Based on ADR-0004, ADR-0008, ADR-0009.
-
- ## Layer Flow
-
- ```
- UI (user actions) ──────┐
- ↓
- Coordinators (system) ─→ Controllers → Application → Services → Models
- ↓ ↓ ↓
- Stores Pipes Database
- ```
-
- ## Entry Points
-
- Only these can initiate workflows:
-
- | Entry Point | Trigger | Calls |
- |-------------|---------|-------|
- | **UI** | User actions (clicks, forms) | Controllers |
- | **Coordinators** | System events (timers, auth, visibility) | Controllers |
-
- ## Layer Responsibilities
-
- ### Controllers (`src/core/controllers/`)
- - ✅ Entry point for user-initiated actions
- - ✅ Invoke pipes for normalization/validation
- - ✅ Call application for business logic
- - ✅ Mutate stores for UI state
- - ❌ NEVER call services directly
- - ❌ NEVER perform IO
-
- ### Coordinators (`src/core/coordinators/`)
- - ✅ Entry point for system-initiated actions
- - ✅ React to auth, visibility, route changes
- - ✅ Call controllers (like UI does)
- - ❌ NEVER call application directly
- - ❌ NEVER call services directly
-
- ### Application (`src/core/*/application/`)
- - ✅ Orchestrate business workflows
- - ✅ Called BY controllers (NOT an entry point)
- - ✅ Call services for IO
- - ✅ Can call other Applications (with restrictions)
- - ❌ NEVER access stores directly
- - ❌ NEVER call controllers
-
- ### Services (`src/core/*/services/`)
- - ✅ Handle IO boundaries
- - ✅ `local/` - Dexie persistence
- - ✅ `homeserver/` - Network writes (PUT/POST/DELETE)
- - ✅ `nexus/` - Network reads
- - ❌ NEVER call application or controllers
- - ❌ NEVER access stores
-
- ### Pipes (`src/core/*/pipes/`)
- - ✅ Normalize and validate data
- - ✅ Transform external shapes to domain shapes
- - ✅ Pure functions only
- - ❌ NEVER perform IO
- - ❌ NEVER access database or network
-
- ### Models (`src/core/*/models/`)
- - ✅ Dexie-based persistence only
- - ✅ CRUD operations on IndexedDB
- - ❌ NEVER perform network calls
- - ❌ NEVER access stores
-
- ## Application Cross-Domain Rules (ADR-0009)
-
- Only these Applications can call other Applications:
- - `PostApplication`
- - `UserApplication`
- - `NotificationApplication`
-
- ### Restrictions
- ```typescript
- // ✅ ALLOWED: Orchestrators can call helper applications
- PostApplication.createWithAttachments() {
- await FileApplication.upload(files); // OK
- await TagApplication.commitCreate(tags); // OK
- }
-
- // ❌ FORBIDDEN: Helper applications cannot call others
- FileApplication.upload() {
- await TagApplication.create(); // ❌ VIOLATION
- await PostApplication.get(); // ❌ VIOLATION
- }
-
- // ❌ FORBIDDEN: No circular dependencies
- PostApplication → FileApplication → PostApplication // ❌
-
- // ❌ FORBIDDEN: Max call depth is 1
- PostApplication → FileApplication → ImageProcessor // ❌ Too deep
- ```
-
- ## Anti-Patterns to Catch
-
- ### ❌ Controller calling Service directly
- ```typescript
- // BAD
- class PostController {
- static create() {
- await LocalPostService.upsert(post); // ❌ Bypass application
- }
- }
-
- // GOOD
- class PostController {
- static create() {
- await PostApplication.create(post); // ✅ Through application
- }
- }
- ```
-
- ### ❌ Application accessing Store
- ```typescript
- // BAD
- class PostApplication {
- static create() {
- usePostStore.getState().setLoading(true); // ❌
- }
- }
-
- // GOOD - Controller handles store
- class PostController {
- static create() {
- usePostStore.getState().setLoading(true);
- await PostApplication.create(post);
- }
- }
- ```
-
- ### ❌ IO in Pipes
- ```typescript
- // BAD
- class PostPipe {
- static normalize(post) {
- const user = await LocalUserService.get(post.author); // ❌ IO!
- }
- }
-
- // GOOD
- class PostPipe {
- static normalize(post) {
- return { ...post, id: `${post.author}:${post.postId}` }; // ✅ Pure
- }
- }
- ```
-
- ### ❌ Coordinator calling Application directly
- ```typescript
- // BAD
- class NotificationCoordinator {
- static poll() {
- await NotificationApplication.fetch(); // ❌ Bypass controller
- }
- }
-
- // GOOD
- class NotificationCoordinator {
- static poll() {
- await UserController.notifications(); // ✅ Through controller
- }
- }
- ```
-
- ## File Organization
-
- ```
- src/core/
- ├── controllers/ # Entry points for UI
- ├── coordinators/ # Entry points for system
- ├── [domain]/
- │ ├── application/ # Business logic orchestration
- │ ├── services/
- │ │ ├── local/ # Dexie operations
- │ │ ├── homeserver/ # Network writes
- │ │ └── nexus/ # Network reads
- │ ├── pipes/ # Data transformation
- │ └── models/ # Dexie tables
- ├── stores/ # UI state (Zustand)
- └── index.ts # Public API
- ```
-
- ## Quick Checklist
-
- When adding/modifying code in `src/core/`:
-
- - [ ] Does it respect layer boundaries?
- - [ ] Is Application called BY controller, not calling controller?
- - [ ] Are Coordinators going through Controllers?
- - [ ] Is IO only in Services?
- - [ ] Are Pipes pure (no IO)?
- - [ ] Does cross-domain call follow ADR-0009 rules?
-
- ---
-
- **Reference**: `.cursor/adr/0004-layering-and-dependency-rules.md`, `.cursor/adr/0008-coordinators-layer.md`, `.cursor/adr/0009-application-cross-domain-orchestration.md`
+ @docs/architecture.md