Files
Marcos Paulo b32650767c
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
docs: specs and briefs for smart folders, version diff, media timestamps, clean paste, link cards, spaced repetition
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 12:18:00 -03:00

6.0 KiB

Note visual diff and time-machine slider (agy gap 21, roadmap 27)

Part of specs/plugins-parity/. Extends the existing versions tab (crates/ui-egui/src/views/versions_view/) and history.* engine commands. One new small pure crate notescraft-textdiff.

Research (2026-10-21; agy claims checked)

Problem

The versions tab lists snapshots with Compare and Restore; Compare opens a unified LINE diff (views/diff_view/compare), which is noisy for prose (one changed word flags a whole paragraph) and needs one click per snapshot.

Goal

In the versions tab, a horizontal slider (time machine) over "oldest snapshot ... now". Dragging selects a snapshot; the pane below shows the WORD-level diff of that snapshot against the next newer one (default) or against now (toggle), additions green, deletions red strikethrough, reading as prose. One button restores the selected snapshot (existing undoable Restore).

Non-goals (v1)

Per-hunk revert; git commits and server revisions as slider stops (Feature ideas 1, 2); changing snapshot retention (Q1); diff of non-text files.

Design decisions

D1 Pure crate notescraft-textdiff (L0, crates/textdiff)

pub enum Op { Same, Add, Del }
pub struct Span { pub op: Op, pub text: String }
pub struct Limits { pub max_bytes: usize /*2 MiB*/, pub max_tokens: usize /*40_000*/, pub max_cells: usize /*16_000_000*/ }
pub fn word_diff(old: &str, new: &str, limits: &Limits) -> Result<Vec<Span>>;   // Err(TooLarge) beyond limits, never truncates silently
pub fn stats(spans: &[Span]) -> (usize, usize);   // added words, removed words

Tokens: runs of word chars, whitespace runs, single punctuation. Algorithm: strip common prefix and suffix tokens, then Myers O(ND) with a cell cap; adjacent same-op spans merged. Concatenating Same+Del reproduces old, Same+Add reproduces new (property test). Own code (about 200 lines) because the similar crate is not approved: Q2.

D2 Engine

Add history.diff to crates/engine/src/commands.rs next to history.list:

id params result
history.diff {path, id, against?: <snapshot id> or "current"} (default "current") {path, id, against, adds, dels, spans:[{op:"same"|"add"|"del", text}]}, spans capped at 5 000 (field truncated)
history.list stays; snapshot id already is the Unix-ms timestamp, so no new field. Add history.diff to READ in ai/assistant_gate.rs and to CORE_IDS. Depends on notescraft-textdiff in crates/engine/Cargo.toml.

D3 UI

views/versions_view/slider.rs: draws a track with one tick per snapshot (max 20 + "Now"), spacing proportional to time, hover tooltip with the local time, keyboard left/right. views/versions_view/word_diff.rs: paints spans as a wrapped rich text (green background tokens.theme.colors success tone at low alpha; deletions danger + strikethrough). Diff computed on a worker thread per selection change (pattern of share_dialog/mod.rs), debounced 120 ms while dragging, result cached by (id, against). The existing rows list stays below as the accessible alternative. Strings: [ui.versions] additions appended in the existing versions table file crates/ui-egui/src/text/versions_view.rs (use its pattern; new keys at the END of that table).

D4 Safety

Snapshots are local files written by the app: still bounded by Limits. A too-large pair shows "Too large to compare, use the list" and keeps Compare (line diff) working.

Acceptance criteria

  1. Slider shows N snapshots + Now with ticks ordered by time; dragging to a tick updates the diff within one frame of the worker result.
  2. A one-word change in a 2 000-word paragraph shows exactly one red and one green word, not the paragraph.
  3. Concatenation property: Same+Del == old and Same+Add == new for 1 000 random pairs.
  4. word_diff of two 2 MiB texts returns within 500 ms or TooLarge; no panics on empty input, only whitespace, emoji, CRLF.
  5. Restore from the slider is ONE undo step; body of other notes untouched.
  6. Dragging makes zero engine calls on the UI thread (worker only), and at most one call per 120 ms.

Open questions

  • Q1: snapshots keep only 20, at least 5 minutes apart (vault/snapshots/mod.rs KEEP, MIN_GAP_MS), so the slider spans about 1.7 h of active editing at the densest. Raise KEEP (setting) or leave? Proposal: setting files.history_keep default 50.
  • Q2: own diff code (proposed) or add the similar crate (MIT, widely used)? New dependency needs owner approval.

Feature ideas (researched, ranked)

  1. Git commits as extra slider stops when the vault is a repo: crates/git already has log and diff; Time Machine plugin merges both sources.
  2. Server revisions (share/history.rs) as stops with another colour.
  3. Per-hunk Keep/Revert (Review Edit): turn spans into hunks, apply one hunk to the buffer.
  4. Side by side layout reusing diff_view/split.rs; 5. "Who changed" author colours from live/collab sessions; 6. Export diff as markdown with {++add++}{--del--} CriticMarkup to share.