feat(web): shiki syntax highlighting for code surfaces

One highlighter for the client: a synchronous fine-grained shiki core
(JS regex engine, no WASM) in ui-primitives with an explicit grammar
allowlist (typescript, shellscript, json — aliases resolve, unknown
languages take a geometry-identical plain arm). The shared CodeBlock
component owns both arms; markdown fences, the run_code expanded
program body (typescript), and the details panel Input (json) all
route through it. Token colors live in a new ui-theme shiki.css sheet
as --shiki-* custom properties (light/dark blocks), wired through the
shell's base.css chain — tokens-only styling holds; shiki's generated
span tree is the sanctioned innerHTML path (static output, no user
HTML). jsdom specs pin token spans, aliases, both fallbacks, and the
fence route; the built-bundle snapshot asserts the highlighted program
under the code row.
This commit is contained in:
Tianyi Cui
2026-07-26 09:52:37 +08:00
parent 4987261d55
commit bb3dc50a4b
17 changed files with 399 additions and 19 deletions
@@ -152,7 +152,7 @@ describe('run_code sub-calls through the real chat machinery', () => {
expect(view.getByText('Tool call')).toBeTruthy()
})
it('expanding the code row reveals the program body verbatim', async () => {
it('expanding the code row reveals the program body verbatim (shiki-tokenized)', async () => {
const parent = 'call-64'
const b = await bench(snapshotWith([codeResult(10, parent)], new Map()))
const view = mountApp(b.slots)
@@ -160,7 +160,12 @@ describe('run_code sub-calls through the real chat machinery', () => {
const toggle = view.container.querySelector('[data-variant="code"] button[aria-expanded]')
expect(toggle).not.toBeNull()
fireEvent.click(toggle!)
expect(view.getByText(/const listing = await tools\.bash/)).toBeTruthy()
// Shiki splits the program into token spans inside one <pre class="shiki">:
// assert the whole text and the highlighted tree rather than one node.
const pre = view.container.querySelector('pre.shiki')
expect(pre).not.toBeNull()
expect(pre!.textContent).toContain('const listing = await tools.bash')
expect(pre!.querySelectorAll('span[style]').length).toBeGreaterThan(3)
})
it('an isError sub-call renders the error state dot exactly like a failed native row', async () => {