architecture · git:20260103.9f62587 · 2026-01-03 · sha256 705622ad6abce4a5
architecture git:20260103.9f62587A
Immutable. This exact content is served forever at /api/v1/blob/705622ad6abce4a5.
---
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`