Skip to content

IPC & Sync Triggers

We do not stream granular keystrokes or ephemeral memory state over the NDJSON IPC bridge. The bridge is reserved for meaningful state synchronization.

Sync Boundaries

C# holds the active paragraph in its local memory. It only sends an updated block payload to Nim at deliberate moments to keep the backend lightweight.

  • Triggers: A sync boundary occurs when the user stops typing for a few seconds, presses Enter, clicks away, or triggers a structural command.
  • Block-Level Splicing: Standard typing/editing is contained within a single block. For structural mutations (bulk deletions/pastes), an array splice command minimizes the payload. By abandoning nested DOM trees, edits become simple string replacements or array splices.

Optimistic UI & Eventual Consistency

To prevent state drift without sacrificing typing latency (for things like live word counts):

  1. Optimistic UI: C# calculates the live word count instantly on every keystroke and updates the screen.
  2. Authoritative Reconciliation: When a sync boundary is reached, C# sends the block to Nim. Nim updates the master document and replies with an ACK containing the true, canonical document statistics.
  3. Silent Correction: C# silently updates its UI counter with Nim's true count. If there was a drift, it snaps to the correct number instantly.

IPC Protocol: Envelope vs. Letter

To maintain a clean bridge between statically typed C# and Nim, the NDJSON IPC structure strictly separates the "envelope" from the "letter".

  • The Split: The IPC bridge is divided into the "Envelope" (the router/dispatcher that parses message_id and action) and the "Letter" (the strongly-typed domain structs like BlockUpdate).
  • Payload Separation: To future-proof for automated code generation, IPC payload structures must live in isolated files (e.g., ipc_payloads.nim or Core/IPC/Payloads/) and remain pure, flat data containers with zero business logic.

The Engine Dispatcher (engine.nim / dispatcher.nim)

  • The core engine file perfectly implements the Envelope/Letter separation.
  • The dispatcher() loop reads stdin and parses the generic envelope. It uses templates (adapt*[T]) to parse the inner JSON into strongly typed domain structs before passing them to the domain handlers.
  • Because of this design, the dispatcher itself doesn't need to change much when adding new features.