perf(grammar): check only the paragraphs that changed #829

Merged
raisondetredev merged 1 commit from refs/pull/829/head into main 2026-08-08 20:30:51 +02:00
raisondetredev commented 2026-08-08 20:30:33 +02:00 (Migrated from codeberg.org)

A check walked the whole document and posted every segment, one await
after another, on every pause in typing. A ten-paragraph note therefore
cost ten sequential round trips through the Edge Function to LanguageTool
for a single edited character — nine of them re-asking about text nobody
had touched. The wait scaled with the size of the note rather than the
size of the edit, which is the wrong axis: the longer a note grows, the
more it stalls on each pause.

Matches are now cached per exact segment text. LanguageTool is a pure
function of (text, language) and the language is fixed for a controller's
lifetime, so an untouched paragraph produces a byte-identical key and its
previous verdict stands. Typing costs one request. Two identical
paragraphs in one document cost one between them. The cache is rebuilt
from the segments each completed run actually saw, so it can't grow across
a long editing session, and a failed segment is deliberately never
recorded — it has to retry.

Offsets survive the reuse because the key IS the text the offsets index;
a cached match maps onto whatever path that paragraph sits at now, even if
it moved.

Runs no longer overlap. The idle timer, the blur flush and the
attach-time checkNow could each start a full pass over the same
document, and a blur landing just after the timer fired doubled the
traffic outright. A pass now runs alone, with at most one rerun coalesced
behind it.

The overlay stops rebuilding for nothing. _recompute ran setState
unconditionally — on every controller notification and once per frame
while the editor scrolls — rebuilding one GestureDetector per squiggle
even when the geometry was identical. It now compares first. The
comparison includes the controller status, not just the rects: the build
reads status for the "checked, no issues" checkmark, and only a
recompute can rebuild this widget, so a status change with unchanged
geometry still has to get through.

Three spans are traced (grammar.segment, grammar.request,
grammar.overlay.recompute) behind the existing HALKYON_PERF_TRACE
define, so what remains of a stall can be attributed rather than guessed
at.

Signed-off-by: Tizian 「ティツィアーン」 raisondetredev@pm.me

A check walked the whole document and posted every segment, one `await` after another, on every pause in typing. A ten-paragraph note therefore cost ten sequential round trips through the Edge Function to LanguageTool for a single edited character — nine of them re-asking about text nobody had touched. The wait scaled with the size of the note rather than the size of the edit, which is the wrong axis: the longer a note grows, the more it stalls on each pause. Matches are now cached per exact segment text. LanguageTool is a pure function of (text, language) and the language is fixed for a controller's lifetime, so an untouched paragraph produces a byte-identical key and its previous verdict stands. Typing costs one request. Two identical paragraphs in one document cost one between them. The cache is rebuilt from the segments each completed run actually saw, so it can't grow across a long editing session, and a failed segment is deliberately never recorded — it has to retry. Offsets survive the reuse because the key IS the text the offsets index; a cached match maps onto whatever path that paragraph sits at now, even if it moved. Runs no longer overlap. The idle timer, the blur flush and the attach-time `checkNow` could each start a full pass over the same document, and a blur landing just after the timer fired doubled the traffic outright. A pass now runs alone, with at most one rerun coalesced behind it. The overlay stops rebuilding for nothing. `_recompute` ran `setState` unconditionally — on every controller notification and once per frame while the editor scrolls — rebuilding one `GestureDetector` per squiggle even when the geometry was identical. It now compares first. The comparison includes the controller status, not just the rects: the build reads `status` for the "checked, no issues" checkmark, and only a recompute can rebuild this widget, so a status change with unchanged geometry still has to get through. Three spans are traced (`grammar.segment`, `grammar.request`, `grammar.overlay.recompute`) behind the existing `HALKYON_PERF_TRACE` define, so what remains of a stall can be attributed rather than guessed at. Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
project-halkyon/halkyon-learn!829
No description provided.