You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
F4 upgrades the web pipeline's code-block presentation:
666
+
667
+
1. **Build-time syntax highlighting** via [Shiki](https://shiki.style) — Shiki tokenizes source with TextMate grammars and returns inline-styled `<span>` rows. Zero runtime highlight JS is shipped (no Prism, no highlight.js).
668
+
2. A **Copy button** per code block, served as a tiny vanilla JS asset `templates/assets/code-copy.js`.
669
+
670
+
### Shiki integration
671
+
672
+
`src/shiki-highlighter.ts`is the only place the toolkit talks to Shiki. It exposes a single `highlight({ code, lang, theme })` API used by `markdown-processor-web.ts`.
↓ code() looks up `${lang}|||${node.text}` → wraps in <pre><code>
681
+
```
682
+
683
+
**Why the pre-pass walks marked's lexer (not regex):** code blocks inside list items are dedented by marked's lexer before reaching the renderer. A regex over the raw markdown would store keys with the original indent, while the renderer looks up the dedented form — every list-nested code block would miss the cache. Walking the lexer guarantees the same `(lang, code)` tuple is used on both sides.
684
+
685
+
**Cache scope:** the in-memory map persists for the duration of one Node process (one `build:web` invocation). Building all four languages in sequence reuses the cache, so a snippet that appears in `en/foo.md` and again in `ko/foo.md` tokenizes once. The cache is not persisted to disk — Shiki's tokenization is fast enough on second-run that an in-memory cache covers the spec's "build wall-clock per language ≤ +50%" budget; for the WebUI docs the all-langs build runs in ~4s end-to-end.
686
+
687
+
**Defensive paths:** unknown languages render as plain `<span class="line">` rows (not highlighted, not error). Unknown themes warn once and fall back to `github-light`. Shiki errors during tokenization are caught and degrade to plain escaped text — never fail the build.
688
+
689
+
### Theme configuration
690
+
691
+
Operators control the syntax theme via `docs-toolkit.config.yaml`:
692
+
693
+
```yaml
694
+
code:
695
+
lightTheme: "github-light"# default; any bundled Shiki theme works
696
+
```
697
+
698
+
Any [bundled Shiki theme](https://shiki.style/themes) is accepted (`github-light`, `vitesse-light`, `light-plus`, etc.). Unknown theme names warn once and fall back to `github-light`.
699
+
700
+
**Reserved namespace — `code.darkTheme`:** intentionally NOT wired in F4. The spec scopes F4 to light-theme only; a future dark-mode bucket will introduce `code.darkTheme` paired with Shiki's dual-theme rendering (`themes: { light, dark }`). Adding the key now would commit us to a CSS-variables vs inline-style output format before that work has chosen one. The slot is reserved at the type level via a code comment in `src/config.ts` only.
701
+
702
+
### Copy button (`templates/assets/code-copy.js`)
703
+
704
+
A vanilla DOM script (no framework, no deps; ~5 KB unminified, well under the per-page 25 KB JS budget). On `DOMContentLoaded`:
705
+
706
+
1. Wrap each `<pre><code>` in `.doc-code-block-wrapper` (idempotent).
707
+
2. Inject a `.doc-code-copy-btn` button that reads `[data-copy-label]` etc. from `<body>` for localized strings.
708
+
3. Click → `navigator.clipboard.writeText(<pre>.textContent)`. Falls back to a hidden-textarea + `document.execCommand("copy")` for non-secure-context previews.
709
+
4. Flash "Copied!" / "Copy failed" states for 1.5s, then revert.
710
+
711
+
Localized labels (`copy`, `copied`, `copyFailed`) live in `WEBSITE_LABELS` (`src/config.ts`) and are injected as `data-*` attributes on `<body>`, so the script itself stays language-agnostic and gets content-hashed once across every language.
712
+
713
+
The script is automatically picked up by `website-generator.ts`'s asset pipeline (the slot was wired in F5) — drop the file at `templates/assets/code-copy.js` and the build hashes it and includes it via `PageAssets.codeCopy`.
714
+
715
+
### CSS additions (`styles-web.ts`)
716
+
717
+
- `.shiki-host > code .line { display: block }` — ensures Shiki's `.line` rows wrap correctly inside the `<pre>` frame instead of pushing it wider.
718
+
- `.doc-code-block-wrapper`— positioning context for the absolutely-placed Copy button. The wrapper is invisible (no border / margin override).
719
+
- `.doc-code-copy-btn` — top-right corner pill that fades in on hover/focus of the wrapper. Distinct color states: `idle`(default), `copied` (green tint), `failed` (red tint).
F4 only touches `markdown-processor-web.ts`. The PDF pipeline (`markdown-processor.ts`) keeps its existing renderer — PDF code blocks remain unhighlighted (the spec does not require Shiki in PDFs). Verified by running `pnpm run pdf:en` on this commit (PDF generated successfully).
737
+
738
+
### Constraints honoured (F4)
739
+
740
+
- **Air-gapped** — Shiki bundles its grammars and themes as JSON inside the `shiki` npm package; no CDN, no network at build time. The Copy button uses `navigator.clipboard` (browser-native, no network).
741
+
- **JS budget** — `code-copy.js` source is ~5 KB unminified, well under the per-page 25 KB budget. No runtime highlight JS is shipped (Shiki runs build-time only).
742
+
- **Build wall-clock** — Shiki tokenization is amortized by the in-memory cache. The all-langs WebUI docs build runs in ~4s end-to-end (well within the "≤ +50% per language" budget).
743
+
- **No dark-mode leakage** — F4 emits inline-styled spans for the configured light theme only. No `data-theme` attributes, no toggle UI, no `code.darkTheme` wiring (namespace reserved by comment only).
744
+
- **Backward compat** — code blocks with `data-highlight="…"` (line-highlight feature) keep using the legacy `code-line.highlighted` renderer; mixing per-token Shiki colors with line-level overlays is left for a follow-up.
0 commit comments