test(renderer): add markdown rendering samples
This commit is contained in:
parent
a0407f9704
commit
d013b5328c
7 changed files with 340 additions and 1 deletions
|
|
@ -8,5 +8,12 @@ These documents support Milestone 2 hybrid editor validation.
|
|||
- `hybrid-stress-1000.md`: Milestone 2.5 stress document for repeatable editor measurements.
|
||||
- `hybrid-stress-5000.md`: Milestone 2.5 stress document for repeatable editor measurements.
|
||||
- `hybrid-stress-10000.md`: Milestone 2.5 stress document for repeatable editor measurements.
|
||||
- `markdown-technical-documentation.md`: Milestone 3 documentation-shaped Markdown rendering sample.
|
||||
- `markdown-meeting-notes.md`: Milestone 3 meeting-notes rendering sample.
|
||||
- `markdown-research-notes.md`: Milestone 3 research-notes rendering sample.
|
||||
- `markdown-project-plan.md`: Milestone 3 project-plan rendering sample.
|
||||
- `markdown-long-form-writing.md`: Milestone 3 long-form prose rendering sample.
|
||||
|
||||
They intentionally use only headings, bold, italic, and inline code because Milestone 2 validates editor architecture before full Markdown rendering.
|
||||
The original Milestone 2 documents intentionally use only headings, bold, italic, and inline code because Milestone 2 validated editor architecture before full Markdown rendering.
|
||||
|
||||
The Milestone 3 samples intentionally exercise headings, emphasis, inline code, blockquotes, horizontal rules, unordered and ordered lists, nested lists, task lists, Markdown links, automatic links, fenced code blocks, and tables. They avoid images, wikilinks, Mermaid, LaTeX, callouts, attachments, audio, and video because those are deferred rendering milestones.
|
||||
|
|
|
|||
53
Docs/EditorPrototypes/markdown-long-form-writing.md
Normal file
53
Docs/EditorPrototypes/markdown-long-form-writing.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# A Quiet Writing Surface
|
||||
|
||||
## Opening
|
||||
|
||||
Long-form writing needs structure, but it also needs restraint. A document should provide landmarks without turning every line into a widget. Headings, lists, quotes, and code blocks are enough to make many real notes pleasant.
|
||||
|
||||
The editor still carries the original Markdown source. That matters because every cursor movement, selection, undo operation, and file save depends on the source staying stable.
|
||||
|
||||
## Section One
|
||||
|
||||
The first test is visual rhythm. A heading should make the next section easy to find. A paragraph with **important language** and *subtle emphasis* should remain readable. A phrase like `DocumentLineIndex` should stand out without interrupting the sentence.
|
||||
|
||||
> A good hybrid editor makes structure visible while keeping source nearby.
|
||||
|
||||
## Section Two
|
||||
|
||||
Lists should feel ordinary:
|
||||
|
||||
- Capture the thought.
|
||||
- Add enough detail.
|
||||
- Keep nested ideas aligned.
|
||||
- Avoid visual clutter.
|
||||
- Move on.
|
||||
|
||||
Ordered lists should also be readable:
|
||||
|
||||
1. State the problem.
|
||||
2. Describe the tradeoff.
|
||||
3. Choose the stable path.
|
||||
|
||||
## Section Three
|
||||
|
||||
Real notes often include small tables.
|
||||
|
||||
| Topic | Note |
|
||||
| ----- | ---- |
|
||||
| Rendering | Use attributed styling first |
|
||||
| Editing | Keep source text canonical |
|
||||
| Performance | Avoid document-wide work during typing |
|
||||
|
||||
And real notes often include small code fragments.
|
||||
|
||||
```swift
|
||||
struct Note {
|
||||
var title: String
|
||||
var body: String
|
||||
}
|
||||
```
|
||||
|
||||
## Closing
|
||||
|
||||
The test is not whether this becomes a perfect preview. The test is whether a user can write documentation, meeting notes, research notes, and project plans without feeling like they are trapped in plain text.
|
||||
|
||||
37
Docs/EditorPrototypes/markdown-meeting-notes.md
Normal file
37
Docs/EditorPrototypes/markdown-meeting-notes.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Product Review Meeting
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Review editor responsiveness.
|
||||
2. Validate Markdown rendering basics.
|
||||
3. Identify unsupported rich content.
|
||||
|
||||
## Notes
|
||||
|
||||
> The editor should feel closer to a writing tool than to a source-only text area.
|
||||
|
||||
- Rendering polish matters when scanning a document.
|
||||
- Editing correctness matters more when typing.
|
||||
- The active line must show source syntax.
|
||||
- This includes list markers.
|
||||
- This includes task checkboxes.
|
||||
|
||||
## Decisions
|
||||
|
||||
- [x] Keep NSTextView as the macOS editor foundation.
|
||||
- [x] Expand attributed Markdown rendering.
|
||||
- [ ] Add interactive task checkboxes later.
|
||||
- [ ] Revisit overlays for rich blocks later.
|
||||
|
||||
## Links
|
||||
|
||||
[Milestone notes](https://example.com/milestone-3) summarize the rendering scope. The benchmark file remains at Docs/Benchmarks/5mb.md.
|
||||
|
||||
## Follow-up Table
|
||||
|
||||
| Owner | Item | Status |
|
||||
| ----- | ---- | ------ |
|
||||
| Editor | Validate large documents | Done |
|
||||
| Renderer | Improve table appearance | In progress |
|
||||
| Product | Define image milestone | Later |
|
||||
|
||||
47
Docs/EditorPrototypes/markdown-project-plan.md
Normal file
47
Docs/EditorPrototypes/markdown-project-plan.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Milestone 3 Project Plan
|
||||
|
||||
## Scope
|
||||
|
||||
Improve Markdown rendering completeness without changing the editor architecture.
|
||||
|
||||
## Work Breakdown
|
||||
|
||||
1. Markdown fundamentals
|
||||
- Headings
|
||||
- Bold and italic
|
||||
- Inline code
|
||||
- Blockquotes
|
||||
- Horizontal rules
|
||||
2. Lists
|
||||
- Unordered lists
|
||||
- Ordered lists
|
||||
- Nested lists
|
||||
- Task lists
|
||||
3. Links
|
||||
- Markdown links
|
||||
- Automatic links
|
||||
4. Blocks
|
||||
- Fenced code blocks
|
||||
- Tables
|
||||
|
||||
## Risk Register
|
||||
|
||||
| Risk | Mitigation |
|
||||
| ---- | ---------- |
|
||||
| Cursor drift | Do not replace source text |
|
||||
| Slow typing | Render only dirty lines |
|
||||
| Unstable scrolling | Preserve native scroll origin |
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [x] Unit tests cover new render plan cases.
|
||||
- [x] Sample documents cover real writing shapes.
|
||||
- [ ] Visual review in the app.
|
||||
- [ ] 5 MB benchmark remains acceptable.
|
||||
|
||||
---
|
||||
|
||||
## Deferred
|
||||
|
||||
Images, wikilinks, Mermaid, LaTeX, callouts, attachments, audio, and video remain future milestones.
|
||||
|
||||
40
Docs/EditorPrototypes/markdown-research-notes.md
Normal file
40
Docs/EditorPrototypes/markdown-research-notes.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Research Notes: Local-First Writing
|
||||
|
||||
## Question
|
||||
|
||||
Can a local-first Markdown workspace feel calm enough for long research sessions while still preserving standard Markdown files?
|
||||
|
||||
## Observations
|
||||
|
||||
- Writers need low-friction capture.
|
||||
- Researchers need stable references.
|
||||
- Developers need code snippets and tables.
|
||||
|
||||
> The file format should stay portable even if Sapling renders extra affordances.
|
||||
|
||||
## References
|
||||
|
||||
- [Local-first software](https://www.inkandswitch.com/local-first/)
|
||||
- https://example.com/research/local-markdown
|
||||
|
||||
## Extract
|
||||
|
||||
```text
|
||||
Source text remains canonical.
|
||||
Rendered styling is a view over that source.
|
||||
```
|
||||
|
||||
## Evidence Table
|
||||
|
||||
| Evidence | Interpretation |
|
||||
| -------- | -------------- |
|
||||
| Fast active-line changes | Hybrid editing remains viable |
|
||||
| Dirty-line rendering | Styling can stay bounded |
|
||||
| Native selection ranges | Cursor behavior stays predictable |
|
||||
|
||||
## Open Questions
|
||||
|
||||
- [ ] What is the smallest useful image milestone?
|
||||
- [ ] Should tables eventually use overlay layout?
|
||||
- [x] Should source remain standard Markdown?
|
||||
|
||||
52
Docs/EditorPrototypes/markdown-technical-documentation.md
Normal file
52
Docs/EditorPrototypes/markdown-technical-documentation.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Sapling Renderer Notes
|
||||
|
||||
## Overview
|
||||
|
||||
Sapling keeps Markdown source as the editable buffer and renders inactive lines with attributed styling. This document is a compact technical note for checking whether common documentation patterns feel readable while editing.
|
||||
|
||||
> Rendering can improve scanning, but the active line must always remain exact Markdown source.
|
||||
|
||||
## Goals
|
||||
|
||||
- Preserve native cursor movement.
|
||||
- Keep typing latency independent from total document size.
|
||||
- Make inactive lines easier to read.
|
||||
- Headings should create a clear hierarchy.
|
||||
- Lists should show nested structure.
|
||||
- Inline code should stand apart from prose.
|
||||
- Defer rich embeds until the editor can support them without offset mapping.
|
||||
|
||||
## API Sketch
|
||||
|
||||
```swift
|
||||
let renderer = HybridMarkdownLineRenderer()
|
||||
let plans = renderer.renderPlans(for: lines)
|
||||
```
|
||||
|
||||
The renderer returns line-level plans. The native text view applies attributes to the canonical source text.
|
||||
|
||||
## Supported Inline Forms
|
||||
|
||||
Use **strong emphasis** for important terms, *light emphasis* for nuance, and `inline code` for identifiers. Links such as [Swift Package Manager](https://swift.org/package-manager/) should be visually distinct.
|
||||
|
||||
Automatic links should also be recognizable: https://example.com/sapling/rendering
|
||||
|
||||
## Tradeoffs
|
||||
|
||||
| Choice | Result |
|
||||
| ---- | ----- |
|
||||
| Single source buffer | Stable cursor offsets |
|
||||
| Attributed rendering | Fast dirty-line updates |
|
||||
| No text replacement | Markdown markers remain part of layout |
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] Headings
|
||||
- [x] Emphasis
|
||||
- [x] Lists
|
||||
- [x] Code blocks
|
||||
- [ ] Images
|
||||
- [ ] Wikilinks
|
||||
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import XCTest
|
||||
@testable import SaplingEditor
|
||||
|
||||
final class EditorMarkdownRenderingSampleTests: XCTestCase {
|
||||
func testMilestoneThreeSamplesExerciseMarkdownRenderingFeatures() throws {
|
||||
let sampleNames = [
|
||||
"markdown-technical-documentation.md",
|
||||
"markdown-meeting-notes.md",
|
||||
"markdown-research-notes.md",
|
||||
"markdown-project-plan.md",
|
||||
"markdown-long-form-writing.md"
|
||||
]
|
||||
let samplesDirectory = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
||||
.appendingPathComponent("Docs/EditorPrototypes", isDirectory: true)
|
||||
let renderer = HybridMarkdownLineRenderer()
|
||||
var counts = FeatureCounts()
|
||||
|
||||
for sampleName in sampleNames {
|
||||
let source = try String(contentsOf: samplesDirectory.appendingPathComponent(sampleName), encoding: .utf8)
|
||||
let lines = EditorActiveLineTracker.lines(from: source, activeLineIndex: 0)
|
||||
let plans = renderer.renderPlans(for: lines)
|
||||
|
||||
XCTAssertGreaterThan(lines.count, 10, "\(sampleName) should represent a realistic document shape.")
|
||||
counts.collect(plans)
|
||||
}
|
||||
|
||||
XCTAssertGreaterThan(counts.headings, 0)
|
||||
XCTAssertGreaterThan(counts.blockquotes, 0)
|
||||
XCTAssertGreaterThan(counts.horizontalRules, 0)
|
||||
XCTAssertGreaterThan(counts.unorderedLists, 0)
|
||||
XCTAssertGreaterThan(counts.orderedLists, 0)
|
||||
XCTAssertGreaterThan(counts.taskLists, 0)
|
||||
XCTAssertGreaterThan(counts.codeFences, 0)
|
||||
XCTAssertGreaterThan(counts.codeContentLines, 0)
|
||||
XCTAssertGreaterThan(counts.tableRows, 0)
|
||||
XCTAssertGreaterThan(counts.boldSpans, 0)
|
||||
XCTAssertGreaterThan(counts.italicSpans, 0)
|
||||
XCTAssertGreaterThan(counts.inlineCodeSpans, 0)
|
||||
XCTAssertGreaterThan(counts.markdownLinks, 0)
|
||||
XCTAssertGreaterThan(counts.automaticLinks, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private struct FeatureCounts {
|
||||
var headings = 0
|
||||
var blockquotes = 0
|
||||
var horizontalRules = 0
|
||||
var unorderedLists = 0
|
||||
var orderedLists = 0
|
||||
var taskLists = 0
|
||||
var codeFences = 0
|
||||
var codeContentLines = 0
|
||||
var tableRows = 0
|
||||
var boldSpans = 0
|
||||
var italicSpans = 0
|
||||
var inlineCodeSpans = 0
|
||||
var markdownLinks = 0
|
||||
var automaticLinks = 0
|
||||
|
||||
mutating func collect(_ plans: [HybridMarkdownLineRenderPlan]) {
|
||||
for plan in plans {
|
||||
switch plan.kind {
|
||||
case .heading:
|
||||
headings += 1
|
||||
case .blockquote:
|
||||
blockquotes += 1
|
||||
case .horizontalRule:
|
||||
horizontalRules += 1
|
||||
case .unorderedList:
|
||||
unorderedLists += 1
|
||||
case .orderedList:
|
||||
orderedLists += 1
|
||||
case .taskList:
|
||||
taskLists += 1
|
||||
case .fencedCodeFence:
|
||||
codeFences += 1
|
||||
case .codeBlockContent:
|
||||
codeContentLines += 1
|
||||
case .tableRow:
|
||||
tableRows += 1
|
||||
case .paragraph:
|
||||
break
|
||||
}
|
||||
|
||||
for span in plan.spans {
|
||||
switch span.kind {
|
||||
case .bold:
|
||||
boldSpans += 1
|
||||
case .italic:
|
||||
italicSpans += 1
|
||||
case .inlineCode:
|
||||
inlineCodeSpans += 1
|
||||
case .link:
|
||||
markdownLinks += 1
|
||||
case .automaticLink:
|
||||
automaticLinks += 1
|
||||
case .markdownDelimiter:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue