State Management Architecture
This document describes the state management architecture of the application, focusing on GeneralStore, YjsStore, and the initialization process.
Component Overview
The application uses a hybrid state management approach:
- Svelte 5 Runes:
GeneralStore(store.svelte.ts) uses$stateand$derivedfor reactive UI state. - Yjs: Distributed state for collaborative features (Project, Page, Items).
- YjsStore: A bridge that monitors
YjsClientconnection status and synchronizes the collaborativeProjectmodel into the reactiveGeneralStore.
mermaid
classDiagram
class GeneralStore {
+Project? project
+Item? currentPage
+Object pages
+string? openCommentItemId
+set project(Project v)
}
note for GeneralStore "Singleton: `store` export.\nMain ViewModel for UI."
class YjsStore {
+YjsClient? yjsClient
+boolean isConnected
-string? _lastProjectGuid
+set yjsClient(YjsClient? v)
}
note for YjsStore "Singleton: `yjsStore` export.\nBridge between Network & UI."
class YjsService {
+createNewProject()
+createClient()
+getClientByProjectTitle()
}
note for YjsService "Stateless Factory (mostly).\nManages Client Registry."
class YjsClient {
+Project getProject()
+string containerId
+boolean isContainerConnected
}
class Project {
+string title
+Y.Doc ydoc
+Items items
}
GeneralStore <-- YjsStore : Updates .project
YjsStore --> YjsClient : Watches
YjsClient --> Project : Manages
YjsService ..> YjsClient : Creates/Connects
YjsService ..> YjsStore : Sets .yjsClientInitialization Process
The initialization flow ensures that the UI has something to show immediately (Provisional Project), and then seamlessly switches to the collaborative data when the connection is established.
mermaid
sequenceDiagram
participant Browser
participant Layout as +layout.svelte
participant Store as GeneralStore
participant YService as YjsService
participant YStore as YjsStore
participant YClient as YjsClient
Note over Browser, Store: 1. Provisional Phase (Immediate)
Browser->>Layout: Load Page (URL: /proj/page)
Layout->>Store: Check/Init Provisional Project
Store-->>Layout: Project ready (Local only)
Layout->>Store: Set Provisional currentPage
Note right of Layout: UI renders immediately with local data
Note over Browser, Store: 2. Connection Phase (Async)
Layout->>YService: Import & Init
YService->>YClient: connect(projectId)
YClient-->>YService: Connected Client
YService->>YStore: set yjsStore.yjsClient = client
Note over YStore, Store: 3. Synchronization Phase
YStore->>YClient: getProject()
YClient-->>YStore: Connected Project (Y.Doc)
YStore->>Store: set project = Connected Project
par Merge State
Store->>Store: Detect Project Switch
Store->>Store: Migrate Page Title/ID if needed
Store->>Store: Re-bind currentPage to new Project
end
Store-->>Layout: Reactivity Update
Note right of Layout: UI updates to show real collaborative dataTest Dependency Injection
In E2E tests (Playwright), we need to inspect and manipulate the internal state. Since Svelte stores are singletons, we expose them to window for test access.
mermaid
graph TD
subgraph Browser Window
G[window.generalStore] --> S[GeneralStore Singleton]
Y[window.__YJS_STORE__] --> YS[YjsStore Singleton]
R[window.__YJS_CLIENT_REGISTRY__] --> Reg[Client Registry]
end
subgraph Test Runner [TestHelpers.ts]
TH[TestHelpers]
end
TH -- page.evaluate() --> G
TH -- page.evaluate() --> Y
TH -- Seeding --> S
note["Test Strategy: <br/>1. Access store via window globals<br/>2. Poll for state changes (waitForFunction)<br/>3. Direct manipulation for seeding"]
TH -.-> noteKey Files
client/src/stores/store.svelte.ts: GeneralStore. Core application state.client/src/stores/yjsStore.svelte.ts: YjsStore. Reactivity bridge.client/src/lib/yjsService.svelte.ts: YjsService. High-level connection logic.client/src/routes/+layout.svelte: Bootstrapper. Orchestrates the init flow.