Skip to content

Feature/replace hoedown - #2

Merged
levous merged 78 commits into
mainfrom
feature/replace-hoedown
Oct 10, 2026
Merged

levous merged 78 commits into
mainfrom
feature/replace-hoedown

Conversation

@levous

@levous levous commented Oct 10, 2026

Copy link
Copy Markdown
Owner

No description provided.

levous and others added 30 commits October 9, 2026 17:38
Tables, fenced code, footnotes, strikethrough and intra-word emphasis are
always on, whatever their old saved values; Quote ("…" as <q>) is never
set. help.md moves strikethrough into the basics and drops Quote, and
HelpDocumentTests expect them as standard (plan decision).

Verified: swift test 105/105 twice; Release build; a throwaway build
showed strikethrough, table and footnote rendering, and no <q>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The renderer, scroll-sync scan and file-name title no longer read
htmlTaskList or htmlDetectFrontMatter: both are standard and always on.
help.md and HelpDocumentTests treat them as standard (plan decision).

Verified: swift test 106/106 twice; Release build; a throwaway build
showed checkboxes and the front-matter table with the old settings unset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops the table, fenced code, footnote, strikethrough, intra-word
emphasis, Quote, task list and front-matter preferences and their
toggles; their saved values stay in user defaults (FR-36). The editor
always highlights footnotes, and the Markdown pane shows Highlight,
Superscript, Autolink and Smart punctuation.

Verified: swift test 108/108 twice; Release build; a temporary probe
(removed) showed the Markdown and Rendering panes in a throwaway build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tep)

Adds translations for the new label to port_translations.json (marked
needs_review) and removes the nine strings of the deleted toggles from
the catalog, keeping Xcode's formatting so the diff is only those keys.

Verified: swift test 108/108; Release build; a temporary probe (removed)
showed the Markdown pane in German with "Intelligente Interpunktion".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fresh installs default to the - list marker, a newline at end of file
and spaces instead of tabs; existing installs keep their values. The
dead extension defaults went with the settings in the previous commit.

Verified: swift test 110/110 twice; Release build; a throwaway build
with its own bundle ID (a real fresh install, defaults removed after)
showed the new values in the Editor pane via a temporary probe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RendererTests renders a table, fenced code, strikethrough, footnote,
task list and front matter with nothing in user defaults (fresh and
existing installs), and checks "text" stays as typed with no <q>.

Verified: swift test 111/111; Release build; the test fails when the
standard flags are made opt-in again (mutation check, reverted).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new "Standard Extensions" section covers tables, fenced code,
footnotes, task lists and front matter (moved from the Rendering
section); the Markdown Settings section lists only Highlight,
Superscript, Autolink and Smart punctuation, with the markup shown in
code spans so it stays literal. Intra-word emphasis moves to the basics.

Verified: swift test 111/111 twice; Release build; excerpts of the new
sections rendered correctly in a throwaway build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README "Differences from the original" notes the always-on standard
features, the trimmed Markdown pane, the dropped Quote setting, and
the new editing defaults for fresh installs.

Verified: documentation only; the behavior it describes was verified
in the app in the preceding early-step commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MacDownKit and the tests depend on the Markdown product; nothing in the
app uses it yet. A spike suite parses in a detached task under Swift 6
strict concurrency; later spikes add to it.

Verified: swift test 112/112; Release build (strict concurrency
complete) succeeded with the new dependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Footnote syntax stays literal text under swift-markdown 0.9.0, while
cmark-gfm parses footnote nodes with source positions when asked.
Recorded as finding F1; Open Question 1 now lists three options
(source scan, keep hoedown, cmark-gfm directly) and stays open. The
Phase 3 footnote task is marked blocked on it. swift-cmark is pinned
explicitly for the test target.

Verified: swift test 114/114; Release build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the table, strikethrough and tasklist extensions are attached, so
bare URLs and emails stay text while <…> autolinks are always links.
Recorded as finding F2: the Autolink setting needs its own pass.

Verified: swift test 115/115 (tests only; no app change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cmark applies smart punctuation unless .disableSmartOpts is passed
(F3), so parses with the setting off must pass it. ==highlight== and
^superscript stay plain text (F4) and need our own pass.

Verified: swift test 117/117 (tests only; no app change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every block kind carries a SourceRange with 1-based UTF-8 columns (F5).
Document can't leave a detached task under Swift 6 (F6), so the Phase 1
model keeps the visitors' Sendable outputs rather than the tree.

Verified: swift test 118/118; the Sendable check was a compile probe
(removed) that failed as recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Findings F1–F6 sit under Decisions; the feature tables, Decision 5 and
FR-8a now state the answers (smart punctuation native, highlight,
superscript and autolinks our own pass, footnotes open question 1)
instead of "native if available".

Verified: documentation only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MarkdownEngine is hoedown (default) or swiftMarkdown, stored under the
markdownEngine key and not shown in Settings; unknown values fall back
to hoedown. Nothing reads it yet.

Verified: swift test 119/119; Release build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Front matter detection matched a closing --- anywhere (even inside a
code span) and accepted any YAML, so a document opening with a --- rule
lost its first lines; with detection now always on, that would hit
every such document. The closing delimiter must be a line of its own
and the YAML a mapping.

Verified: swift test 126/126 twice; Release build; a throwaway build
showed a leading-rule document intact and valid front matter as a table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nineteen purpose-written documents (inline, blocks, quotes, code, links
and images, tables, HTML, footnotes, front matter cases, task lists,
TOC, math, opt-in syntax, diagrams, Unicode, CRLF, an image-heavy
document) plus help.md and a generated 10k-line document, with a Corpus
loader for the diff harnesses. .gitattributes keeps the CRLF file exact.

Verified: swift test 126/126; Release build; the image-heavy and Unicode
documents rendered correctly in a throwaway build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Renders the corpus (21 documents) under a 9-setting matrix (standard,
each opt-in alone, all on) with two engines, normalizes attribute order
and whitespace, and writes html-diff.md to $MACDOWN_DIFF_REPORT. It
reports 0 differences for hoedown against itself and catches a
deliberately altered engine. Runs in the serialized live group so it
doesn't starve the WKWebView tests.

Verified: swift test 129/129 twice (tests only; no app change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Compares editor highlight spans per element type over the corpus and
writes highlight-diff.md (with the source of each differing span) to
$MACDOWN_DIFF_REPORT. PEG against itself has no differences, and an
engine that drops EMPH and shifts STRONG is reported precisely. Spans
that end before they start are skipped, as the editor does; PEG emits
one for a header inside a block quote (noted for Phase 2).

Verified: swift test 130/130 twice (tests only; no app change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
The harness now diffs token by token and checks each differing hunk
against Resources/expected-html-diffs.json, find/replace rules applied
to hoedown's side. Expected differences are listed once with counts;
only unexplained hunks are reported per document. It starts with the
CommonMark intra-word underscore rules (Decision 3). Quote no longer
differs: the early step dropped it from hoedown too.

Verified: swift test 132/132 twice (tests only; no app change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
There is no CI, so README and CLAUDE.md give the swift test --filter
commands for both harnesses, the MACDOWN_DIFF_REPORT reports, the
expected-diff list, and the rule that the corpus is generated only.

Verified: the documented command writes html-diff.md and
highlight-diff.md (6 harness tests pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Converts 1-based line and UTF-8 column positions to UTF-16 offsets and
NSRanges, with lines ending at \n, \r\n or a lone \r as in cmark. It
takes plain numbers, so only the model needs to import Markdown.

Verified: swift test 137/137; Release build; LineIndexTests cover
ASCII, emoji, surrogate pairs, CRLF, bounds, and every block range of
every corpus document; they fail if 4-byte characters count as one unit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
ProtectedSource replaces math with same-length x filler and valid front
matter with spaces, keeping line breaks, so every source position is
unchanged. Math uses MacDown's delimiters ($$, \\[ \\], \\( \\), and $
with inline dollars), never inside code or raw HTML (found by a
swift-markdown parse, so indented code counts) or across a blank line;
amounts like "$5 and $10" aren't math (finding F7). LineIndex moves to
Document/Model and gains utf8Offset; the corpus math file uses
MacDown's double-backslash syntax.

Verified: swift test 146/146; Release build; the tests fail if code
ranges aren't skipped (mutation check, reverted).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Built where the parse runs, since Document isn't Sendable (F6): it
protects the source, parses it with swift-markdown (smart punctuation
only when on), and keeps the source, LineIndex, front matter, math
ranges and the block list (kind, source lines, UTF-16 range). Phase 2
and 3 visitors will add their outputs here. LineIndex gains a UTF-8 to
UTF-16 offset lookup.

Verified: swift test; Release build; the model builds in a detached
task and its blocks map into every corpus document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
ParseSettings carries the markdownEngine setting, so changing it
re-renders. With swiftMarkdown, Renderer builds MarkdownDocumentModel
in the same detached task as the hoedown parse and keeps it as
`model`; the generation counter still discards stale results. The HTML
stays hoedown's until Phase 3.

Verified: swift test 155/155 twice; Release build; a throwaway build run
with -markdownEngine swiftMarkdown rendered help.md normally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
ThemeStyle(parsing:) reads .style files as pmh_styleparser.c does: same
element names, attributes, colors (strtoll semantics), font traits and
error messages, and the same quirks (reverse attribute order, later
editor rules replace earlier ones). Not wired into the highlighter yet;
that is a new Phase 2 task.

Verified: swift test; Release build; output equals the C parser's for
all 15 themes and for a malformed stylesheet (9 errors, CRLF, BOM);
the tests fail if attribute order is changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
levous and others added 28 commits October 9, 2026 20:20
The model puts the front matter table (Yams, shared with hoedown) before
the body. Valid front matter and a leading rule match hoedown; invalid
YAML is Markdown, where CommonMark's multi-line setext header takes both
YAML lines into the <h2>.

Verified: swift test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
With the setting on, the renderer records each header's content (links
as text) and replaces [TOC] paragraphs with hoedown's nested lists, the
outer one classed "toc", linking to the toc_N ids.

Verified: swift test; matches hoedown on the corpus TOC document and on
headers that start deeper than later ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
With "Render newline literally" on, every soft break is <br>.
Paragraphs match hoedown; tight list items now break too (hoedown's
rule lived in its paragraph renderer and skipped them).

Verified: swift test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Math is now protected with characters no document uses (Private Use
Area, padded with control characters to the same byte length), so the
renderer finds it in cmark's text and emits it as hoedown did: escaped,
in MathJax 2's \( \) and \[ \] delimiters, $$ as display math when
inline dollars are on or it's alone in its paragraph. A span over
several lines is emitted once. FR-14 is reworded to match (MathJax 2
doesn't read the original $ delimiters).

Verified: swift test; display, inline, bracketed, in-text $$, header
and table-cell math render byte-for-byte like hoedown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
With the setting on, bare URLs, www. links and emails render exactly as
hoedown did (cmark-gfm's autolink extension, attached only then); off,
only <url> links. Also fixes FR-14's delimiters in the PRD.

Verified: swift test (autolinks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
cmark's smart mode, on only with the setting, gives the same quotes,
dashes and ellipses as hoedown's smartypants (as characters rather than
entities) and leaves code alone.

Verified: swift test (smartPunctuation).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
With their settings on, plain text runs get <mark> and <sup> by the
rules the editor highlighting uses (ExtendedSyntax, hoedown's), never
in code. Only within one text run, so not around other formatting.

Verified: swift test; matches hoedown byte for byte, including the
cases that stay literal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
With sourceLines on (the preview), paragraphs, headers, quotes, lists
and items, code blocks, tables and rules carry data-source-line, for
Phase 4's scroll sync. HTML export and Copy HTML strip them
(PageBuilder.withoutSourceLines); PDF export prints the page.

Verified: swift test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
With markdownEngine = swiftMarkdown, Renderer.parse builds only the
document model (with source lines) and fills ParseResult from it, so
one cmark-gfm parse serves the preview and the editor; hoedown isn't
run. PageBuilder is unchanged.

Verified: swift test twice; Release build; help.md's code section in a
throwaway build renders pixel-identical with either engine (0 of 666,000
preview pixels differ), Prism coloring included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Every RendererTests case takes the engine as an argument, and the
empty-defaults standard-formatting test checks cmark-gfm too. No
expectation needed changing.

Verified: swift test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
HelpDocumentRenderingTests take the engine as an argument: help.md's
standard Markdown with default settings, its opt-in syntax staying
plain, and every option rendering its example (highlight, superscript,
autolink, smart punctuation, task lists, [TOC], math) pass on cmark-gfm
as on hoedown. Entities are decoded so either form compares alike.
With HTMLRendererTests this covers the plan's opt-in, TOC, task list,
front matter, footnote and code-block tests.

Verified: swift test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
A live test with the new engine: the preview page has data-source-line
on its blocks, while the HTML export and the PDF printed from the page
don't carry them.

Verified: swift test, the live suite twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
HelpDocumentPreviewTests takes the engine as an argument: on cmark-gfm,
help.md's Mermaid diagrams draw without errors, Graphviz replaces its
block, Prism colors Swift, JavaScript and Python, images load and task
checkboxes show, as on hoedown.

Verified: swift test, the live suite twice; in a throwaway build with
the new engine, MathJax typesets the corpus math document (display,
bracketed, inline $, math with _ and *) and Mermaid draws the corpus
diagrams.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
All 96 remaining differences across 21 documents and 9 settings are
intended (CommonMark/GFM rules, hoedown bugs such as doubled <br> and
footnotes read as superscript, $5-and-$10 not being math) and are
listed with reasons in Resources/reviewed-html-diffs.json; the
comparison now asserts, failing on a new or vanished difference. Users'
summary in docs/MACDOWN-PORT.md. The review found two real problems,
fixed: help.md's "($)" opened a math span with dollars on (now a code
span), and TOC entries copied header anchors (now dropped).

Verified: swift test 196/196 twice; Release build; a throwaway build
with the new engine shows a clean TOC with emphasis and math.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
…FR-30)

When the preview marks blocks with data-source-line, the page reports
each block's top and line; the editor finds the top of each of those
lines in its own layout; ScrollMap pairs them by line number, skipping
lines one side lacks or that aren't increasing. hoedown documents keep
the header and image anchors.

Verified: swift test, scroll suites twice; Release build; a live test
centers a line in one pane and finds it centered in the other, in both
directions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
A live test with the new engine, a 300-point editor and a 600-point
preview: help.md (with its fence-in-code-span case) and the image-heavy
corpus document stay aligned at 30%, 50% and 70%, editor to preview
exactly and preview to editor within half a line (where a short preview
block faces a long wrapped one, a page pixel is many editor pixels).

Verified: swift test, scroll suites twice; in a throwaway build a
temporary probe (removed) scrolled the image document's editor halfway
and the preview showed the same lines at the same height.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
markdownEngine now defaults to swiftMarkdown (cmark-gfm); hoedown stays
selectable for one release through the hidden setting. Tests of the
hoedown scroll anchors pin that engine, and the resize and re-render
scroll tests run on both. The docs describe cmark-gfm as the default.
Removing hoedown waits for that release.

Verified: swift test twice; Release build; a throwaway build with no
setting renders "#Not a header" as text and [X] as checked (cmark-gfm).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
swift-cmark's COPYING, now that cmark-gfm renders by default. The
hoedown and PEG Markdown Highlight licenses go when they're removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
CLAUDE.md's render pipeline, editor and test sections describe the
document model, HTMLRenderer and HighlightMapper on cmark-gfm, drop the
byte-identical output rule (hoedown's markup is kept where CommonMark
agrees; differences are reviewed), and note hoedown as the one-release
fallback. The port doc's class map and differences add the renderer and
the plain-text limit of highlight and superscript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
help.md now says a backslash also breaks a line, underscores don't
emphasize inside words, a list can follow a paragraph, - and + start
lists of their own, items may end in ) and an ordered list starts at its
first number. Footnotes, the Inline Formatting table and Smart
punctuation were already accurate. The reviewed hoedown/cmark
differences for help.md are refreshed (same categories).

Verified: swift test 199/199 twice; the lists section renders as
described in a throwaway build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
The hoedown/PEG removal, its license and doc cleanups, and the final
gate after it wait for the release that ships cmark-gfm as default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Xcode's build marks package strings it doesn't extract as stale; every
other entry already is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Final-review fixes. A footnote's sub-renderer had its own Prism
language collector, so code only in a footnote rendered uncolored, and
its header count wasn't carried back; both are now shared. The
benchmark now builds the model as the preview does (with source
lines), which showed highlight and superscript each adding ~6 ms on
10,000 lines: String.contains on every text node. A byte scan, and a
marker check before the mapper's regex passes, bring it back.

Verified: swift test 200/200; benchmark on 10k lines: 16.8 ms at
defaults, 19.3 ms with math, highlight and superscript, PEG 19.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
The whole model, preview HTML included: 16.8 ms at defaults and 19.3 ms
with math and opt-ins on 10,000 lines, against PEG's 19.3 for
highlighting alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
Every user defaults key of MacDown's preferences is a case of
PreferenceSettingKey, its raw value the original app's key. save(_:_:),
the reads, editorPreferencesToObserve (Set<PreferenceSettingKey>),
setupEditor and preferenceDidChange, the notifications
(Notification.preferenceKey), @AppStorage and the tests use it. The
editor's text-checking settings pair their NSTextView key paths with
explicit cases instead of building "editor…" names, and retired keys
are cases too (left in user defaults, FR-36).

Verified: swift test 201/201, including a test that each key keeps the
original name; Release build; a throwaway build loads the saved theme,
style and smart punctuation as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
responds(to:) and forwardingTarget(for:) are NSObject's nonisolated
methods, but read main-actor state. WindowCloseGuard's original delegate
is stored nonisolated(unsafe) (set and read on the main thread);
DocumentResponder's selector set is nonisolated, and its controller
check runs main-actor-isolated (false off the main thread).

Verified: swift build and xcodebuild with no isolation warnings;
swift test 201/201 twice (saving and closing tests included).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLgm3XqgEyhQkA49mRsEnP
@levous
levous merged commit b2115e6 into main Oct 10, 2026
@levous
levous deleted the feature/replace-hoedown branch October 12, 2026 19:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant