Timeline Reconstruction
chronicle advanced 6 min read
ELI5
Scrubbing the timeline is like asking the librarian “show me the book as it looked at 3:14 pm”. The librarian replays every sticky-note operation up to that moment, sorted by their LSEQ shelf coordinate, skipping the ones marked deleted, and hands you the resulting page.
Technical Deep Dive
Two-Channel Design (ADR-006)
| Concern | Request | Response |
|---|---|---|
| List of edit events for the UI | timeline_metadata_request | timeline_metadata |
| Document text at a specific moment | timeline_query | timeline_snapshot |
Splitting metadata and content lets the UI render the timeline strip from a small payload while only fetching the heavyweight content when the user actually pauses on a moment.
Content Reconstruction
flowchart LR Q[timeline_query target_hlc] --> Fetch[SELECT operations WHERE document_id = ? AND hlc_timestamp <= target_hlc ORDER BY hlc_timestamp, hlc_counter] Fetch --> Map[BTreeMap position_id → char,deleted] Map --> Apply{op.type} Apply -- insert --> Ins[map.insert position_id → content,false] Apply -- delete --> Del[map.get_mut → mark deleted=true] Apply -- format/retain --> Skip[ignored] Ins --> Build[Build content from sorted position_ids, skipping deleted] Del --> Build Build --> Resp[TimelineSnapshotMsg actual_hlc=last_op.hlc, content, operation_count]actual_hlc may differ from target_hlc when no operation exists at the exact timestamp — it is the HLC of the last operation included in the replay.
Metadata Aggregation
const BUCKET_MS: i64 = 5000; // 5-second bucketsOperations group by (author_id, hlc_wall_time / BUCKET_MS). Each bucket becomes one TimelineEntry { hlc_timestamp, operation_count, author_id, author_name, summary? }. Entries are sorted descending and truncated to limit. Summaries are short human strings (“Inserted ‘Hello world’”, “Deleted 15 chars”).
End-to-End
sequenceDiagram autonumber participant UI as TimelineStore participant Hook as useTimeline participant WS participant Relay participant DB UI->>Hook: enable timeline (mount) Hook->>WS: timeline_metadata_request { limit: 1000 } WS->>Relay: forward Relay->>DB: SELECT ... ORDER BY hlc_timestamp DB-->>Relay: rows Relay-->>WS: timeline_metadata WS-->>Hook: entries UI->>UI: user scrubs to hlc=T UI->>Hook: goToTimestamp(T) Hook->>Hook: contentCacheRef miss Hook->>WS: timeline_query { target_hlc: T } WS->>Relay: forward Relay->>DB: SELECT ops WHERE hlc_timestamp <= T Relay->>Relay: reconstruct_content_from_operations Relay-->>WS: timeline_snapshot { actual_hlc, content } WS-->>Hook: cache + onTimeChangeClient Caching Layers
contentCacheRef— in-memoryMap<hlc, content>for snapshots already viewed in this session.TimelineStore.snapshots— coarser periodic snapshots that allow local replay.- Relay fallback — for any uncached HLC, the round-trip above.
Key Terms
- actual_hlc → the HLC of the latest operation ≤ target_hlc; sent back so the client can label “you are viewing the document as of T₀”.
- 5-second bucket →
BUCKET_MS = 5000in the relay aggregation; tuned to keep timeline strips readable at typing cadence. - operation_count (snapshot) → number of ops included in this reconstruction; lets the UI show “47 edits up to here”.
Q&A
Q: Does timeline reconstruction respect tombstones?
A: Yes. delete ops mark the entry’s deleted flag; the final filter drops them before string assembly.
Q: Why a BTreeMap and not a Vec sort?
A: BTreeMap keyed on position_id keeps insertion order consistent under repeated mutation and lets the same map serve both insert and delete updates without re-sorting.
Q: What if many operations share the same wall time bucket?
A: They are merged into a single TimelineEntry per (author, bucket) regardless of count — the operation_count field tells you how dense it was.
Examples
User scrubs to T = 1700000050000. Relay loads ops with hlc_timestamp <= T, replays into a BTreeMap, returns content="Hello" and actual_hlc = 1700000049820 (last op before T). The client caches (T, "Hello") so dragging back to T is instant.
neighbors on the map
- Operations & Versions Schema writing a new sync query
- CRDT Operation Message adding a new operation type
- LORE Causality Graph & Decision Visualization understanding how decisions relate to each other