diff --git a/apps/website/src/app/global.css b/apps/website/src/app/global.css index ccda4df2b..f93e1e61e 100644 --- a/apps/website/src/app/global.css +++ b/apps/website/src/app/global.css @@ -50,6 +50,31 @@ pre { overflow-x: auto; } +/* + * Shiki `codeToHtml` output — components/landing/HighlightedCode.tsx and + * components/solutions/SolutionCodeBlock.tsx. + * + * Shiki writes the theme background INLINE on the
 but emits no padding,
+ * so without this the code text sits flush against the dark surface's edges.
+ * That padding used to come from a global `.shiki` rule, deleted in #863 on a
+ * docs-only survey ("zero elements matched") that missed these two TSX call
+ * sites — rehype-pretty-code really does not emit `.shiki`, but `codeToHtml`
+ * does. Selector is `pre.shiki`, not `.shiki`: both call sites put the class on
+ * a wrapper 
too, and padding there lands OUTSIDE the dark surface. + * + * Held by a style contract (styles/style-contracts.spec.ts) — this comment is + * exactly the tell that registry asks for. + */ +pre.shiki { + margin: 0; + padding: 1.25rem 1.5rem; +} +pre.shiki code { + font-family: var(--font-mono), "JetBrains Mono", monospace; + font-size: inherit; + line-height: inherit; +} + /* Long unbreakable tokens (e.g. package names like @threadplane/chat, * file paths like app.config.ts) live inside marketing headings and pull * the layout wider than the viewport on narrow phones. Allow breaking diff --git a/apps/website/src/components/landing/HighlightedCode.spec.tsx b/apps/website/src/components/landing/HighlightedCode.spec.tsx new file mode 100644 index 000000000..3bcbe3ad2 --- /dev/null +++ b/apps/website/src/components/landing/HighlightedCode.spec.tsx @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +import { describe, expect, it } from 'vitest'; +import { HighlightedCode } from './HighlightedCode'; + +/** + * The markup half of the padding contract that regressed in #863. + * + * The CSS half — that `pre.shiki` actually declares a padding — is a style + * contract in styles/style-contracts.spec.ts. Both halves are needed and + * neither is sufficient: the contract survives this component switching + * highlighters (guarding a rule nothing emits any more), and these assertions + * survive the rule being deleted again as dead. + * + * What makes the pairing load-bearing is that Shiki puts the theme background + * INLINE on the
 while emitting no padding of its own. So the padding has
+ * to land on that same element — the wrapper 
carries `shiki` too, and + * padding there sits outside the dark surface as a light gutter. + */ +async function renderToHtml() { + // An async Server Component: await the element and read the markup it hands + // to `dangerouslySetInnerHTML`, no DOM needed. + const element = await HighlightedCode({ code: 'const answer = 42;' }); + return element.props.dangerouslySetInnerHTML.__html as string; +} + +describe('HighlightedCode', () => { + it('emits a
 for the pre.shiki contract to match', async () => {
+    expect(await renderToHtml()).toMatch(/
', async () => {
+    // This is why padding must go on the 
 and not on the wrapper.
+    expect(await renderToHtml()).toMatch(
+      /
]*style="[^"]*background-color:/,
+    );
+  });
+
+  it('emits no padding of its own, leaving CSS as the only source', async () => {
+    expect(await renderToHtml()).not.toMatch(/]*style="[^"]*padding/);
+  });
+
+  it('puts `shiki` on the wrapper too, so the rule must stay pre-scoped', async () => {
+    const element = await HighlightedCode({ code: 'const answer = 42;' });
+    expect(element.props.className).toBe('shiki');
+  });
+});
diff --git a/apps/website/src/components/solutions/SolutionCodeBlock.tsx b/apps/website/src/components/solutions/SolutionCodeBlock.tsx
index 8c53c0ee3..f64465bf6 100644
--- a/apps/website/src/components/solutions/SolutionCodeBlock.tsx
+++ b/apps/website/src/components/solutions/SolutionCodeBlock.tsx
@@ -45,8 +45,9 @@ export async function SolutionCodeBlock({ code }: { code: SolutionCodeBlocks })
                 {block.label}
               

{/* - Shiki emits a complete
 that already carries its own background,
-            padding, and `overflow-x: auto`, so this wrapper owns only the frame.
+            Shiki emits a 
 carrying its own background; its padding and
+            `overflow-x: auto` come from the `pre.shiki` / `pre` rules in
+            global.css, so this wrapper owns only the frame.
             `overflow: hidden` is what makes the radius clip that background — it
             must not be `auto`, which would nest a second scroll container around
             a element that already scrolls and can show two scrollbars.
diff --git a/apps/website/src/styles/landing.css b/apps/website/src/styles/landing.css
index 8f90f35d4..a2e6a475b 100644
--- a/apps/website/src/styles/landing.css
+++ b/apps/website/src/styles/landing.css
@@ -986,17 +986,20 @@
 }
 
 /* HighlightedCode — components/landing/HighlightedCode.tsx
- * `.shiki[data-ui="highlighted-code"]` (specificity 0,2,0) intentionally beats
- * the plain `.shiki` rule in docs.css (0,1,0) regardless of stylesheet import
- * order, so this landing-only override of margin/padding/font-size/line-height
- * keeps winning the way the inline style used to. */
+ * The wrapper 
owns only type scale; the frame around it + * (.home-code-frame, .lg-show-card, ...) owns radius and clipping. Padding + * belongs on the
, which is where Shiki's dark background lives — putting
+ * it here would inset the code from a light gutter instead. The base
+ * `pre.shiki` padding is in global.css; this is the tighter landing scale. */
 .shiki[data-ui="highlighted-code"] {
   margin: 0;
   border-radius: 0;
-  padding: 16px 20px;
   font-size: 0.78rem;
   line-height: 1.65;
 }
+.shiki[data-ui="highlighted-code"] > pre.shiki {
+  padding: 16px 20px;
+}
 
 /* RenderCodeShowcase — components/landing/render/RenderCodeShowcase.tsx */
 .render-code {
diff --git a/apps/website/src/styles/style-contracts.spec.ts b/apps/website/src/styles/style-contracts.spec.ts
index 955d8beaa..405067289 100644
--- a/apps/website/src/styles/style-contracts.spec.ts
+++ b/apps/website/src/styles/style-contracts.spec.ts
@@ -49,6 +49,14 @@ const CONTRACTS: StyleContract[] = [
       'align-self': /align-self:\s*flex-start/,
     },
   },
+  {
+    file: '../app/global.css',
+    selector: 'pre.shiki',
+    why: "Shiki writes the theme background inline on the 
 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.",
+    requires: {
+      padding: /padding:/,
+    },
+  },
   {
     file: 'docs.css',
     selector: '.docs-control-plane [data-control-plane-pane]',