PERF: reuse Note reminder decorations on selection-only editor updates (#663) #749

Open
opened 2026-10-02 13:08:49 +00:00 by kayg · 3 comments
Owner

Parent: #663. Related: #701 (Notes adoption), #641 (traversal), DESIGN §42 (block reminders). This is code evidence, not a measured millisecond regression.

Source
origin/dev c4a61e8cf0; the implementation is unchanged on merge-round-7a 2f4482ded0.
apps/web/src/lib/notes/editorHost.ts:115–175, blockReminderDecorations(): props.decorations(state) builds a new byBlock map, calls state.doc.descendants(), builds new chip DOM, and calls DecorationSet.create() each time. It has no document identity or reminder revision guard and no stored DecorationSet in plugin state. With no reminders it exits early; this finding applies to Notes with at least one reminder.

Reasoned impact
A selection-only update uses the same document but still walks it. Each editor decoration query can visit every unreminded block and allocate replacement chips for reminders. Typing and caret moves therefore pay work proportional to Note size, even when reminders are unchanged. The callback does not stop after the last reminder was found. Unrelated selection updates need no new reminder content. This conflicts with #663 rule 8 and the editor keystroke budget in DESIGN §18; the audit does not claim that budget has been measured or exceeded.

Concrete fix
Keep one DecorationSet in ProseMirror plugin state. Return it for selection-only transactions. Map it through transaction mappings for edits; rebuild only when the reminder revision, required block structure or time-zone formatting changes. Reuse the existing reminder input and widget keys. Keep decorations outside Markdown and Yjs content. Do not change reminder behavior or introduce a second reminder store.

Acceptance tests
Use a 10k-block Note with sparse reminders. Repeated selection-only transactions must reuse the set and perform zero full-document reminder scans. Edits before a reminder must move its widget; a block delete must remove it; reminder changes and time-zone changes must refresh its text. No reminder decoration may alter saved Markdown or Undo. Extend #641/#549 to compare caret movement and typing with and without reminders in production, with at least five locked samples.

Duplicate search
Searched all issue titles for reminder, decoration, grammar and editor performance; no issue describes this selection-only scan. Keep this as a focused editor-runtime slice under the Notes owner. No product edits were made in the audit.

Parent: #663. Related: #701 (Notes adoption), #641 (traversal), DESIGN §42 (block reminders). This is code evidence, not a measured millisecond regression. Source origin/dev c4a61e8cf090170f35b1bed3350d9de20c83ecd5; the implementation is unchanged on merge-round-7a 2f4482ded066d9c5d9c59130377907f7fd2916c9. apps/web/src/lib/notes/editorHost.ts:115–175, blockReminderDecorations(): props.decorations(state) builds a new byBlock map, calls state.doc.descendants(), builds new chip DOM, and calls DecorationSet.create() each time. It has no document identity or reminder revision guard and no stored DecorationSet in plugin state. With no reminders it exits early; this finding applies to Notes with at least one reminder. Reasoned impact A selection-only update uses the same document but still walks it. Each editor decoration query can visit every unreminded block and allocate replacement chips for reminders. Typing and caret moves therefore pay work proportional to Note size, even when reminders are unchanged. The callback does not stop after the last reminder was found. Unrelated selection updates need no new reminder content. This conflicts with #663 rule 8 and the editor keystroke budget in DESIGN §18; the audit does not claim that budget has been measured or exceeded. Concrete fix Keep one DecorationSet in ProseMirror plugin state. Return it for selection-only transactions. Map it through transaction mappings for edits; rebuild only when the reminder revision, required block structure or time-zone formatting changes. Reuse the existing reminder input and widget keys. Keep decorations outside Markdown and Yjs content. Do not change reminder behavior or introduce a second reminder store. Acceptance tests Use a 10k-block Note with sparse reminders. Repeated selection-only transactions must reuse the set and perform zero full-document reminder scans. Edits before a reminder must move its widget; a block delete must remove it; reminder changes and time-zone changes must refresh its text. No reminder decoration may alter saved Markdown or Undo. Extend #641/#549 to compare caret movement and typing with and without reminders in production, with at least five locked samples. Duplicate search Searched all issue titles for reminder, decoration, grammar and editor performance; no issue describes this selection-only scan. Keep this as a focused editor-runtime slice under the Notes owner. No product edits were made in the audit.
Author
Owner

Starting work on job/notesperf, based on job/merge-round-7a at 2f4482ded066d9c5d9c59130377907f7fd2916c9. I will address the issue with focused changes and regression coverage, then report the final head SHA and verbatim gate output here.

Starting work on `job/notesperf`, based on `job/merge-round-7a` at `2f4482ded066d9c5d9c59130377907f7fd2916c9`. I will address the issue with focused changes and regression coverage, then report the final head SHA and verbatim gate output here.
Author
Owner

Evidence from the old code: the new 10,000-block collaboration test counted two reminder reads during setup, then 20 selection-only transactions raised the count to 22. The old props.decorations callback rebuilt its map and walked the document each time. The plugin now caches a DecorationSet, returns it for selection-only transactions, maps it on edits, and validates only mapped reminder widgets.

Evidence from the old code: the new 10,000-block collaboration test counted two reminder reads during setup, then 20 selection-only transactions raised the count to 22. The old `props.decorations` callback rebuilt its map and walked the document each time. The plugin now caches a `DecorationSet`, returns it for selection-only transactions, maps it on edits, and validates only mapped reminder widgets.
Author
Owner

Implemented in commit 25232cd10: cache and map reminder decorations across caret moves and text transactions; the regression test exercises a 10,000-block document. Full web suite had 1,070 passes and five unrelated 5-second timeouts (details on #747). Screenshot validation was blocked by server startup under shared-host load.

Implemented in commit 25232cd10: cache and map reminder decorations across caret moves and text transactions; the regression test exercises a 10,000-block document. Full web suite had 1,070 passes and five unrelated 5-second timeouts (details on #747). Screenshot validation was blocked by server startup under shared-host load.
Sign in to join this conversation.
No labels
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
kayg/calternal#749
No description provided.