Conversation
The rendered Markdown face becomes an editor. The vendored domd kernel runs in a WKWebView beside the existing reader, reaching its page, fonts and the document's own images through one allow-listed scheme handler — the page's CSP forbids the network outright, so that handler is the only door in. Raw HTML, mermaid, KaTeX, tables, GitHub alerts and YAML front matter all render, and the reader's type scale comes with them: iA Writer Quattro at 17px/1.6 on a 76ch measure, with the CJK leading and punctuation compression the reader tuned. Checkpoint. None of this has been seen on screen — macOS denies Screen Recording to every agent session here, so the face flip, the image path through the scheme handler, and the Settings work remain unverified. Known open: 110 of 164 repo markdown files do not round-trip byte-identically (tables re-padded, blank lines added, `0.` lists renumbered).
The rendered face was unclickable everywhere below the last line of text: the editable element sized itself to its content rather than the pane, so those clicks landed on the page behind it and placed no caret. A flex chain gives the editable region the full height. Code blocks were rendering at prose size with no padding in a stack the app never chose. The rules were written with `:where()`, which contributes no specificity, so the vendored stylesheet won the cascade; they now apply, and `codeTokenizer` is wired to the bundled highlight.js so code carries colour. Images and diagrams get a full-screen viewer, ported from the reader's: an opaque overlay in the page's own background, open and close in one frame, vector fills the pane while a raster stops at its natural size. The reader opens an image on click, which an editable surface cannot do — a click there places the caret — so both node types take the hover zoom chip the reader reserved for diagrams, and only diagrams get wheel-zoom and pan. Escape had two owners. `stopPropagation` never reaches `cancelOperation:`, so one press closed the viewer and the editor with it; the page now reports the viewer's state and the host consumes exactly one press. Remote images stay blocked by default and are unblocked per document. A remote image in a Markdown file is a tracking pixel, and this file may be a README an agent just fetched; the reader ships no CSP and accepts that exposure, which is not a thing to inherit silently into an editor. Known open, all upstream in the domd kernel and none reachable from here: an HTML block runs to its closing tag instead of ending at a blank line, so markdown inside a centred `<div>` stays literal; inline `<abbr>` is dropped; canonical re-serialisation rewrites 110 of 164 repo documents on save. Each has a repro in .domd-proto/out/UPSTREAM-ASK.md.
The caret in a contenteditable is the font's ascent and descent — 23px at 18px Quattro — and no leading changes it. At 1.5 it filled 85% of the row and read as a bar spanning the line; at 1.7 it fills 75%, with air above and below. Tightening leading backfires: below about 1.28 the caret is taller than its own line box and overflows into its neighbours.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The two failures the localization check reported: a missing key resolves to its English source rather than failing, so a Chinese pane would have shipped with both of these in English and nothing would have said so.
…tting The kernel's model is canonical Markdown, so serializing a parsed file back out re-pads table columns, moves blank lines and renumbers a `0.` ordered list to `1.` — a round trip 110 of this repo's 164 Markdown documents do not survive. Opening a file was already safe, because the page reports nothing until the document changes. Typing one character was not: the report is the whole canonical document, so a keystroke rewrote the file. The page now hands back its serialization of the document it was just given, and the host merges an edit against that base instead of adopting it. Measured over all 164 documents: an unedited save is byte-identical 164/164, and a one-character edit is a one-line diff 164/164. Where the two sides collide — the user typed into a row canonicalization had also rewritten — the edit wins and the damage stops at that row. Table alignment makes the case: the kernel drops it in both directions, so `|:--|:-:|--:|` serializes back as `| - | - | - |`. Without the merge, saving any document with an aligned table destroyed the alignment.
`Resources/domd/app.js` is a 400 KB built bundle whose source was not in the repo — the README pointed at a scratch directory that exists on one machine. `web/domd` is that source, with both versions pinned exactly: rebuilding there reproduces the committed bundle byte for byte, so a diff means the input really changed.
The page answers for the document it was actually handed, so the host cannot hold a baseline from one document beside the buffer of another — a merge against a mismatched pair rewrites lines nobody touched.
The page corrects both halves when it answers, but between the flip and that answer only one of them was set. Seeding it with the document about to be pushed means the pair is never half-set, whatever the page does.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Open a
.mdfile and the rendered face is now an editor: type into the document you are reading, flip to source and back, and the two faces stay one document.The vendored domd kernel runs in a
WKWebViewbeside the existing reader. Its page, its fonts and the open document's own images all arrive through a single scheme handler with explicit allow-lists — the page's CSP forbids the network outright, so that handler is the only door in. The page's own source lives inweb/domd; rebuilding there reproduces the committed bundle byte for byte.What renders
Raw HTML, mermaid, KaTeX, tables, all five GitHub alert kinds, footnotes, task lists, emoji shortcodes, and YAML front matter as a metadata panel. Code is tokenized by the bundled highlight.js and set in your terminal font, so a fenced block matches the source face you flipped from.
The reader's typography comes with it — iA Writer Quattro, a capped measure, and the CJK leading and punctuation compression that block was tuned for.
Three decisions worth the review
Images and diagrams zoom, but not on click. The reader opens an image on click; an editable surface cannot, because a click there has to place the caret. Both node types take the hover zoom chip the reader reserved for diagrams, and only diagrams get wheel-zoom and pan — a raster never scales past its natural size, since enlarged pixels read as a bug.
Escape had two owners.
stopPropagationnever reachescancelOperation:, so one press closed the viewer and the editor with it. The page now reports the viewer's state and the host consumes exactly one press, claiming and releasing in a single call so the race resolves the same way whichever side lands first.Remote images are held by default and unblocked per document. A remote image in a Markdown file is a tracking pixel: opening a README tells that host your IP and when you opened it, and the file may be one an agent just fetched. The reader ships no CSP and accepts that exposure; inheriting it silently into an editor is not the same decision.
A save writes the edit, not the document
The kernel's model is canonical Markdown, so parsing a file and serializing it straight back re-pads table columns, moves blank lines and renumbers a
0.-anchored ordered list to1.— a round trip that 110 of this repo's 164 Markdown documents do not survive. Opening a file was already safe, because the page reports nothing until the document changes. Typing one character was not: what comes back is the whole canonical document, so a keystroke rewrote the whole file.MarkdownWriteBackis an ordinary three-way merge and closes it. The page now hands back its own serialization of the document it was just given; that is the base, the file on disk is ours, the reported document is theirs, and only what separates the last two reaches the file. Measured over all 164 documents:Where the two sides collide — the user typed into a row canonicalization had also rewritten — the edit wins and the damage stops at that row.
web/domd/scripts/writeback-corpus.mjsis the measurement; re-run it after a kernel bump.Known open, all upstream in the domd kernel
None of these are reachable from this repo. Each has a runnable repro:
<div>stays literal — every README with a centred header is affected. Repro:store.resetMD("<div>\n\n# Heading\n\n</div>\n")yields one HTML node and no heading.:---:and---:reach neither the DOM nor the text: every cell computestext-align: left, andtoMarkdownwrites the delimiter row back as| - | - | - |. Repro:resetMD("| a | b | c |\n|:--|:-:|--:|\n| 1 | 2 | 3 |\n")round-trips to| - | - | - |. The write-back merge is what keeps a save from destroying an aligned table in the meantime.<abbr>is dropped to literal source.One typography question to settle
The kernel sets
white-space: pre-wrapon its paragraphs and keeps the source's newlines, so a hand-wrapped paragraph renders with the author's line breaks rather than reflowing to the measure. Every document in this repo is wrapped at about 80 columns, so at the 66ch measure each source line breaks again and leaves a short fragment behind it:The reader this replaces reflowed —
MarkdownReaderRendererpassessoftBreaksAsBreaks: false. Preserving the newlines is how the kernel keeps the caret over the text model, so this is a decision to take deliberately rather than a defect to patch: either the rendered face reads this way, or the paragraph face overrideswhite-spaceand the caret mapping is re-checked against it.Verification
swift buildandswift testclean,scripts/check-strings.shgreen — the two missing catalog keys that failed CI are in. The domd suite covers the scheme handler's allow-lists, the viewer's Escape ordering, the prefix guard on write-back, and the document-delivery path;MarkdownWriteBackTestscovers the merge against the three shapes canonicalization actually changes, and the 164-document corpus measurement is above.On the running app: a dev build of this branch was driven through Accessibility with the document open in the rendered face. Clicking into the prose and typing one character logged
mousedown on span inEditor=true editable=true, the character appeared in the accessibility tree, and the file on disk came back with exactly this diff — the aligned table row and the0.list untouched, which is the whole point of the merge:Flipping to the source face showed the same buffer, flipping back kept it, and one Escape closed the editor once and left the window and file browser up. The two commits after that run only move where the merge's baseline pair is set; the flip into the rendered face still seeds it exactly as the verified build did.
On screen: macOS denies Screen Recording to every agent session on this machine, so the page was rendered instead in a real
WKWebViewoff screen — the shippedindex.html,app.js,app.cssand fonts, served through the same kind of scheme handler, with the host's own appearance tokens applied — and snapshotted withtakeSnapshot. That is WebKit's own rendering of the real page: front matter panel, headings, wrapped prose, Chinese prose, table, blockquote, GitHub alert, highlighted Swift, task lists, KaTeX inline and display, mermaid and the footnote all render in both appearances, the page never scrolls horizontally, and prose sets in iA Writer Quattro at 18px on 1.7 leading across a 713px measure, headings take their optical negative tracking, and a document with Chinese in it moves to the CJK register's 1.8.Release Notes: Markdown files now open in an editable rendered view. Type directly into the formatted document, click an image or diagram to open it full screen, and flip to the source at any time.