137 lines
7.7 KiB
Markdown
137 lines
7.7 KiB
Markdown
# Markdown view — comparison with bezel and gpuikit
|
|
|
|
Peer read for API ideas only; nothing ported, nothing depended on
|
|
([AGENTS.md](../AGENTS.md)). Assessed 2026-11 against `main` of
|
|
[bezel](https://github.com/crabtalk/bezel) (`crates/markdown`) and
|
|
[gpuikit](https://github.com/iamnbutler/gpuikit) (`src/markdown`). Both move
|
|
fast and bezel's APIs are explicitly still evolving, so details may have
|
|
shifted when this is revisited. Queue entry:
|
|
[CHECKLIST.md](CHECKLIST.md) — "Markdown view follow-ups".
|
|
|
|
## Positioning — three different products
|
|
|
|
| | gpui-native-kit (ours) | bezel | gpuikit |
|
|
|---|---|---|---|
|
|
| Ambition | Read-only document viewer | Editable Notion-style document engine, markdown as wire form | Streaming reader built for LLM/chat output |
|
|
| Core model | `Vec<MarkdownBlock>` IR | `Doc` = flat block list + indent; `Text` = string + marks over byte ranges | Pulldown events kept with source ranges, rendered straight to elements |
|
|
| Editing | None (Input covers fields) | Full: splice/shortcut edits, caret, selection, parse↔serialize fixed point | None |
|
|
| gpui coupling | One crate, feature-gated | Pure core + `gpui` feature; builds for wasm | All gpui |
|
|
|
|
## Dimension by dimension
|
|
|
|
**Parsing.** All three use pulldown-cmark. gpuikit's option set is the most
|
|
disciplined: GFM + footnotes on, every option left off documented with what
|
|
turning it on would silently break (`~x~` becoming subscript instead of
|
|
strikethrough, wikilinks, math), and exhaustive `Start`/`End` matches with no
|
|
wildcard arm, so a pulldown upgrade is a compile error instead of silently
|
|
dropped styling. We match on tables/strikethrough/tasklists but skip footnotes
|
|
and GFM alerts, and use `_ => {}` wildcard arms in two places.
|
|
|
|
**Block model.** bezel is the outlier: flat blocks with a one-level-deeper
|
|
indent invariant; marks as ordered spans (`**_x_**` differs from `_**x**_`,
|
|
which run flags cannot express); block-level Bookmark/Mention rich links;
|
|
image width in the title slot (``); GFM alert quotes
|
|
(`[!NOTE]`); GitHub-style heading slugs; a parse↔serialize fixed-point
|
|
guarantee so an edit/save cycle never drifts. Neither viewer attempts
|
|
round-tripping — fine for us, and the reason bezel's parse layer is its own
|
|
crate.
|
|
|
|
**Streaming (gpuikit's headline).** `append()` for LLM deltas: parse on the
|
|
background executor, previous events keep rendering (never blanks), deltas
|
|
arriving mid-parse coalesce into one follow-up parse, first parse synchronous
|
|
so the document is never empty on frame one. Two extra streaming details: a
|
|
`stitch` preprocessing pass closes unterminated inline syntax (`**bold`)
|
|
before parsing so text does not flicker between plain and styled mid-stream,
|
|
and an unclosed fence is drawn plain until its closer arrives — a streaming
|
|
fence is a new cache key per delta, so highlighting it costs a full pass per
|
|
frame. Their open-fence byte scan errs only toward "closed", the harmless
|
|
direction. We parse synchronously in `set_source` and re-highlight every fence
|
|
on each parse.
|
|
|
|
**Code highlighting.** tree-sitter for bezel, syntect for gpuikit
|
|
(feature-gated, with a highlight cache and theme selection), tree-sitter with
|
|
per-language cargo features for us. Ours shares one grammar stack with the
|
|
code view — one dependency set across both surfaces — but re-highlights every
|
|
block on every parse where gpuikit caches settled blocks and suspends the open
|
|
one.
|
|
|
|
**Layout and performance.** The one place we are clearly ahead: neither
|
|
library virtualizes. gpuikit builds one flex column of every block in the
|
|
document each frame (confirmed in its `render_events`); bezel renders blocks
|
|
directly (not assessed in depth). We window over blocks like the code and diff
|
|
views do. The trade: an all-in-one-column layout makes text selection
|
|
tractable, which our windowing makes harder.
|
|
|
|
**Text selection.** Both ship it, we do not. gpuikit's is the deeper
|
|
implementation: a document-wide selection of `(run, byte offset)` positions, a
|
|
per-frame run registry, `append` preserves the selection while `set_source`
|
|
drops it, `selected_text()` for the copy binding. bezel's rides on its
|
|
editable model. This is our biggest functional gap against both.
|
|
|
|
**Accessibility.** gpuikit is far ahead: `Role::Document` on the root, per-run
|
|
roles (heading with level, paragraph, list item, blockquote, code), and
|
|
element ids stable across frames because assistive technology reads a changed
|
|
node id as a replaced element. Tested through a recorded-run harness. We ship
|
|
one optional `aria_label` on the root. bezel's a11y not assessed.
|
|
|
|
**Tables.** gpuikit honors GFM column alignment, stripes body rows, borders
|
|
per column. We parse cell order but dropped `Alignment` — cheap to add since
|
|
pulldown hands it over.
|
|
|
|
**Images.** gpuikit renders real image elements with alt text; bezel supports
|
|
captions and widths. We render alt text only (documented M16 scope).
|
|
|
|
**Styling philosophy.** gpuikit: a per-instance `MarkdownStyle` struct with
|
|
per-key theme fallbacks, plus knobs like `soft_break_as_hard_break` (LLM
|
|
output treats single newlines as breaks). bezel: nothing passed at all — style
|
|
flows through the environment, `Brand`/`Tint` rotate one palette. Ours is
|
|
closest to bezel's philosophy: everything resolves from the native system
|
|
theme, zero per-instance color knobs, which is the kit's stated goal. We have
|
|
no soft-break knob yet.
|
|
|
|
**Lists.** All three solved the same loose/tight and multi-block-item
|
|
problems. gpuikit's mechanism is elegant: the item's first block takes the
|
|
marker (`Shown`), later blocks reserve the marker's width but draw it
|
|
`Hidden`, so ordinals are not burned and continuation text aligns exactly with
|
|
the first block's text column. Ours reaches the same semantics (marker on the
|
|
first row, continuations as deeper paragraphs) with approximate alignment
|
|
(indent step 1.5rem vs marker column 1.75rem).
|
|
|
|
## Ideas worth stealing
|
|
|
|
Ranked by value to downstream consumers, signed first. API ideas only —
|
|
implementation stays ours.
|
|
|
|
1. Streaming `append` + coalesced background parse (gpuikit). For an LLM
|
|
client this is the headline feature; our synchronous `set_source` blocks
|
|
the UI thread on large documents.
|
|
2. Text selection with copy (both). The pattern exists in the code view; a
|
|
wrapped-text version is real work but the biggest functional gap.
|
|
3. Per-block a11y roles + stable ids (gpuikit). Medium effort, high
|
|
correctness value; the recorded-run test technique is reusable as an idea.
|
|
4. Incremental highlight caching (gpuikit). Cheap version for us: reuse
|
|
per-block tree-sitter trees across parses — `highlight_with_tree` already
|
|
takes `old_tree` — instead of re-parsing every fence per `set_source`.
|
|
5. GFM alerts + table column alignment (both). Nearly free; pulldown emits
|
|
`BlockQuoteKind` and `Alignment` today and we ignore both.
|
|
6. `soft_break_as_hard_break` (gpuikit). One option, real payoff for agent
|
|
output.
|
|
7. Exhaustive match, no wildcard arms (gpuikit). Turns pulldown upgrades into
|
|
compile errors.
|
|
8. Heading anchors/slugs (bezel). Useful when a consumer wants in-document
|
|
navigation.
|
|
9. Per-view typography overrides (gpuikit's `MarkdownStyle`). Conflicts with
|
|
the theme-global discipline; the checklist's editor follow-ups already
|
|
gesture at per-view config — decide deliberately, not by accident.
|
|
|
|
## Deliberate differences
|
|
|
|
- Virtualization is ours alone and is the right call for a kit whose gallery
|
|
ships 5,000-line perf tests.
|
|
- Native theme resolution (platform sources → tokens) instead of per-instance
|
|
style structs is the kit's identity, not a gap.
|
|
- No editing or serialization. bezel's editor-grade model is a different
|
|
product; if a consumer ever wants an editable markdown surface, bezel's
|
|
flat-block model is the architecture study to make first, and its pure-core
|
|
split is the cleanest layering of the three.
|