Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions apps/website/src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pre> 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 <div> 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
Expand Down
46 changes: 46 additions & 0 deletions apps/website/src/components/landing/HighlightedCode.spec.tsx
Original file line number Diff line number Diff line change
@@ -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 <pre> while emitting no padding of its own. So the padding has
* to land on that same element — the wrapper <div> 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 <pre class="shiki"> for the pre.shiki contract to match', async () => {
expect(await renderToHtml()).toMatch(/<pre class="shiki[^"]*"/);
});

it('carries the theme background inline on that <pre>', async () => {
// This is why padding must go on the <pre> and not on the wrapper.
expect(await renderToHtml()).toMatch(
/<pre class="shiki[^"]*"[^>]*style="[^"]*background-color:/,
);
});

it('emits no padding of its own, leaving CSS as the only source', async () => {
expect(await renderToHtml()).not.toMatch(/<pre[^>]*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');
});
});
5 changes: 3 additions & 2 deletions apps/website/src/components/solutions/SolutionCodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ export async function SolutionCodeBlock({ code }: { code: SolutionCodeBlocks })
{block.label}
</p>
{/*
Shiki emits a complete <pre> that already carries its own background,
padding, and `overflow-x: auto`, so this wrapper owns only the frame.
Shiki emits a <pre> 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.
Expand Down
13 changes: 8 additions & 5 deletions apps/website/src/styles/landing.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div> owns only type scale; the frame around it
* (.home-code-frame, .lg-show-card, ...) owns radius and clipping. Padding
* belongs on the <pre>, 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 {
Expand Down
8 changes: 8 additions & 0 deletions apps/website/src/styles/style-contracts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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.",
requires: {
padding: /padding:/,
},
},
{
file: 'docs.css',
selector: '.docs-control-plane [data-control-plane-pane]',
Expand Down
Loading