Write-Effect Coordinator
Status: Implemented Crate: apps/desktop/src-tauri/src/side_effects.rs
Overview
Section titled “Overview”The Write-Effect Coordinator is a static namespace struct (WriteEffectCoordinator) that dispatches fire-and-forget
side effects after every write operation. It exists to enforce a critical invariant: writes never fail due to
side-effect failures. Analytics tracking, event logging, and embedding re-indexing are all best-effort — if any side
effect fails, the error is logged and discarded.
This decoupling serves three purposes:
-
Write reliability — A page save must never fail because analytics is unreachable or the embedding model is unavailable. Side effects are secondary to the user’s data mutation.
-
Centralized audit — Every write path in the application flows through one coordinator, making it easy to audit which mutations trigger which side effects.
-
Zero-cost abstraction — The coordinator is a static struct with no instance state. Methods take
&AppStateas a parameter, avoiding circular ownership in the dependency graph.
SideEffectContext Trait
Section titled “SideEffectContext Trait”The SideEffectContext trait (defined in crates/application/src/side_effects.rs) is the application-layer abstraction
that makes write-effect coordination transport-agnostic.
pub trait SideEffectContext: Send + Sync { fn workspace_name(&self) -> String; fn event_log_repo(&self) -> Arc<dyn EventLogRepository + Send + Sync>; fn analytics_repository(&self) -> Arc<dyn AnalyticsRepository + Send + Sync>; fn try_send_embed_page(&self, page_id: Uuid); fn try_send_fts_rebuild_page(&self, page_id: Uuid);}WriteEffectCoordinator methods accept &impl SideEffectContext instead of concrete state types. This means the same
coordinator logic runs unchanged across all transports:
| Implementor | Location | Behavior |
|---|---|---|
AppState | apps/desktop/src-tauri/src/ | Full side effects — embedding, analytics, FTS |
McpState | apps/desktop/src-tauri/src/mcp/ | Partial side effects — analytics, FTS (no embed) |
BridgeState | apps/http-bridge/src/state.rs | All no-ops — QA bridge does not fire side effects |
The trait uses primitive dispatch methods (try_send_embed_page, try_send_fts_rebuild_page) rather than exposing
framework-level handle types. This keeps the application crate free of framework dependencies while enabling full
coordination in the Tauri layer.
Architecture Diagram
Section titled “Architecture Diagram”Registered Effects
Section titled “Registered Effects”Each public method on WriteEffectCoordinator corresponds to a specific mutation. The table below lists all methods
grouped by entity domain, along with which side effects each triggers.
Page Effects
Section titled “Page Effects”| Method | Entity Type | Event Type | Analytics | Event Log | Embedding |
|---|---|---|---|---|---|
on_page_created | Page | Created | Yes | Yes | — |
on_page_updated | Page | Updated | — | Yes | — |
on_page_deleted | Page | Deleted | Yes | Yes | — |
on_page_moved | Page | Moved | — | Yes | — |
on_page_renamed | Page | Renamed | — | Yes | — |
Block Effects
Section titled “Block Effects”| Method | Entity Type | Event Type | Analytics | Event Log | Embedding |
|---|---|---|---|---|---|
on_block_content_saved | Block | Updated | — | Yes | Yes |
on_block_metadata_updated | Block | Updated | — | Yes | Yes |
on_block_area_changed | Block | Updated | — | Yes | Yes |
Workspace Effects
Section titled “Workspace Effects”| Method | Entity Type | Event Type | Analytics | Event Log | Embedding |
|---|---|---|---|---|---|
on_workspace_created | — | — | Yes | — | — |
on_workspace_opened | — | — | Yes | — | — |
Layout Effects
Section titled “Layout Effects”| Method | Entity Type | Event Type | Analytics | Event Log | Embedding |
|---|---|---|---|---|---|
on_layout_changed | — | — | — | — | — |
Layout changes are currently a no-op placeholder.
Tag Effects
Section titled “Tag Effects”| Method | Entity Type | Event Type | Analytics | Event Log | Embedding |
|---|---|---|---|---|---|
on_tag_created | Tag | Created | — | Yes | — |
on_tag_updated | Tag | Updated | — | Yes | — |
on_tag_deleted | Tag | Deleted | — | Yes | — |
on_tag_assigned_to_page | PageTag | Assigned | — | Yes | — |
on_tag_removed_from_page | PageTag | Removed | — | Yes | — |
on_page_tags_replaced | PageTag | Replaced | — | Yes | — |
Attachment Effects
Section titled “Attachment Effects”| Method | Entity Type | Event Type | Analytics | Event Log | Embedding |
|---|---|---|---|---|---|
on_attachment_uploaded | Attachment | Created | — | Yes | — |
on_attachment_deleted | Attachment | Deleted | — | Yes | — |
Type Definition Effects
Section titled “Type Definition Effects”| Method | Entity Type | Event Type | Analytics | Event Log | Embedding |
|---|---|---|---|---|---|
on_type_created | TypeDefinition | Created | — | Yes | — |
on_type_updated | TypeDefinition | Updated | — | Yes | — |
on_type_deleted | TypeDefinition | Deleted | — | Yes | — |
Property Definition Effects
Section titled “Property Definition Effects”| Method | Entity Type | Event Type | Analytics | Event Log | Embedding |
|---|---|---|---|---|---|
on_property_created | Property | Created | — | Yes | — |
on_property_updated | Property | Updated | — | Yes | — |
on_property_deleted | Property | Deleted | — | Yes | — |
Side-Effect Channels
Section titled “Side-Effect Channels”Analytics
Section titled “Analytics”Calls crate::analytics::track_event(state, event_name, None). Fire-and-forget with log-discard on error. Currently
triggered only by page create/delete and workspace create/open events.
Event Log
Section titled “Event Log”Calls RecordEventUseCase::execute() with entity type, entity ID, event type, and optional before/after values. The use
case writes to the event_log SQLite table. Fire-and-forget — the Result is discarded with let _ =.
Embedding
Section titled “Embedding”Sends an EmbeddingEvent::EmbedPage { page_id } to the EmbeddingTask via a bounded channel (try_send with capacity
256). If the channel is full, the event is silently dropped. Triggered only by block content mutations
(on_block_content_saved, on_block_metadata_updated, on_block_area_changed).
Error Handling
Section titled “Error Handling”The coordinator enforces a strict invariant: write operations are never rolled back due to side-effect failures.
- Analytics:
track_eventhandles its own errors internally. The coordinator does not inspect the result. - Event Log:
RecordEventUseCasereturns aResultthat is explicitly discarded (let _ = use_case.execute(...)). The use case logs errors internally. - Embedding:
try_sendreturnsErrif the channel is full. The error is discarded withlet _ =. The page is re-indexed on the next workspace open viaIndexWorkspace.
Task Runner Integration
Section titled “Task Runner Integration”The embedding side effect is backed by EmbeddingTask, which implements the EventDrivenTask trait from
infrastructure-task-runner.
Key behaviors:
- Bounded channel: Capacity 256 events. Overflow is silently dropped (backpressure via
try_send). - Deduplication: Batched events are collected into a
HashSet<Uuid>before processing, eliminating duplicate page IDs within a batch. - Debounce: 2-second delay (
DEBOUNCE_DELAY) before processing, allowing rapid successive saves to coalesce. - Blocking execution: Embedding computation runs on
tokio::task::spawn_blockingto avoid blocking the async runtime. - Status broadcasting: Bulk indexing progress is published via a
watchchannel asIndexingStatus(Idle orIndexing { completed, total }). - Two event types:
EmbedPage(single page re-embed after save) andIndexWorkspace(bulk index on workspace open or model upgrade).
Key Files
Section titled “Key Files”| File | Description |
|---|---|
apps/desktop/src-tauri/src/side_effects.rs | WriteEffectCoordinator struct (all public methods) |
apps/desktop/src-tauri/src/embedding.rs | EmbeddingTask (EventDrivenTask impl, debounce, batching) |
crates/application/src/event_log/record.rs | RecordEventUseCase (event log persistence) |
crates/domain/src/event_log.rs | EntityType, EventType domain enums |
crates/infrastructure/task-runner/src/lib.rs | EventDrivenTask trait and TaskRunner |
apps/desktop/src-tauri/src/commands/ | Tauri command handlers that call coordinator methods |
Related Documents
Section titled “Related Documents”Was this page helpful?
Thanks for your feedback!