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.
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!