docs(renderer): document rendering correctness
This commit is contained in:
parent
ca306ba8bf
commit
fc21b9d1bd
1 changed files with 80 additions and 0 deletions
|
|
@ -772,6 +772,86 @@ 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.
|
||||
|
||||
## Finding #13 — Rendering vs Syntax Styling
|
||||
|
||||
Milestone 3.1 corrects the first Markdown expansion from syntax-colored Markdown into meaning-oriented rendered lines. The editor still keeps one canonical Markdown source buffer in the native text view, but inactive lines now hide more structural syntax instead of only dimming it.
|
||||
|
||||
Previous behavior:
|
||||
|
||||
- Headings were styled with larger fonts while the leading `#` marker remained visible.
|
||||
- Bold, italic, and inline-code delimiters remained visible in inactive rendered lines.
|
||||
- Fenced code blocks colored the fence syntax, but the opening and closing fences still read as source text.
|
||||
- Task lists were detected by the renderer, but the inactive line did not show a distinct task state treatment.
|
||||
- Dirty rendering of lines inside fenced code blocks could lose block context when the dirty slice did not include the opening fence.
|
||||
|
||||
Corrected behavior:
|
||||
|
||||
- Inactive headings hide the heading marker and following separator while preserving heading level, spacing, and hierarchy.
|
||||
- Inline emphasis and code delimiters are hidden on inactive lines; the content keeps the bold, italic, or monospace treatment.
|
||||
- Fenced code blocks receive a dedicated monospace block treatment. Fence markers are hidden in rendered mode, and an opening-fence language label remains visible when present.
|
||||
- Task-list bullets are hidden. The `[x]` or `[ ]` token receives a visible state treatment, and completed task content is struck through.
|
||||
- Active lines always remain source text, so the same heading, task, code fence, or inline markup becomes editable Markdown when the cursor enters that line.
|
||||
|
||||
Render invalidation audit:
|
||||
|
||||
Initial rendering was not being skipped. Full renders still style every indexed line, and `EditorDirtyLineInvalidationTests.testInitialRenderTouchesEveryLine` covers that contract. The most visible heading case is the expected hybrid behavior when the heading is the active line: active lines intentionally show source. Moving focus away causes the line to become inactive and therefore rendered.
|
||||
|
||||
The real invalidation gap was block context for dirty slices. The renderer already understood fenced code blocks during full contiguous rendering, but a dirty update for only a code-content line could be planned without its surrounding fences. The fix adds contextual dirty rendering only when a dirty line is near a fence marker. That path asks `DocumentLineIndex` for enough source context to decide whether the dirty line is inside a fenced block, avoiding a document-wide refresh or unconditional full-source scan.
|
||||
|
||||
Rendering architecture:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Source["Native text storage source"] --> Index["DocumentLineIndex"]
|
||||
Index --> Dirty["Dirty line invalidation"]
|
||||
Dirty --> Context{"Fence nearby?"}
|
||||
Context -->|No| LinePlans["Line-only render plans"]
|
||||
Context -->|Yes| ContextPlans["Contextual code-block plans"]
|
||||
LinePlans --> Styler["MarkdownTextStyler"]
|
||||
ContextPlans --> Styler
|
||||
Styler --> Hidden["Hidden syntax attributes"]
|
||||
Styler --> Meaning["Rendered meaning attributes"]
|
||||
Hidden --> Source
|
||||
Meaning --> Source
|
||||
```
|
||||
|
||||
The correction is still source-preserving. Hidden syntax uses temporary text attributes, not string replacement. This keeps TextKit source offsets, cursor movement, selections, undo, and persistence aligned with the Markdown file.
|
||||
|
||||
Tradeoffs:
|
||||
|
||||
- Hidden delimiters still occupy a tiny layout footprint because the backing text remains present. This is intentional until replacement or overlay rendering is proven stable.
|
||||
- Task lists do not yet replace `[x]` and `[ ]` with true `☑` and `☐` glyphs. AppKit text attachments require replacement characters, which would break the source-offset model. The current rendered mode hides the list marker and styles the task-state token instead.
|
||||
- Code blocks are rendered as attributed text blocks, not independent block widgets. Padding is represented through paragraph and background styling.
|
||||
- Long-range dirty code-block context is resolved only when the dirty line is near fence syntax. This keeps normal typing and active-line switching cheap; broader block invalidation can be added later if editing fences inside very long code blocks exposes stale styling.
|
||||
- Blockquotes, lists, horizontal rules, tables, and links remain attributed renderings rather than custom layout objects.
|
||||
|
||||
Validation:
|
||||
|
||||
- Focused renderer tests cover heading marker hiding, active-line source preservation, inline delimiter hiding, task-state styling, code-fence hiding, and dirty code-block context recovery.
|
||||
- Full test suite on May 31, 2026: 58 XCTest cases passed.
|
||||
- Release benchmark on May 31, 2026:
|
||||
|
||||
| Scenario | Lines | Measured total | Render planning | Initial styling | Dirty typing render |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| sample document | 54 | 13.506 ms | 1.264 ms | 4.733 ms | 0.330 ms |
|
||||
| 2,100-line prototype | 2,101 | 423.835 ms | 88.610 ms | 98.276 ms | 0.368 ms |
|
||||
| 5 MB benchmark | 51,482 | 3,275.857 ms | 248.582 ms | 361.581 ms | 1.002 ms |
|
||||
|
||||
Tracked 5 MB interaction metrics:
|
||||
|
||||
| Operation | Time |
|
||||
| --- | ---: |
|
||||
| active-line lookup | 0.001 ms |
|
||||
| selection update | 0.003 ms |
|
||||
| dirty click invalidation | 0.001 ms |
|
||||
| typing state update | 0.116 ms |
|
||||
| dirty typing invalidation | 0.004 ms |
|
||||
| dirty typing render | 1.002 ms |
|
||||
|
||||
Conclusion:
|
||||
|
||||
Milestone 3.1 moves Sapling's inactive-line display closer to rendered Markdown while preserving the validated hybrid editing contract. The renderer now hides the most distracting source syntax for headings, inline markup, and fenced code blocks, and it gives task lists visible state. The remaining limitations are deliberate source-preserving tradeoffs rather than missed parser features.
|
||||
|
||||
## AttributedString and NSAttributedString
|
||||
|
||||
Swift `AttributedString` is useful for renderer-facing APIs and SwiftUI previews.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue