Skip to content

feat: gascity acceptance and cleanup (hy-sadjq) - #39

Merged
philcunliffe merged 1 commit into
integration/hypaware-plugin-surfacesfrom
polecat/hy-sadjq
May 26, 2026
Merged

feat: gascity acceptance and cleanup (hy-sadjq)#39
philcunliffe merged 1 commit into
integration/hypaware-plugin-surfacesfrom
polecat/hy-sadjq

Conversation

@philcunliffe

Copy link
Copy Markdown
Contributor

Summary

  • Reframes gascity exclusion as default-activation policy (not invisibility)
  • Adds catalog test for gascity contributions
  • Adds detach coverage to gascity smoke

Merges polecat/hy-sadjq → integration/hypaware-plugin-surfaces

Bead: hy-sadjq

Reframe gascity from "excluded and invisible" to "excluded from default
activation but discoverable through the plugin catalog." Update test
and smoke labels/comments to reflect that gascity absence in status is
about the config scenario, not a blanket prohibition. Add catalog test
for gascity contributions (source, commands, dataset, init preset,
skill). Add detach coverage to the gascity smoke test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@philcunliffe
philcunliffe merged commit bf503c4 into integration/hypaware-plugin-surfacesMay 26, 2026
6 checks passed
@philcunliffe
philcunliffe deleted the polecat/hy-sadjq branch May 26, 2026 04:30
philcunliffe added a commit that referenced this pull request May 26, 2026
* chore: open plugin surface integration branch
* feat: config-backed sink materialization (hy-9d39h) (#37)
* feat: config-backed sink materialization (hy-9d39h)
Add production runtime materialization for configured sinks, so
daemon boot and CLI dispatch automatically instantiate sinks from
config.sinks entries without manual kernel.sinks.instantiate() calls.
- Add `fromProvider(provider, name, range)` to capability registry for
provider-specific capability lookup
- New `src/core/sinks/materialize.js` resolves writer/destination/encoder
capabilities and calls sinks.instantiate() for all three sink shapes
(request, blob, table-format)
- Wire materializeSinks into daemon runtime (after plugin activation)
and CLI dispatch (after boot)
- Update local_parquet_export smoke to use config-backed sinks instead
of manual instantiation, proving the production path works
- 19 new tests covering all sink shapes, error modes, and fromProvider
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(types): resolve CI typecheck failures for sink materialization (hy-9d39h)
- Add tmpRoot to RunDaemonOptions interface
- Add fromProvider to CapabilityRegistry interface and activation facade
- Capture config.sinks before withSpan callback to preserve TS narrowing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: plugin-owned product logic — client descriptors, generalized skills, hook migration (hy-bv310) (#38)
Move client-specific and plugin-specific product behavior out of core
and behind plugin-owned manifest descriptors:
- Add `contributes.client` descriptor to plugin manifests with
skill_dir, attach_probe, and required_upstreams fields
- Extend plugin catalog to extract client descriptors from manifests
- Replace hardcoded client loops in status.js with catalog-driven
descriptor iteration for attach probing
- Generalize `hyp skills install` to resolve skill directories from
catalog instead of hardcoding .claude/skills and .codex/skills
- Move `claude-hook session-context` command from core to the Claude
plugin — registered during plugin activation
- Rewrite V1 advisory diagnostics to derive client/upstream checks
and encoder/blob-store checks from catalog metadata instead of
hardcoded plugin name sets
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: gascity acceptance and plugin-surface cleanup (hy-sadjq) (#39)
Reframe gascity from "excluded and invisible" to "excluded from default
activation but discoverable through the plugin catalog." Update test
and smoke labels/comments to reflect that gascity absence in status is
about the config scenario, not a blanket prohibition. Add catalog test
for gascity contributions (source, commands, dataset, init preset,
skill). Add detach coverage to the gascity smoke test.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: replace inline import types with @import declarations and sanitize env var derivation (hy-hfne7) (#40)
Replace 20+ inline import('...') type annotations with @import JSDoc
declarations at file tops across validate.js, status.js, materialize.js,
and sink-materialize.test.js.
Fix resolveClientSettingsPath to sanitize non-alphanumeric characters
(hyphens, dots, etc.) in client names when deriving env var keys, so
that hyphenated clients like 'claude-desktop' produce valid POSIX env
var names (CLAUDE_DESKTOP_HOME instead of CLAUDE-DESKTOP_HOME).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address PR 35 diagnostic review feedback
* fix: surface CLI sink materialization warnings
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
philcunliffe pushed a commit that referenced this pull request Aug 11, 2026
…pellings
Three heading-id divergences from the pandoc that master shelled out to. Each
one still renders the heading correctly, so the only visible symptom is a
pandoc-era `#anchor` that silently dangles. All measured against a real pandoc
3.1.11 binary with one heading per document, so no de-dup counter drift can
cascade between cases.
Uppercase `&#X...;` never decoded. The entity regex spelled the hex alternative
`#x[0-9a-fA-F]+`, which made the existing `body[1] === 'X'` guard unreachable:
marked's escaper passes the uppercase form through (its no-encode pattern
spells the prefix `#[Xx]`), so it arrived verbatim and leaked its digits.
`## A &#X41; B` minted `a-x41-b` against pandoc's `a-a-b`. This also left
`&#X110000;` inside the out-of-range window unsubstituted, leaking digits where
the guard's own comment says it must not, so that comment is corrected too.
`&apos;` was missing from NAMED_ENTITIES, so `## What&apos;s next` minted
`whataposs-next` against pandoc's `whats-next` - a single-entry hole in the
exact case the decoder's docstring uses to motivate itself, while `&#39;`,
`&#x27;` and `&rsquo;` were all already right. The rest of the HTML5
ASCII-punctuation block had the same hole and is added with it: all 27 names
were verified individually against pandoc rather than taken on trust.
`<br>` was deleted outright by the general tag strip where pandoc's reader
yields a space, welding `## Line one<br/>Line two` into `line-oneline-two`.
It now substitutes a space before the tag strip and after the whitespace
collapse, because pandoc counts that space as its own token: `## A <br /> B`
mints `a---b`, not `a-b`. The match is lowercase-only, since pandoc treats
`<BR>` as raw inline HTML contributing nothing. Every other tag still vanishes
without a trace; `<span>a</span>b` and `A<em>B</em>C` are pinned so a future
general tag-to-space rule cannot regress them. Moving the collapse ahead of the
tag strip also stops a tag's own inner whitespace being merged away, which
pandoc likewise counts as its own token. The heading renderer's comment claiming
pandoc emits no id for a `<br>`-only heading was wrong and is corrected: pandoc
mints `-`, and now so does this.
Two measured divergences are left alone deliberately. U+2028, U+2029 and U+FEFF
are in JS's `\s` but pandoc drops them, each minting one extra hyphen; the
literal-authored spellings break further upstream in marked's block parser
(no heading is emitted at all), so a character-class fix would only close half
the case. And pandoc slugs a Markdown image's alt text (`## ![img](x.png)` ->
`img`) but emits no id for a raw-HTML `<img alt="...">`, which this renderer
already matches; marked emits byte-identical HTML for both, so there is no
string-level discriminator and a fix belongs at the token level, not here.
LLP 0208's footnote consequence is tightened: it described only the multi-token
definition, which survives as literal text. A single-token one (`[^1]: notes.md`)
is parsed as a link reference definition instead, so the definition line
disappears and the reference becomes a live link that rewriteHrefs then rewrites.
Co-Authored-By: Claude <noreply@anthropic.com>
bgmcmullen added a commit that referenced this pull request Aug 11, 2026
* Report renderer drops pandoc and converts in process (LLP 0205)
Supersedes LLP 0196 open question 1's keep-pandoc resolution through the escape
hatch that resolution named for itself: the only pandoc property the component
vocabulary relies on is gfm passing raw HTML through untouched, "so in-process
rendering stays available if the dependency ever becomes a problem." Server-side
generation (hypaware-server LLP 0112) made it one twice over: pandoc would be
that daemon's first non-npm binary, and execFileSync blocks the single thread
every customer org shares, measured at ~40ms per page.
The substitution was measured before it was made: all 94 files of a real
reports tree converted under both engines; 66 structurally identical, 28
differing only in pandoc's syntax-highlighting markup, 0 genuine differences.
After the swap the same tree renders to 94 pages with zero structural diffs
against the pandoc reference, and the style-before-theme link order holds on
every page.
- pandocPage becomes htmlPage: marked (pinned, zero transitive deps) plus an
explicit standalone template doing what -s did, with assets/head.html inlined
after the base stylesheet link so theme.css keeps loading last
(LLP 0196#theme-layer).
- A renderer override reproduces pandoc's heading ids, -1 suffixes included, so
existing in-page anchors keep resolving.
- hasPandoc, the CLI preflight, the help text, the CI apt-get step, and the
skill's prerequisite all go. The render tests lose their skip guard and run
everywhere: a renderer with no external dependency has no excuse for untested
paths. 3892 tests pass, none skipped for pandoc.
- Syntax highlighting is the one visible change, light mode only: the
stylesheet's token rules sit in its dark-mode block and flattened pandoc's
spans to one colour anyway. language-* classes survive for later colour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Report render: pandoc-parity heading ids and table alignment, pinned by tests
Round-1 review of #705. The pandoc-to-marked swap was measured structurally,
which is exactly the comparison that cannot see either of these: one changes an
attribute's spelling, the other changes text the browser never shows.
- headingId was fed marked's already-escaped inline HTML, so `&` reached the
slug rule as `&amp;` and left its letters behind: `Cost & Usage` minted
cost-amp-usage where pandoc mints cost--usage, and every authored
[link](#whats-next) pointed at what39s-next. It now strips tags, unescapes,
collapses whitespace runs before dropping punctuation (a dropped `&` leaves
two spaces and pandoc emits both hyphens), and hyphenates per space rather
than per run. Unicode letter and number classes keep `Café résumé` and
`日本語` intact, as pandoc does. Re-verified 24/24 against pandoc 3.1.11
`-f gfm -t html5`, `-1` repeat suffixes included.
- A tablecell override restates column alignment as pandoc's inline
`style="text-align: ..."`. marked's built-in emits `align="right"`, a
presentational hint the cascade ranks below assets/style.css's
`th, td { text-align: left }`, so every right-aligned numeric column and
every centred column silently rendered left, against tabular-nums.
- test/core/report-render.test.js gains a golden page over the authoring
vocabulary (component block byte-for-byte, aligned table, fenced block with
its language class, headings carrying `&`, `'` and `/`) plus an anchor
integrity check: every href="#..." must match an id on the page. Both fixes
above fail this test before they land.
- src/core/reports/README.md drops the stale "pandoc is still a hard
dependency" rule and pandoc's `-H` for head.html. It ships in the package.
- assets/style.css suppresses the list marker on task lists, which pandoc's
template did through a task-list class marked does not emit. Footnotes are a
real loss (marked's gfm has none) and are accepted and recorded rather than
extension-patched.
- LLP 0208 narrows its measurement claim to what is true of tables, and records
the footnote and task-list consequences. LLP 0196 gains a header-level
Superseded-in-part-by forward-ref.
Co-Authored-By: Claude <noreply@anthropic.com>
* Report render: fix round-2 findings on heading ids, entities, and task lists
Four residual round-2 findings on PR #705's pandoc-to-marked swap, each
verified against a real pandoc 3.1.11 binary.
Finding A: heading ids dropped combining marks (`[^\p{L}\p{N}_\s-]`
stripped `\p{M}`), mangling Indic, Thai, Arabic/Hebrew, Vietnamese and
decomposed Latin text unconditionally. headingId now NFC-normalizes
before lowercasing and keeps `\p{M}` in the retained class, matching
pandoc on decomposed "Café résumé", Turkish dotted-I, and Devanagari.
Finding B: unescapeHtml only covered the five entities marked itself
emits, so an author-written entity (`&rsquo;`, `&#x27;`, ...) leaked its
raw letters/digits into the slug and could dangle an in-page anchor.
Replaced the five-entity table with a general decoder: numeric
(`&#NNN;`, `&#xHH;`) plus a bounded, case-sensitive "HTML4" named-entity
table (Latin-1, Greek, typography), sourced from the WHATWG entity list
and stored as codepoints rather than literal characters. Case-sensitive
matching was chosen over the case-insensitive suggestion after measuring
pandoc itself: it decodes `&AMP;` (a real legacy dual-case alias) but not
`&MDASH;` or `&RSQUO;`, and a case-fold would wrongly collide distinct
entries like `&Alpha;`/`&alpha;`. Fixing this also surfaced a second bug:
whitespace-run collapsing had to move to before entity decoding, or a
decoded `&nbsp;` merged with real spaces around it into one hyphen
instead of pandoc's one-hyphen-per-token count (verified: `A &nbsp; B`
-> `a---b`).
Finding C: the `ul:has(> li > input[type="checkbox"])` rule only matched
"tight" task lists; a blank-line-separated ("loose") list wraps the
checkbox in `li > p > input`, so it kept its bullet. The selector now
matches both shapes. Also matched pandoc's cheap-to-copy indent
behavior: pandoc's template kept the list's normal indent and pulled the
checkbox left with a negative margin, rather than zeroing padding-left.
LLP 0208's task-list bullet is corrected to name both shapes instead of
just one.
Finding D: a heading that reduces to nothing (`## <emoji>`) minted
`id=""`; pandoc emits no id attribute. The heading renderer now omits
the attribute when the slug is empty, while still running pandoc's own
de-dup counter against the empty base for a repeat (`id="-1"`).
Each fix is pinned in test/core/report-render.test.js, verified to fail
against the pre-fix renderer before the change landed.
Co-Authored-By: Claude <noreply@anthropic.com>
* Report render: an out-of-range numeric entity no longer aborts the build
Two heading-id findings from the round-2 entity decoder, both measured against
a real pandoc 3.1.11 binary.
An out-of-range numeric character reference in a heading crashed the whole
render. `unescapeHtml` called `String.fromCodePoint` behind only a
`Number.isFinite` guard, and that throws above U+10FFFF. The window is
reachable rather than theoretical: marked's escaper passes numeric references
of up to 7 decimal or 6 hex digits through untouched, so every value in
`&#x110000;`-`&#xFFFFFF;` and `&#1114112;`-`&#9999999;` arrived verbatim.
The blast radius is what made it worth fixing now: `renderReports` wipes
`html/` before building and builds in sorted slug order, so one bad heading in
one report destroyed the already-built pages of every report sorting at or
after it and left the landing page stale. Reports are model-authored, so the
input is not fully under a human's control. pandoc substitutes U+FFFD, which
its slug then strips, minting `a--b` for `## A &#x110000; B`, so the guard
substitutes U+FFFD too rather than returning the entity intact, which would
leak its digits into the id. Lone surrogates and U+10FFFF stay on the
`fromCodePoint` path, where they already matched pandoc.
`headingId` also trimmed after the punctuation strip, which pandoc does not
do. Any heading starting or ending with stripped punctuation next to a space
minted a different id: `## 🚀 Rollout plan` gave `rollout-plan` where pandoc
gives `-rollout-plan`, and `## end &` gave `end` where pandoc gives `end-`.
Emoji-led headings are ordinary in model-authored reports, so every pandoc-era
`#-rollout-plan` anchor dangled silently, contradicting LLP 0208's promise
that existing in-page anchors keep resolving. The trim is dropped, which
restores parity across the whole measured table, including the all-whitespace
case (pandoc mints `-` for `## ( )`, not no id) and the de-dup counters that
run off those degenerate bases. Headings that reduce to the truly empty string
still carry no id, and the reader has already trimmed authored outer
whitespace, so no stray leading hyphen appears.
Regression tests cover both, plus the amplifier: a malformed entity in one
report must not destroy the pages of the reports that sort after it.
Co-Authored-By: Claude <noreply@anthropic.com>
* Report render: heading ids keep pandoc's `&#X`, `&apos;` and `<br>` spellings
Three heading-id divergences from the pandoc that master shelled out to. Each
one still renders the heading correctly, so the only visible symptom is a
pandoc-era `#anchor` that silently dangles. All measured against a real pandoc
3.1.11 binary with one heading per document, so no de-dup counter drift can
cascade between cases.
Uppercase `&#X...;` never decoded. The entity regex spelled the hex alternative
`#x[0-9a-fA-F]+`, which made the existing `body[1] === 'X'` guard unreachable:
marked's escaper passes the uppercase form through (its no-encode pattern
spells the prefix `#[Xx]`), so it arrived verbatim and leaked its digits.
`## A &#X41; B` minted `a-x41-b` against pandoc's `a-a-b`. This also left
`&#X110000;` inside the out-of-range window unsubstituted, leaking digits where
the guard's own comment says it must not, so that comment is corrected too.
`&apos;` was missing from NAMED_ENTITIES, so `## What&apos;s next` minted
`whataposs-next` against pandoc's `whats-next` - a single-entry hole in the
exact case the decoder's docstring uses to motivate itself, while `&#39;`,
`&#x27;` and `&rsquo;` were all already right. The rest of the HTML5
ASCII-punctuation block had the same hole and is added with it: all 27 names
were verified individually against pandoc rather than taken on trust.
`<br>` was deleted outright by the general tag strip where pandoc's reader
yields a space, welding `## Line one<br/>Line two` into `line-oneline-two`.
It now substitutes a space before the tag strip and after the whitespace
collapse, because pandoc counts that space as its own token: `## A <br /> B`
mints `a---b`, not `a-b`. The match is lowercase-only, since pandoc treats
`<BR>` as raw inline HTML contributing nothing. Every other tag still vanishes
without a trace; `<span>a</span>b` and `A<em>B</em>C` are pinned so a future
general tag-to-space rule cannot regress them. Moving the collapse ahead of the
tag strip also stops a tag's own inner whitespace being merged away, which
pandoc likewise counts as its own token. The heading renderer's comment claiming
pandoc emits no id for a `<br>`-only heading was wrong and is corrected: pandoc
mints `-`, and now so does this.
Two measured divergences are left alone deliberately. U+2028, U+2029 and U+FEFF
are in JS's `\s` but pandoc drops them, each minting one extra hyphen; the
literal-authored spellings break further upstream in marked's block parser
(no heading is emitted at all), so a character-class fix would only close half
the case. And pandoc slugs a Markdown image's alt text (`## ![img](x.png)` ->
`img`) but emits no id for a raw-HTML `<img alt="...">`, which this renderer
already matches; marked emits byte-identical HTML for both, so there is no
string-level discriminator and a fix belongs at the token level, not here.
LLP 0208's footnote consequence is tightened: it described only the multi-token
definition, which survives as literal text. A single-token one (`[^1]: notes.md`)
is parsed as a link reference definition instead, so the definition line
disappears and the reference becomes a live link that rewriteHrefs then rewrites.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: test <test@test.com>
philcunliffe added a commit that referenced this pull request Aug 11, 2026
…710)
* Report renderer drops pandoc and converts in process (LLP 0205)
Supersedes LLP 0196 open question 1's keep-pandoc resolution through the escape
hatch that resolution named for itself: the only pandoc property the component
vocabulary relies on is gfm passing raw HTML through untouched, "so in-process
rendering stays available if the dependency ever becomes a problem." Server-side
generation (hypaware-server LLP 0112) made it one twice over: pandoc would be
that daemon's first non-npm binary, and execFileSync blocks the single thread
every customer org shares, measured at ~40ms per page.
The substitution was measured before it was made: all 94 files of a real
reports tree converted under both engines; 66 structurally identical, 28
differing only in pandoc's syntax-highlighting markup, 0 genuine differences.
After the swap the same tree renders to 94 pages with zero structural diffs
against the pandoc reference, and the style-before-theme link order holds on
every page.
- pandocPage becomes htmlPage: marked (pinned, zero transitive deps) plus an
explicit standalone template doing what -s did, with assets/head.html inlined
after the base stylesheet link so theme.css keeps loading last
(LLP 0196#theme-layer).
- A renderer override reproduces pandoc's heading ids, -1 suffixes included, so
existing in-page anchors keep resolving.
- hasPandoc, the CLI preflight, the help text, the CI apt-get step, and the
skill's prerequisite all go. The render tests lose their skip guard and run
everywhere: a renderer with no external dependency has no excuse for untested
paths. 3892 tests pass, none skipped for pandoc.
- Syntax highlighting is the one visible change, light mode only: the
stylesheet's token rules sit in its dark-mode block and flattened pandoc's
spans to one colour anyway. language-* classes survive for later colour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Report render: pandoc-parity heading ids and table alignment, pinned by tests
Round-1 review of #705. The pandoc-to-marked swap was measured structurally,
which is exactly the comparison that cannot see either of these: one changes an
attribute's spelling, the other changes text the browser never shows.
- headingId was fed marked's already-escaped inline HTML, so `&` reached the
slug rule as `&amp;` and left its letters behind: `Cost & Usage` minted
cost-amp-usage where pandoc mints cost--usage, and every authored
[link](#whats-next) pointed at what39s-next. It now strips tags, unescapes,
collapses whitespace runs before dropping punctuation (a dropped `&` leaves
two spaces and pandoc emits both hyphens), and hyphenates per space rather
than per run. Unicode letter and number classes keep `Café résumé` and
`日本語` intact, as pandoc does. Re-verified 24/24 against pandoc 3.1.11
`-f gfm -t html5`, `-1` repeat suffixes included.
- A tablecell override restates column alignment as pandoc's inline
`style="text-align: ..."`. marked's built-in emits `align="right"`, a
presentational hint the cascade ranks below assets/style.css's
`th, td { text-align: left }`, so every right-aligned numeric column and
every centred column silently rendered left, against tabular-nums.
- test/core/report-render.test.js gains a golden page over the authoring
vocabulary (component block byte-for-byte, aligned table, fenced block with
its language class, headings carrying `&`, `'` and `/`) plus an anchor
integrity check: every href="#..." must match an id on the page. Both fixes
above fail this test before they land.
- src/core/reports/README.md drops the stale "pandoc is still a hard
dependency" rule and pandoc's `-H` for head.html. It ships in the package.
- assets/style.css suppresses the list marker on task lists, which pandoc's
template did through a task-list class marked does not emit. Footnotes are a
real loss (marked's gfm has none) and are accepted and recorded rather than
extension-patched.
- LLP 0208 narrows its measurement claim to what is true of tables, and records
the footnote and task-list consequences. LLP 0196 gains a header-level
Superseded-in-part-by forward-ref.
Co-Authored-By: Claude <noreply@anthropic.com>
* Report render: fix round-2 findings on heading ids, entities, and task lists
Four residual round-2 findings on PR #705's pandoc-to-marked swap, each
verified against a real pandoc 3.1.11 binary.
Finding A: heading ids dropped combining marks (`[^\p{L}\p{N}_\s-]`
stripped `\p{M}`), mangling Indic, Thai, Arabic/Hebrew, Vietnamese and
decomposed Latin text unconditionally. headingId now NFC-normalizes
before lowercasing and keeps `\p{M}` in the retained class, matching
pandoc on decomposed "Café résumé", Turkish dotted-I, and Devanagari.
Finding B: unescapeHtml only covered the five entities marked itself
emits, so an author-written entity (`&rsquo;`, `&#x27;`, ...) leaked its
raw letters/digits into the slug and could dangle an in-page anchor.
Replaced the five-entity table with a general decoder: numeric
(`&#NNN;`, `&#xHH;`) plus a bounded, case-sensitive "HTML4" named-entity
table (Latin-1, Greek, typography), sourced from the WHATWG entity list
and stored as codepoints rather than literal characters. Case-sensitive
matching was chosen over the case-insensitive suggestion after measuring
pandoc itself: it decodes `&AMP;` (a real legacy dual-case alias) but not
`&MDASH;` or `&RSQUO;`, and a case-fold would wrongly collide distinct
entries like `&Alpha;`/`&alpha;`. Fixing this also surfaced a second bug:
whitespace-run collapsing had to move to before entity decoding, or a
decoded `&nbsp;` merged with real spaces around it into one hyphen
instead of pandoc's one-hyphen-per-token count (verified: `A &nbsp; B`
-> `a---b`).
Finding C: the `ul:has(> li > input[type="checkbox"])` rule only matched
"tight" task lists; a blank-line-separated ("loose") list wraps the
checkbox in `li > p > input`, so it kept its bullet. The selector now
matches both shapes. Also matched pandoc's cheap-to-copy indent
behavior: pandoc's template kept the list's normal indent and pulled the
checkbox left with a negative margin, rather than zeroing padding-left.
LLP 0208's task-list bullet is corrected to name both shapes instead of
just one.
Finding D: a heading that reduces to nothing (`## <emoji>`) minted
`id=""`; pandoc emits no id attribute. The heading renderer now omits
the attribute when the slug is empty, while still running pandoc's own
de-dup counter against the empty base for a repeat (`id="-1"`).
Each fix is pinned in test/core/report-render.test.js, verified to fail
against the pre-fix renderer before the change landed.
Co-Authored-By: Claude <noreply@anthropic.com>
* Report render: an out-of-range numeric entity no longer aborts the build
Two heading-id findings from the round-2 entity decoder, both measured against
a real pandoc 3.1.11 binary.
An out-of-range numeric character reference in a heading crashed the whole
render. `unescapeHtml` called `String.fromCodePoint` behind only a
`Number.isFinite` guard, and that throws above U+10FFFF. The window is
reachable rather than theoretical: marked's escaper passes numeric references
of up to 7 decimal or 6 hex digits through untouched, so every value in
`&#x110000;`-`&#xFFFFFF;` and `&#1114112;`-`&#9999999;` arrived verbatim.
The blast radius is what made it worth fixing now: `renderReports` wipes
`html/` before building and builds in sorted slug order, so one bad heading in
one report destroyed the already-built pages of every report sorting at or
after it and left the landing page stale. Reports are model-authored, so the
input is not fully under a human's control. pandoc substitutes U+FFFD, which
its slug then strips, minting `a--b` for `## A &#x110000; B`, so the guard
substitutes U+FFFD too rather than returning the entity intact, which would
leak its digits into the id. Lone surrogates and U+10FFFF stay on the
`fromCodePoint` path, where they already matched pandoc.
`headingId` also trimmed after the punctuation strip, which pandoc does not
do. Any heading starting or ending with stripped punctuation next to a space
minted a different id: `## 🚀 Rollout plan` gave `rollout-plan` where pandoc
gives `-rollout-plan`, and `## end &` gave `end` where pandoc gives `end-`.
Emoji-led headings are ordinary in model-authored reports, so every pandoc-era
`#-rollout-plan` anchor dangled silently, contradicting LLP 0208's promise
that existing in-page anchors keep resolving. The trim is dropped, which
restores parity across the whole measured table, including the all-whitespace
case (pandoc mints `-` for `## ( )`, not no id) and the de-dup counters that
run off those degenerate bases. Headings that reduce to the truly empty string
still carry no id, and the reader has already trimmed authored outer
whitespace, so no stray leading hyphen appears.
Regression tests cover both, plus the amplifier: a malformed entity in one
report must not destroy the pages of the reports that sort after it.
Co-Authored-By: Claude <noreply@anthropic.com>
* Report render: heading ids keep pandoc's `&#X`, `&apos;` and `<br>` spellings
Three heading-id divergences from the pandoc that master shelled out to. Each
one still renders the heading correctly, so the only visible symptom is a
pandoc-era `#anchor` that silently dangles. All measured against a real pandoc
3.1.11 binary with one heading per document, so no de-dup counter drift can
cascade between cases.
Uppercase `&#X...;` never decoded. The entity regex spelled the hex alternative
`#x[0-9a-fA-F]+`, which made the existing `body[1] === 'X'` guard unreachable:
marked's escaper passes the uppercase form through (its no-encode pattern
spells the prefix `#[Xx]`), so it arrived verbatim and leaked its digits.
`## A &#X41; B` minted `a-x41-b` against pandoc's `a-a-b`. This also left
`&#X110000;` inside the out-of-range window unsubstituted, leaking digits where
the guard's own comment says it must not, so that comment is corrected too.
`&apos;` was missing from NAMED_ENTITIES, so `## What&apos;s next` minted
`whataposs-next` against pandoc's `whats-next` - a single-entry hole in the
exact case the decoder's docstring uses to motivate itself, while `&#39;`,
`&#x27;` and `&rsquo;` were all already right. The rest of the HTML5
ASCII-punctuation block had the same hole and is added with it: all 27 names
were verified individually against pandoc rather than taken on trust.
`<br>` was deleted outright by the general tag strip where pandoc's reader
yields a space, welding `## Line one<br/>Line two` into `line-oneline-two`.
It now substitutes a space before the tag strip and after the whitespace
collapse, because pandoc counts that space as its own token: `## A <br /> B`
mints `a---b`, not `a-b`. The match is lowercase-only, since pandoc treats
`<BR>` as raw inline HTML contributing nothing. Every other tag still vanishes
without a trace; `<span>a</span>b` and `A<em>B</em>C` are pinned so a future
general tag-to-space rule cannot regress them. Moving the collapse ahead of the
tag strip also stops a tag's own inner whitespace being merged away, which
pandoc likewise counts as its own token. The heading renderer's comment claiming
pandoc emits no id for a `<br>`-only heading was wrong and is corrected: pandoc
mints `-`, and now so does this.
Two measured divergences are left alone deliberately. U+2028, U+2029 and U+FEFF
are in JS's `\s` but pandoc drops them, each minting one extra hyphen; the
literal-authored spellings break further upstream in marked's block parser
(no heading is emitted at all), so a character-class fix would only close half
the case. And pandoc slugs a Markdown image's alt text (`## ![img](x.png)` ->
`img`) but emits no id for a raw-HTML `<img alt="...">`, which this renderer
already matches; marked emits byte-identical HTML for both, so there is no
string-level discriminator and a fix belongs at the token level, not here.
LLP 0208's footnote consequence is tightened: it described only the multi-token
definition, which survives as literal text. A single-token one (`[^1]: notes.md`)
is parsed as a link reference definition instead, so the definition line
disappears and the reference becomes a live link that rewriteHrefs then rewrites.
Co-Authored-By: Claude <noreply@anthropic.com>
* Heading ids: every raw tag starting with `br` yields a space (#709)
Deferred findings from PR #705, which this stacks on.
Item 1 (fixed). pandoc 3.1.11's stringify keys on a raw inline's LEADING
TEXT, not on a parsed tag name, so ANY raw inline HTML token whose text
starts with the case-sensitive prefix `<br` becomes a space. The regex
here was spelled `<br(?:\s[^>]*)?\/?>` on the stated rationale that it
must not "swallow `<brand>`"; pandoc swallows `<brand>` too, so that
rationale was false and the code comment recording it is corrected.
Widening to `/<br[^>]*>/g` closes 11 divergences with no regressions,
verified case by case against a real pandoc 3.1.11 binary, one heading
per document so no de-dup counter drift can read as parity:
`## A <brand> B` a--b -> a---b (pandoc a---b)
`## A <brand>x</brand> B` a-x-b -> a--x-b (only the open tag counts)
`## A <bra> B` a--b -> a---b
`## A <br-x> B` a--b -> a---b
`## A <br2> B` a--b -> a---b
`## A <brand attr="y"> B` a--b -> a---b
`## A <brand/> B` a--b -> a---b
`## A <breakfast time> B` a--b -> a---b
`## A <brand > B` a--b -> a---b
`## A <brand>B` a-b -> a--b
`## A<brand> B` a-b -> a--b
The exclusions stay excluded: `<BR>`, `<Br>` and `<bR>` miss the
lowercase prefix, `</brand>` starts `</b`, and `<b>`, `<bold>`,
`<span>`, `<custom>` and `<q>` are not `br` at all. Widening is safe
because the regex never sees a string marked did not already accept as
a tag: marked escapes invalid tag syntax to entities first, and pandoc
likewise leaves `<br@>` as text.
Items 2 and 3 (recorded, not fixed). Both stay deferred, with the
measurement behind them now in the doc instead of only the PR thread.
Item 4. LLP 0208 records the footnote, task-list and highlighting
consequences but promised heading ids "so existing in-page anchors keep
resolving" without naming where that is inexact. A new
`#heading-id-gaps` consequence names the two remaining classes, and
`headingId` carries a `@ref` to it.
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix PR #710 review findings: revert LLP 0208 settled text, correct @ref gloss
LLP 0208 is Status: Active and merged to master, so its Decision paragraph
is a settled record; revert the added qualifier on the heading-id sentence
back to the master text and keep only the additive #heading-id-gaps
consequence section.
The @ref gloss in render.js overstated the constraint: the entity spellings
of the Unicode separators/BOM are decoded by unescapeHtml and are reachable
inside headingId, only the literal spellings break above this function in
marked's block parser. Reword the gloss to match the LLP section it cites.
* Correct BOM attribution in heading-id gap notes (#710 review round 2)
Round 2 verified against marked 18.0.9 through the real renderReports path
that a literal U+FEFF survives inline parsing and reaches headingId intact
(`## K <BOM> L` renders `<h2 id="k-l">`), so its divergence comes from the
slug rule's own `\s` handling, not from marked's block parser. Only a
literal U+2028/U+2029 breaks upstream, splitting the block so no heading
is produced at all.
Reword the @ref gloss and the LLP 0208 #heading-id-gaps sub-bullet so the
BOM is no longer cited as evidence of upstream breakage, and record that a
slug-rule-only change would close the BOM case in full. Comment and doc
accuracy only; no executable code, regex, or test changes.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Brendan McMullen <bgmcmullen@comcast.net>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: test <test@test.com>
Sign up for freeto 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

@philcunliffe