Skip to content

T-LNJA-md-engine-large-file-guard

State: closed/done · Impact: high · Complexity: small

A very large note in live mode rebuilds decorations it can never afford, and nothing stops a host from opening one that way. This task adds the large-file guard: sourceModeThreshold, initialMode forcing 'source' with modeForced: 'large-file', a setMode refusal, and decoration layers that stand down over the threshold.

LocationRole today
packages/ts/obsidian-editor/src/state.tsmodeFacet, getMode, setMode; no size threshold and no forced-mode state
packages/ts/obsidian-editor/src/types.ts#ObsidianEditorOptionsmode?: EditorMode; no sourceModeThreshold
packages/ts/obsidian-editor/src/decorations.tsDecoration layers run regardless of document size
packages/ts/obsidian-editor/src/vue/index.ts#useObsidianEditorNo mode or modeForced refs
packages/ts/obsidian-editor/test/mode.test.tsSource/live switch tests, the base the guard tests extend

The catalogue’s editor.toggle-mode comes from T-EXQ7-md-engine-command-catalogue; the bench from T-0GV4-md-engine-line-endings-corpus.

  • sourceModeThreshold?: number on ObsidianEditorOptions, default 600 000 — the UTF-8 byte length of the document.
  • docBytesField: StateField<number> computes the byte length once at creation and updates incrementally from change lengths, never rescanning the document per transaction.
  • initialMode(doc, options): { mode: EditorMode; modeForced: 'large-file' | null } — over the threshold the mode is 'source' and modeForced is 'large-file' regardless of options.mode. obsidianEditorExtensions uses it; getModeForced(state) reads the forced state back.
  • setMode(view, 'live') returns false and leaves the mode 'source' while the document is over the threshold; the refusal clears once edits bring it under.
  • buildInlineDecorations and the block widget field return Decoration.none while over the threshold, so even a host that forces a reconfigure gets no decoration cost.
  • The catalogue’s editor.toggle-mode reports enabled: false while modeForced is set — the chrome mode toggle disables Live with a tooltip off this state.
  • useObsidianEditor returns mode (effective, recomputed on reconfigure and change) and modeForced shallow refs.
  • The bench gains an open-600k-source case so opening at the threshold is baselined and gated like every other measure; the browser-level 500 ms open budget is proven by T-I630-md-ui-playground-e2e-perf.
  1. Extend packages/ts/obsidian-editor/test/mode.test.ts first: a 600 001-byte document opens in 'source' with getModeForced(state) === 'large-file'; setMode(view, 'live') returns false and getMode stays 'source'; a 599 999-byte document opens per options.mode and switches freely; deleting half the large document then setMode(view, 'live') succeeds.
  2. Add a decoration case: over the threshold, buildInlineDecorations and the block field produce zero decorations.
  3. Implement docBytesField, initialMode, getModeForced, the setMode refusal and the threshold option; assert docBytesField equals the byte length of state.sliceDoc() after an insert, a delete, and over the CRLF corpus fixture.
  4. Wire editor.toggle-mode’s isEnabled to the forced state; extend the catalogue test.
  5. Extend useObsidianEditor (mode, modeForced) and the Vue test.
  6. Add the open-600k-source bench case; re-record the baseline entry for it only; bun test, tsc --noEmit, moon run obsidian-editor:bench.
LocationKindChange
packages/ts/obsidian-editor/src/state.tsmodifydocBytesField, initialMode, getModeForced, setMode refusal
packages/ts/obsidian-editor/src/types.tsmodifysourceModeThreshold?: number
packages/ts/obsidian-editor/src/decorations.tsmodifyDecoration.none over the threshold
packages/ts/obsidian-editor/src/commands/catalogue.tsnewCreated by T-EXQ7-md-engine-command-catalogue; editor.toggle-mode disabled while forced
packages/ts/obsidian-editor/src/vue/index.tsmodifymode and modeForced shallow refs
packages/ts/obsidian-editor/src/index.tsmodifyExport initialMode, getModeForced, docBytesField
packages/ts/obsidian-editor/test/mode.test.tsmodifyGuard, refusal and byte-field tests
packages/ts/obsidian-editor/test/vue.test.tsmodifymode / modeForced refs
packages/ts/obsidian-editor/bench/run.tsnewCreated by T-0GV4-md-engine-line-endings-corpus; gains the open-600k-source case
packages/ts/obsidian-editor/bench/baseline.jsonnewCreated by T-0GV4-md-engine-line-endings-corpus; entry for the new case
  • AC-1: bun test packages/ts/obsidian-editor passes, including the extended mode and Vue suites.
  • AC-2: a 600 001-byte document opens in 'source' with getModeForced(state) === 'large-file'; setMode(view, 'live') returns false and getMode(view.state) stays 'source'; a 599 999-byte document is unaffected.
  • AC-3: over the threshold, buildInlineDecorations and the block widget field return zero decorations.
  • AC-4: commandStates(state)['editor.toggle-mode'].enabled is false while forced and true otherwise.
  • AC-5: docBytesField equals the UTF-8 byte length of state.sliceDoc() after each of the three edits in the test (insert, delete, CRLF fixture load).
  • AC-6: useObsidianEditor returns mode and modeForced; the Vue test proves modeForced is 'large-file' for an over-threshold document and null after edits shrink it.
  • AC-7: moon run obsidian-editor:bench passes with the open-600k-source case recorded in bench/baseline.json and gated at 1.5× like the rest.
  • The chrome mode toggle’s disabled state and tooltip (T-A0HT-md-ui-layer-skeleton’s MdModeToggle reads modeForced).
  • The read mode; 'read' is a chrome-level mode outside the engine’s EditorMode.
  • The browser-level open-time budget (500 ms for a 600 KB note), proven by T-I630-md-ui-playground-e2e-perf.

Member of milestone M-931L; engine step E10 of decision D-K3EM-markdown-editor-chrome-and-plugin-environment.

T-EXQ7-md-engine-command-catalogue


← Back to Tasks