Skip to content

Commit 1bd7df3

Browse files
bloveclaude
andauthored
fix(website): restore padding on Shiki code blocks (#924)
PR #863 deleted the global `.shiki` rules from docs.css on the finding that zero elements matched. That was true of docs and blog — rehype-pretty-code writes its background inline and never emits a `.shiki` class — but two TSX call sites highlight with Shiki's `codeToHtml` directly, which does: - components/landing/HighlightedCode.tsx (homepage Code tabs, /langgraph, /render, /chat) - components/solutions/SolutionCodeBlock.tsx (every /solutions/* page) Those <pre> elements lost their padding. The dark theme background rides inline on the <pre>, while the landing override put its 16px 20px on the wrapper <div> (which carries `shiki` too) — outside the dark box. The result was a light gutter with the code flush against the dark edges. Restores the padding as a `pre.shiki` base rule in global.css, and moves the landing override's padding off the wrapper onto `> pre.shiki`. The selector is pre-scoped deliberately: both call sites also put the class on a wrapper div, where padding lands outside the surface. SolutionCodeBlock's comment claimed Shiki emits its own padding — that wrong premise is what made the deletion look safe, so it is corrected too. Guarded in two halves, since neither is sufficient alone. The CSS half is a style contract (#926's registry — this is precisely the "comment explaining why a declaration must not be removed" that it asks for), mutation-tested by deleting the declaration and by renaming the selector. The markup half lives in HighlightedCode.spec.tsx: that Shiki still emits a `pre.shiki` carrying an inline background and no padding of its own, so the contract keeps guarding a rule something actually matches. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 303849f commit 1bd7df3

5 files changed

Lines changed: 90 additions & 7 deletions

File tree

apps/website/src/app/global.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,31 @@ pre {
5050
overflow-x: auto;
5151
}
5252

53+
/*
54+
* Shiki `codeToHtml` output — components/landing/HighlightedCode.tsx and
55+
* components/solutions/SolutionCodeBlock.tsx.
56+
*
57+
* Shiki writes the theme background INLINE on the <pre> but emits no padding,
58+
* so without this the code text sits flush against the dark surface's edges.
59+
* That padding used to come from a global `.shiki` rule, deleted in #863 on a
60+
* docs-only survey ("zero elements matched") that missed these two TSX call
61+
* sites — rehype-pretty-code really does not emit `.shiki`, but `codeToHtml`
62+
* does. Selector is `pre.shiki`, not `.shiki`: both call sites put the class on
63+
* a wrapper <div> too, and padding there lands OUTSIDE the dark surface.
64+
*
65+
* Held by a style contract (styles/style-contracts.spec.ts) — this comment is
66+
* exactly the tell that registry asks for.
67+
*/
68+
pre.shiki {
69+
margin: 0;
70+
padding: 1.25rem 1.5rem;
71+
}
72+
pre.shiki code {
73+
font-family: var(--font-mono), "JetBrains Mono", monospace;
74+
font-size: inherit;
75+
line-height: inherit;
76+
}
77+
5378
/* Long unbreakable tokens (e.g. package names like @threadplane/chat,
5479
* file paths like app.config.ts) live inside marketing headings and pull
5580
* the layout wider than the viewport on narrow phones. Allow breaking
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-License-Identifier: MIT
2+
import { describe, expect, it } from 'vitest';
3+
import { HighlightedCode } from './HighlightedCode';
4+
5+
/**
6+
* The markup half of the padding contract that regressed in #863.
7+
*
8+
* The CSS half — that `pre.shiki` actually declares a padding — is a style
9+
* contract in styles/style-contracts.spec.ts. Both halves are needed and
10+
* neither is sufficient: the contract survives this component switching
11+
* highlighters (guarding a rule nothing emits any more), and these assertions
12+
* survive the rule being deleted again as dead.
13+
*
14+
* What makes the pairing load-bearing is that Shiki puts the theme background
15+
* INLINE on the <pre> while emitting no padding of its own. So the padding has
16+
* to land on that same element — the wrapper <div> carries `shiki` too, and
17+
* padding there sits outside the dark surface as a light gutter.
18+
*/
19+
async function renderToHtml() {
20+
// An async Server Component: await the element and read the markup it hands
21+
// to `dangerouslySetInnerHTML`, no DOM needed.
22+
const element = await HighlightedCode({ code: 'const answer = 42;' });
23+
return element.props.dangerouslySetInnerHTML.__html as string;
24+
}
25+
26+
describe('HighlightedCode', () => {
27+
it('emits a <pre class="shiki"> for the pre.shiki contract to match', async () => {
28+
expect(await renderToHtml()).toMatch(/<pre class="shiki[^"]*"/);
29+
});
30+
31+
it('carries the theme background inline on that <pre>', async () => {
32+
// This is why padding must go on the <pre> and not on the wrapper.
33+
expect(await renderToHtml()).toMatch(
34+
/<pre class="shiki[^"]*"[^>]*style="[^"]*background-color:/,
35+
);
36+
});
37+
38+
it('emits no padding of its own, leaving CSS as the only source', async () => {
39+
expect(await renderToHtml()).not.toMatch(/<pre[^>]*style="[^"]*padding/);
40+
});
41+
42+
it('puts `shiki` on the wrapper too, so the rule must stay pre-scoped', async () => {
43+
const element = await HighlightedCode({ code: 'const answer = 42;' });
44+
expect(element.props.className).toBe('shiki');
45+
});
46+
});

apps/website/src/components/solutions/SolutionCodeBlock.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ export async function SolutionCodeBlock({ code }: { code: SolutionCodeBlocks })
4545
{block.label}
4646
</p>
4747
{/*
48-
Shiki emits a complete <pre> that already carries its own background,
49-
padding, and `overflow-x: auto`, so this wrapper owns only the frame.
48+
Shiki emits a <pre> carrying its own background; its padding and
49+
`overflow-x: auto` come from the `pre.shiki` / `pre` rules in
50+
global.css, so this wrapper owns only the frame.
5051
`overflow: hidden` is what makes the radius clip that background — it
5152
must not be `auto`, which would nest a second scroll container around
5253
a element that already scrolls and can show two scrollbars.

apps/website/src/styles/landing.css

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -986,17 +986,20 @@
986986
}
987987

988988
/* HighlightedCode — components/landing/HighlightedCode.tsx
989-
* `.shiki[data-ui="highlighted-code"]` (specificity 0,2,0) intentionally beats
990-
* the plain `.shiki` rule in docs.css (0,1,0) regardless of stylesheet import
991-
* order, so this landing-only override of margin/padding/font-size/line-height
992-
* keeps winning the way the inline style used to. */
989+
* The wrapper <div> owns only type scale; the frame around it
990+
* (.home-code-frame, .lg-show-card, ...) owns radius and clipping. Padding
991+
* belongs on the <pre>, which is where Shiki's dark background lives — putting
992+
* it here would inset the code from a light gutter instead. The base
993+
* `pre.shiki` padding is in global.css; this is the tighter landing scale. */
993994
.shiki[data-ui="highlighted-code"] {
994995
margin: 0;
995996
border-radius: 0;
996-
padding: 16px 20px;
997997
font-size: 0.78rem;
998998
line-height: 1.65;
999999
}
1000+
.shiki[data-ui="highlighted-code"] > pre.shiki {
1001+
padding: 16px 20px;
1002+
}
10001003

10011004
/* RenderCodeShowcase — components/landing/render/RenderCodeShowcase.tsx */
10021005
.render-code {

apps/website/src/styles/style-contracts.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ const CONTRACTS: StyleContract[] = [
4949
'align-self': /align-self:\s*flex-start/,
5050
},
5151
},
52+
{
53+
file: '../app/global.css',
54+
selector: 'pre.shiki',
55+
why: "Shiki writes the theme background inline on the <pre> but emits no padding, so losing this sits the code flush against the dark surface's edges — on the homepage Code tabs, /langgraph, /render, /chat and every /solutions page. Deleted once already in #863, on a docs-only survey that concluded `.shiki` matched nothing.",
56+
requires: {
57+
padding: /padding:/,
58+
},
59+
},
5260
{
5361
file: 'docs.css',
5462
selector: '.docs-control-plane [data-control-plane-pane]',

0 commit comments

Comments
 (0)