Skip to content

Indexing: scan-as-you-type pipeline (EI.3) - #9

Open
TheWhatDeep wants to merge 1 commit into
mainfrom
slice/index-loop
Open

Indexing: scan-as-you-type pipeline (EI.3)#9
TheWhatDeep wants to merge 1 commit into
mainfrom
slice/index-loop

Conversation

@TheWhatDeep

Copy link
Copy Markdown
Owner

Closes the editor→engine loop: typing a scene schedules a scan, the scan runs off the typing thread, and the records are written through the mentions service. This is the sole editor→engine path, and it carries data (text → mentions), never decisions — no continuity logic lives anywhere in it.

What's here

editor/indexScheduler.ts — debounce + forward. Coalesces keystrokes; skips redundant work when the revision is unchanged; re-runs when the scene changed mid-scan (latest content wins); cancels cleanly on scene delete; reports failures rather than throwing (typing must never break). Fully dependency-injected, so it's tested with fake timers and no editor, worker, or disk.

editor/indexWorker.ts + indexRunner.ts — off-thread scanning. The worker's only output is posting records back: it holds no handle to the editor and cannot trigger a render (S3.2 thread wall). It imports the same indexScene the tests exercise, so worker and test paths can't diverge. Degrades to in-process indexing if workers are unavailable — typing works either way (EI.3's constraint is that typing never blocks).

indexing.ts — the coordinator (injected, tested), including catchUp(): on project open it adopts cached records whose revision still matches, re-scans only what is stale (S1.6/D38 — a stale record is never trusted), and forgets cached scenes that no longer exist.

Wiring. Editor.svelte's onUpdate does extract + forward only (EI.3 audit item 5). App.svelte catches up after every open path and stops the pipeline on sign-out. ManuscriptTree forgets a deleted scene's — and a deleted chapter's scenes' — records immediately (the chapter case collects scene ids before deletion).

Bug found while testing (worth a look)

The mid-scan re-run originally chained on any revision mismatch. On a persistently failing scan nothing is committed, so the revision still looks un-indexed → infinite recursion. It hung the Vitest worker for 57s and crashed it. Fixed: a failure reports and stops, waiting for the next real change; only a successful commit chains. Both behaviours are now tested (retries on the next change after a failure, and the mid-flight re-run).

Also worth noting: one test initially failed because my stub read the revision when it resolved rather than when it was called — unrealistic. The stub now snapshots at call time, as the real indexer does. The scheduler was correct; the harness was lying.

Verified beyond unit tests

In the browser preview I dynamically imported IndexRunner and ran a real document through it. The worker genuinely starts under Vite and returned correctly-anchored records — Aria 1–5, Corin 10–15, Castle Raven 19–31 — which match the true ProseMirror positions. No worker or indexing errors in the console (only the pre-existing "Tauri unavailable in a browser" ones).

Reviewer notes

  • Nothing is visible in the app yet, by design. No UI creates entities, so the live lexicon is empty and the pipeline finds nothing. Passive mentions are also invisible on purpose (D36 — plain text while writing). The plumbing is complete; the payoff needs highlight-to-declare, which is the natural next slice.
  • Worker chunk is ~385 kB (it pulls in the ProseMirror schema so worker and main thread agree on positions). It's a lazily-loaded separate chunk, so it doesn't affect startup — but flagging the size in case you'd rather the worker build a minimal schema instead.
  • catchUp assumes a scene's cached records share one revision, which holds because a scene's set is always written as one unit (per the D18 storage shape).
  • No spec changes, no new decisions. No Rust touched.
  • The pre-existing unrelated src-tauri/Cargo.toml edit is deliberately left out, as in Indexer (Layer 2): name matching, mention store, and position-mapped anchoring #6Mentions: persist found names in the bundle (D18) #8.

Checks

vitest 133 (+22) · svelte-check 0 errors/0 warnings · eslint 0 · prettier clean · web build OK (worker emitted as its own chunk).

Closes the editor->engine loop: typing a scene now schedules a scan, the scan
runs off the typing thread, and the records are written through the mentions
service. This is the SOLE editor->engine path and it carries data, never
decisions — no continuity logic lives anywhere in it.

- editor/indexScheduler.ts: debounce + forward. Coalesces keystrokes, skips
  redundant work when the revision is unchanged, re-runs when the scene changed
  mid-scan, cancels cleanly on scene delete, and reports failures rather than
  throwing (typing must never break). Fully injectable, so it is tested with
  fake timers and no editor/worker/disk.
- editor/indexWorker.ts + indexRunner.ts: the scan runs in a Web Worker whose
  only output is posting records back — it holds no handle to the editor and
  cannot trigger a render (S3.2 thread wall). Degrades to in-process indexing if
  workers are unavailable, so typing works either way.
- indexing.ts: the pipeline coordinator (dependency-injected, tested), including
  catchUp() — on project open it adopts cached records whose revision still
  matches and re-scans only what is stale (S1.6/D38), and forgets cached scenes
  that no longer exist.
- Wiring: Editor.svelte onUpdate does extract+forward only; App.svelte catches
  up after every open and stops the pipeline on sign-out; ManuscriptTree forgets
  a deleted scene's (and a deleted chapter's scenes') records immediately.

Bug found and fixed while testing: the mid-scan re-run chained on any revision
mismatch, so a persistently failing scan recursed forever (nothing committed =>
still looks un-indexed) and hung the test worker. Failures now stop and wait for
the next change; only a successful commit chains.

Verified in the browser preview beyond unit tests: the worker really does start
under Vite and returned correctly-anchored records for a real document
("Aria" 1-5, "Corin" 10-15, "Castle Raven" 19-31), with no worker or indexing
errors in the console.

Note: no UI creates entities yet, so the live lexicon is empty and the pipeline
will find nothing in the app until highlight-to-declare lands. The plumbing is
complete and correct; the payoff needs that next slice.

Tests: vitest 133 (+22). No Rust touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant