Skip to content

fix(docs): the tidying sweep in the code-example subsystem (#139b) - #398

Merged
IgorShevchik merged 3 commits into
mainfrom
claude/text-tools-docs-refactor-mb9dj7
Aug 26, 2026
Merged

fix(docs): the tidying sweep in the code-example subsystem (#139b)#398
IgorShevchik merged 3 commits into
mainfrom
claude/text-tools-docs-refactor-mb9dj7

Conversation

@IgorShevchik

@IgorShevchik IgorShevchik commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

The remaining seven items from #139, plus everything in its follow-up comment. #393 took the four correctness items; this is the rest.

Two of them turned out to be real bugs rather than tidying, and one reported item turned out not to be a bug at all.

For the mirror maintainer: four files are deleted here — docs/app/composables/cachedParseMarkdown.ts, docs/app/composables/useDocs.ts, docs/server/utils/extractSections.ts, docs/server/utils/normalizeComponentName.ts — along with HOOK_PLACEHOLDER and b24Config from useB24.ts. All six are unreferenced in this tree, checked by grepping the bare identifiers across docs/app and docs/server rather than just import lines, since Nuxt auto-imports mean a symbol can be used without one. If anything downstream layers on those exports, that is the diff to look at. upperName was reported as unused too and is not — it stays.


Two bug fixes (not tidying)

The prettier worker could hang the page

app/plugins/prettier.ts parks a [resolve, reject] pair under each message's uid and clears it only when a reply arrives. So a message that produces no reply is not a failed format — it is a promise that never settles, and CodeExample.vue awaits it forever. The worker had no error handling anywhere: a throw in prettier.format (an ordinary thing while a snippet is being edited) or a blocked CDN wedged the component.

Worth noting where the fix came from: upstream nuxt/ui, which this file was forked from, has already fixed this, and its shape is better than what I wrote first. I had built a message queue with manual draining on load failure; upstream has no queue at all — loading is lazy inside the format path, and one try/catch around the whole handler covers both a load failure and a format failure. Adopted rather than reinvented, and pinned to the upstream commit compared against (b751eae, 2026-08-25) so the claim stays checkable.

CodeExample.vue already falls back to unformatted code on a rejection, so the visible effect is: hangs forever → renders unformatted.

The dedent assumed two-space indentation

.slice(2) per line is right only while every example is written with exactly two spaces. A four-space example would have stayed indented; a tab-indented one would have had characters eaten. It is now measured from the region's non-blank lines (blank lines skipped — an empty line has no indentation and would drag the minimum to zero).

Output is byte-identical on all 11 shipped examples, checked by running the old and new implementations side by side.

Also fixed: the two prettier versions had drifted

The worker hard-coded 3.7.4 from the CDN; the server prerenders with the installed ^3.9.6. The same snippet could come out formatted one way before hydration and another way after.

The version is now substituted at build time from the resolved package, so pnpm up moves both sides together. Verified on a clean rebuild (.output and the vite cache removed first): the emitted bundle asks for prettier@3.9.6 and no __PRETTIER_VERSION__ placeholder survives anywhere in the output. Upstream still carries this drift — 3.8.2 against ^3.9.6 — so this part is a deliberate divergence, recorded in #400.

One reported item was wrong

upperName is not unused — chat/Chat.vue calls it four times. Left alone.

The tidying

  • The 'tip' branch in processLinks was dead: callouts are rewritten to blockquotes earlier in the same pass, so it could never match.
  • processLinks was typed with the global DOM Node, but it walks MDC [tag, attrs, ...children] tuples. It type-checked only because every access is an index inside an Array.isArray branch.
  • The class on category nav entries tested 'restApiVersion' in category, but no entry in the categories map has that field — always false, always undefined, while reading as though category-level version badges were supported. Removed, with the reasoning left in place; the real per-page filtering in filterChildrenByRestapiVersion is untouched.
  • await useCodeExample() added a Suspense barrier for a synchronous function.
  • b24Config was an empty untyped {} passed to two functions whose parameter is optional — dropped rather than typed, since an empty object configures nothing and the untyped literal was the actual risk.
  • _configResolved: any is now a structural type for the single field read. Not Vite's own ResolvedConfig: Vite is not a direct dependency here — it arrives through Nuxt — so that import does not resolve.
  • The wildcard Access-Control-Allow-Origin came off code-examples.get.ts; every caller is same-origin (the fetchCodeExample composable and the server-side MCP b24-jssdk-get-example tool). Upstream keeps it, which is why the reason for diverging is written down next to the handler.
  • prepareHref's reported empty-path case is not currently reachable — every branch requires a /docs/ segment first — but the guard is what lets the non-null assertions go away, so it is in. Said plainly rather than claimed as a fix.
  • .github/contributing/documentation.md told contributors that body lines are "dedented by exactly two spaces, so keep the wrapper at one level of indentation" — the behaviour this PR removes. Updated, so the guide stops describing a limitation that no longer exists.

Tests

The message routing moved out of app/plugins/prettier.ts, which cannot be imported outside Vite because of its ?worker&inline specifier, into app/utils/prettierWorkerApi.ts — the same split codeTransform.ts got in #393. Six tests pin what the page does with each reply shape, including the one that never used to arrive.

Five more cover the dedent: four-space, tab, minimum-taken-from-every-line, the blank-line case, and one pinning a known quirk (the trailing .trim() eats the first line's remaining indent when that line is deeper than the minimum — no example opens on a nested line, so nothing shipped is affected).

Both specs state what they do not cover rather than leaving a green run to be over-read: the worker tests cannot prove the worker sends { uid, error } on every failure path (that half needs a browser), and cannot see whether a settled entry is removed from handlers, since a promise cannot settle twice — deleting that line leaves the file green, confirmed by mutation.

Checks

  • pnpm run typecheck — all eight passes, 0 errors
  • jsSdk:unit + jsSdk:types + skills:unit — 661 tests, no type errors
  • pnpm run docs:build from a cleaned .output — used to verify the version substitution reaches the bundle
  • lint (1 pre-existing unrelated warning in docs/server/api/ai.post.ts, untouched), docs-lint --strict, lint:md, md-internal-links

Follow-ups filed

Closes #139

🤖 Generated with Claude Code

https://claude.ai/code/session_01F22e2ft66y7nuBJjzdThBr

The remaining seven items from #139 plus everything in its follow-up comment.
Two of them turned out to be real bugs rather than tidying, and one reported
item turned out not to be a bug at all.

The prettier worker could hang the page. `app/plugins/prettier.ts` parks a
`[resolve, reject]` pair under each message's `uid` and clears it only when a
reply arrives, so a message that produces no reply is not a failed format — it
is a promise that never settles. The worker had no error handling anywhere: a
throw in `prettier.format`, or a blocked CDN, meant `CodeExample.vue` awaited
forever. Upstream `nuxt/ui`, which this file was forked from, has since fixed
it, and its shape is better than the one here: one try/catch around the whole
handler, and lazy loading inside the format path instead of a start-up queue
guarded by a flag that was never reset on failure. Adopted.

The prettier versions had drifted: the worker hard-coded `3.7.4` from the CDN
while the server prerendered with the installed `3.9.6`, so the same snippet
could be formatted one way before hydration and another way after. The version
is now substituted at build time from the resolved package, verified against a
real `docs:build` — the emitted bundle asks for `prettier@3.9.6` and no
placeholder survives. (Upstream still has this drift.)

The dedent was a hard-coded `.slice(2)`, correct only while every example is
indented with exactly two spaces; a four-space or tab-indented example would
have been mangled rather than dedented. Now measured from the region's non-blank
lines. Output is byte-identical on all 11 shipped examples.

`upperName` is NOT unused — it is called four times in `chat/Chat.vue`. Left
alone. Deleted the ones that really are unreferenced: `cachedParseMarkdown.ts`,
`extractSections.ts`, `normalizeComponentName.ts` (which is where the reported
always-true condition lived, so that goes with it), `useDocs.ts`, and
`HOOK_PLACEHOLDER`.

Also: the `'tip'` branch in `processLinks` was dead, because callouts are
rewritten to blockquotes earlier in the same pass; `processLinks` was typed with
the DOM `Node` rather than the MDC tuple it actually walks; `await
useCodeExample()` added a Suspense barrier for a synchronous function;
`b24Config` was an empty untyped object passed to two functions whose parameter
is optional, so it is gone rather than typed; `_configResolved: any` is now a
structural type for the single field read (Vite is not a direct dependency here,
so its own `ResolvedConfig` does not resolve); and the wildcard CORS header came
off `code-examples.get.ts`, whose every caller is same-origin.

`prepareHref`'s reported empty-path case is not currently reachable — every
branch requires a `/docs/` segment first — but the guard is what lets the
non-null assertions go away, so it is in.

Closes #139

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F22e2ft66y7nuBJjzdThBr
QA was right that the largest change shipped untested. The page↔worker message
routing moved out of `app/plugins/prettier.ts` — which cannot be imported
outside Vite, because of its `?worker&inline` specifier — into
`app/utils/prettierWorkerApi.ts`, the same split `codeTransform.ts` got in #393.
Six tests now pin what the page does with each reply shape, including the one
that never used to arrive. Verified by mutation: making the error branch
unreachable fails the suite.

Two limits are written into that spec rather than left for a reader to assume:
it cannot prove the worker sends `{ uid, error }` on every failure path (that
half needs a browser), and it cannot see whether a settled entry is removed from
`handlers`, since a promise cannot settle twice — deleting that line leaves the
file green.

`.github/contributing/documentation.md` said body lines are "dedented by exactly
two spaces, so keep the wrapper at one level of indentation". That was the
behaviour this PR removed, so the guide contradicted the code — and told
contributors to work around a limitation that no longer exists.

The `useNavigation.ts` dead-branch removal went in unexplained, which in a
commit that names every other change reads as a drive-by. Documented in place:
no entry in the `categories` map carries `restApiVersion`, so the test was
always false while the code read as though category-level version badges were
supported. The real per-page filtering is untouched.

Claims about upstream `nuxt/ui` are now pinned to the commit they were checked
against (`b751eae`, 2026-08-25) instead of being stated as timeless fact, and
the fork's own former CDN version is named as `3.7.4` rather than being confused
with upstream's `3.8.2`.

Filed #399 for what the security review correctly separated from this diff: the
worker executes six third-party modules from a CDN with no SRI, and the site
sets no CSP. Pre-existing and inherited from the fork; the comment in the worker
now points at the issue rather than staying silent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F22e2ft66y7nuBJjzdThBr
The extraction carried a cast that hid a real hole. `handleMessage` in the
worker returns `undefined` for any `type` it does not recognise and the reply is
posted regardless, so an unknown type arrives as
`{ uid, message: undefined, error: undefined }`. The routing branched only on
`error`, then asserted `message as string` — resolving a promise with an
`undefined` that `CodeExample.vue` would call string methods on.

Unreachable today, since `format` is the only message type ever sent. But the
old code was `any`, where the mismatch was at least visible; writing `as string`
is what turns the next added message type into a silent landmine rather than a
failure with a name on it. Now checked, and rejected with the uid in the message.
Test added.

Also dropped `export type { SimplePrettier }` from the plugin — a re-export with
no importer, left behind by the extraction — and updated
`.github/contributing/testing.md`, which enumerated the `*.unit.spec.ts`
exception by naming three files and had silently stopped being the full list.
It now names `test/integration/docs/` as a group and says the list is
illustrative, since the suffix is the actual rule.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F22e2ft66y7nuBJjzdThBr
@IgorShevchik
IgorShevchik merged commit a5b9538 into main Aug 26, 2026
10 checks passed
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.

docs/: code-quality issues in the code-example subsystem and server utils

2 participants