Summary
Add a persistent Client entity as the top-level organizational unit for grouping Projects. Clients are event-sourced through the existing command pipeline — no new architectural patterns required.
Scope
- Define
ClientId branded type in packages/contracts/src/baseSchemas.ts
- Add
OrchestrationClient schema to packages/contracts/src/orchestration.ts
- Add commands:
client.create, client.rename, client.reorder, client.archive, client.unarchive, client.delete
- Add corresponding events:
client.created, client.renamed, client.reordered, client.archived, client.unarchived, client.deleted
- Add
"client" to aggregateKind union and commandToAggregateRef in OrchestrationEngine.ts
- Add decider cases in
decider.ts
- Add projector cases in
projector.ts
- Add SQLite migration for
projection_clients table (client_id, name, sort_order, created_at, updated_at, archived_at, deleted_at)
- Add
ProjectionClientRepository service + SQL layer
- Add
clients[] array to OrchestrationReadModel snapshot
- Wire up RPC endpoint for dispatch
Constraints
- Client names unique (case-insensitive)
- Name length: 1–100 chars (trimmed, non-empty)
- Max 100 clients per instance
- Delete is soft-delete (
deletedAt), does not cascade to Projects
- Archive hides from default sidebar but preserves children
sortOrder persisted server-side (not localStorage)
- "Unassigned" section is virtual (not a real Client entity)
Key files to modify
packages/contracts/src/baseSchemas.ts — add ClientId
packages/contracts/src/orchestration.ts — commands, events, read model
packages/contracts/src/rpc.ts — if new RPC method needed
apps/server/src/orchestration/Layers/OrchestrationEngine.ts — commandToAggregateRef
apps/server/src/orchestration/decider.ts
apps/server/src/orchestration/projector.ts
apps/server/src/persistence/Migrations/ — new migration
apps/server/src/persistence/Services/ — new repository interface
apps/server/src/persistence/Layers/ — new repository SQL impl
Dependencies
None — this is the foundation.
Acceptance criteria
Summary
Add a persistent Client entity as the top-level organizational unit for grouping Projects. Clients are event-sourced through the existing command pipeline — no new architectural patterns required.
Scope
ClientIdbranded type inpackages/contracts/src/baseSchemas.tsOrchestrationClientschema topackages/contracts/src/orchestration.tsclient.create,client.rename,client.reorder,client.archive,client.unarchive,client.deleteclient.created,client.renamed,client.reordered,client.archived,client.unarchived,client.deleted"client"toaggregateKindunion andcommandToAggregateRefinOrchestrationEngine.tsdecider.tsprojector.tsprojection_clientstable (client_id,name,sort_order,created_at,updated_at,archived_at,deleted_at)ProjectionClientRepositoryservice + SQL layerclients[]array toOrchestrationReadModelsnapshotConstraints
deletedAt), does not cascade to ProjectssortOrderpersisted server-side (not localStorage)Key files to modify
packages/contracts/src/baseSchemas.ts— addClientIdpackages/contracts/src/orchestration.ts— commands, events, read modelpackages/contracts/src/rpc.ts— if new RPC method neededapps/server/src/orchestration/Layers/OrchestrationEngine.ts—commandToAggregateRefapps/server/src/orchestration/decider.tsapps/server/src/orchestration/projector.tsapps/server/src/persistence/Migrations/— new migrationapps/server/src/persistence/Services/— new repository interfaceapps/server/src/persistence/Layers/— new repository SQL implDependencies
None — this is the foundation.
Acceptance criteria
clients[]arrayclientsdecode successfully (backward compat viaSchema.optional/withDecodingDefault)