From c9afdcaa84b00544b6d637344db40a4fc0309847 Mon Sep 17 00:00:00 2001 From: Feror Date: Sun, 31 May 2026 20:54:13 +0200 Subject: [PATCH] docs(editor): document rendering expansion --- Docs/editor-investigation.md | 99 ++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/Docs/editor-investigation.md b/Docs/editor-investigation.md index 89416c6..ecaf369 100644 --- a/Docs/editor-investigation.md +++ b/Docs/editor-investigation.md @@ -673,6 +673,105 @@ Conclusion: The main Milestone 2.8 Sapling-side interaction bottlenecks have been removed from the measured 5 MB typing and selection paths. After this change, the dominant measured costs are TextKit full/cold layout and full-document initial rendering/planning work, not active-line tracking or dirty invalidation. +## Finding #12 — Markdown Rendering Expansion + +Milestone 3 expands the attributed hybrid renderer from the Milestone 2 proof of concept into a more complete Markdown writing surface. The implementation still keeps one canonical source buffer inside the native text view. It does not replace Markdown source with rendered strings, and it does not introduce overlay views or a custom editor engine. + +Supported rendering plans now cover: + +- ATX headings +- bold and italic emphasis using asterisks or underscores +- inline code +- blockquotes +- horizontal rules +- unordered lists +- ordered lists +- nested list indentation +- task lists +- Markdown links +- automatic links +- fenced code blocks with language labels +- Markdown table rows and divider rows + +Architecture: + +```mermaid +flowchart TD + TextKit["NSTextView / UITextView source buffer"] --> Index["DocumentLineIndex"] + Index --> Lines["EditorLine values"] + Lines --> Parser["HybridMarkdownLineRenderer"] + Parser --> Plans["HybridMarkdownLineRenderPlan"] + Plans --> Styler["MarkdownTextStyler"] + Styler --> Attributes["NSTextStorage attributes"] + Attributes --> TextKit +``` + +`HybridMarkdownLineRenderer` remains a line-oriented parser. It emits line kinds and inline spans with `NSRange` values in source-buffer coordinates. `MarkdownTextStyler` applies fonts, colors, paragraph styles, background treatments, underlines, and strikethroughs to those source ranges. + +The active line continues to receive source styling. Inactive lines receive rendered-style attributes. Because the actual text is not replaced, cursor locations, selections, undo, TextKit layout ranges, dirty-line invalidation, and persistence all remain tied to the same source string. + +Tradeoffs: + +- Markdown delimiters are visually de-emphasized rather than removed. This is less visually complete than Typora-style replacement, but it avoids source/render offset mapping. +- Task lists show checkbox state with styling on `[ ]` and `[x]`; interactive toggling is deferred. +- Horizontal rules are represented with attributed separator styling over the source marker instead of a true drawn rule. +- Tables use monospaced attributed rendering for readability. They do not yet become independent grid views. +- Fenced code block contents are styled when render plans are produced with contiguous line context. Dirty-line rendering avoids document-wide context scans, so long code-block context remains conservative around local edits. +- Markdown links and automatic links are visually distinct. Full click handling is deferred unless it can be added without destabilizing editing. +- Reference-style link definitions remain unsupported in the hybrid renderer. The 5 MB benchmark is dominated by reference-style definitions, so automatic-link parsing intentionally skips those lines for now. + +Scalability impact: + +The expanded renderer was validated with: + +- sample document benchmark +- `Docs/EditorPrototypes/hybrid-large-2100.md` +- `Docs/Benchmarks/5mb.md` + +Release benchmark run on May 31, 2026 after parser guards: + +| Scenario | Lines | Measured total | Render planning | Initial styling | Dirty typing render | +| --- | ---: | ---: | ---: | ---: | ---: | +| sample document | 54 | 10.151 ms | 1.005 ms | 3.941 ms | 0.265 ms | +| 2,100-line prototype | 2,101 | 413.814 ms | 88.227 ms | 96.668 ms | 0.369 ms | +| 5 MB benchmark | 51,482 | 3,157.751 ms | 244.277 ms | 371.099 ms | 0.985 ms | + +Tracked 5 MB interaction metrics remained within the Milestone 2.9 responsiveness envelope: + +| Operation | Time | +| --- | ---: | +| active-line lookup | 0.000 ms | +| selection update | 0.002 ms | +| dirty click invalidation | 0.001 ms | +| typing state update | 0.189 ms | +| dirty typing invalidation | 0.003 ms | +| dirty typing render | 0.985 ms | + +The main remaining costs are still full/cold TextKit layout and initial full-document work, not active-line switching or dirty edit rendering. Parser guards were necessary because the first expanded renderer performed too much per-line Markdown detection on the 5 MB reference-definition benchmark. After guarding by first token and skipping reference-style definitions for automatic-link parsing, the measured 5 MB total remained below the Milestone 2.9 reported baseline. + +Validation fixtures: + +- `Docs/EditorPrototypes/markdown-technical-documentation.md` +- `Docs/EditorPrototypes/markdown-meeting-notes.md` +- `Docs/EditorPrototypes/markdown-research-notes.md` +- `Docs/EditorPrototypes/markdown-project-plan.md` +- `Docs/EditorPrototypes/markdown-long-form-writing.md` + +Unsupported elements explicitly deferred: + +- images +- wikilinks +- Mermaid +- LaTeX +- callouts +- attachments +- audio +- video + +Conclusion: + +Sapling now renders enough standard Markdown structure for documentation, technical notes, project notes, meeting notes, and long-form writing to feel substantially closer to a real Markdown editor while preserving the validated hybrid editing model. The renderer remains deliberately attributed and source-preserving until richer block layout can be proven without harming cursor stability, selection stability, typing latency, or scrolling behavior. + ## AttributedString and NSAttributedString Swift `AttributedString` is useful for renderer-facing APIs and SwiftUI previews.