Skip to content

fix(repl-sdk): correct the whitespace escape in formatDefaultId - #2215

Merged
NullVoxPopuli merged 2 commits into
NullVoxPopuli:mainfrom
gitKrystan:fix/heading-id-whitespace-escape
Aug 15, 2026
Merged

NullVoxPopuli merged 2 commits into
NullVoxPopuli:mainfrom
gitKrystan:fix/heading-id-whitespace-escape

Conversation

@gitKrystan

@gitKrystan gitKrystan commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Draft — tiny one, happy to close if you'd rather handle it differently.

formatDefaultId in packages/repl-sdk/src/compilers/markdown/heading-id.js reads:

return kebabCase(value.replaceAll(/\\s+/g, ' ').trim());

/\\s+/g matches a literal backslash followed by one or more s, not whitespace. The intent looks like collapsing runs of whitespace before kebab-casing.

Mostly a no-op. kebabCase already splits on whitespace, so realistic headings slug identically either way. The observable difference is a heading containing a literal \s, where the s got eaten:

## a \s b     before: id="a-b"     after: id="a-s-b"

Tests

Two cases in parse.test.ts:

  • runs of whitespace collapse to a single - — passes either way, pinning the intent so a future refactor of formatDefaultId can't quietly change it
  • a literal \s is text, not whitespace — the actual regression test

I verified the second genuinely fails without the fix (expected '<h2 id="a-b">a \s b</h2>' to contain 'id="a-s-b"') rather than just passing alongside it.

Tests  21 passed (21)     # packages/repl-sdk parse.test.ts

Full repl-sdk suite is unchanged otherwise: 1 test file (compilers.ember.onUnhandled.test.ts) fails to load and lint:types reports 3 errors, both identical on clean main in my checkout.

No README change — this doesn't alter documented behavior.

Alternative

Two reasonable outcomes and I don't have a strong preference:

  1. This PR — fix the escape, keep the call.
  2. Drop the replaceAll entirely, since kebabCase makes it redundant. (Note this would change the \s case again, to a-s-b as well — same result, one fewer moving part.)

Note

Related but independent: #2216 adds a configurable heading slugger, which needs whitespace actually collapsed — a space-to-dash slugger emits hello--there without it. It normalizes at a different call site, so the two don't conflict and either can merge first.


🤖 Drafted by Claude (Opus 5) for @gitKrystan, while tracking down why heading anchors differed between our docs site and GitHub (soxhub/auditboard-frontend#42085).

`/\\s+/g` matches a literal backslash followed by one or more `s`, not
whitespace. The intent reads as collapsing runs of whitespace before
kebab-casing the heading text.

In practice this is a no-op: `kebabCase` already splits on whitespace, so
every realistic heading slugs identically either way. The one input that
differs is a heading containing a literal `\s`:

  "a \s b"   before: a-b     after: a-s-b

Happy to close this in favour of dropping the `replaceAll` entirely, since
`kebabCase` makes it redundant — I went with the minimal change.
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Two cases for the escape fix:

- runs of whitespace in a heading collapse to a single `-` in the id (passes
  either way, since kebabCase also splits on whitespace -- pins the intent so a
  future refactor of formatDefaultId cannot quietly change it)
- a literal `\s` in a heading is text, not whitespace

The second is the actual regression test. With the old `/\\s+/g`,
`## a \s b` produced `id="a-b"` -- the `s` was consumed as part of the match.
@NullVoxPopuli NullVoxPopuli added the bug Something isn't working label Aug 14, 2026
@NullVoxPopuli
NullVoxPopuli marked this pull request as ready for review August 14, 2026 23:59
@github-actions

Copy link
Copy Markdown
Contributor
Project Preview URL1 Manage
Limber https://fix-heading-id-whitespace-es.limber-glimdown.pages.dev on Cloudflare
Tutorial https://fix-heading-id-whitespace-es.limber-glimmer-tutorial.pages.dev on Cloudflare

Logs

Footnotes

  1. if these branch preview links are not working, please check the logs for the commit-based preview link. There is a character limit of 28 for the branch subdomain, as well as some other heuristics, described here for the sake of implementation ease in deploy-preview.yml, that algo has been omitted. The URLs are logged in the wrangler output, but it's hard to get outputs from a matrix job. ↩

@NullVoxPopuli
NullVoxPopuli merged commit b51acb2 into NullVoxPopuli:main Aug 15, 2026
14 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 15, 2026
gitKrystan added a commit to gitKrystan/limber that referenced this pull request Aug 15, 2026
@gitKrystan asked whether the whitespace normalization was needed at all with
github-slugger. It wasn't -- and it was making us diverge from GitHub.

Checked against rehype-slug, which is the github-slugger reference:

  ##   Hello    World      -> id="hello----world"      (does NOT collapse)
  ## Hello *there*         -> id="hello-there"         (no separator inserted)
  ## `setupMirage` and more -> id="setupmirage-and-more"

So the actual defect was `extractText` joining children with a space, adding one
the markdown never had: `## Hello *there*` extracted as `'Hello  there'` and
slugged as `hello--there`. Joining with '' fixes that at the source. Collapsing
author-written runs on top of it was wrong -- GitHub keeps those.

Drops normalizeText and the test asserting collapsing (which came from NullVoxPopuli#2215,
back when kebabCase was the target). Adds two tests pinning parity with the
rehype-slug results above.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants