Files
max-sixty__worktrunk/docs/tests/built-site.test.mjs
T

636 lines
26 KiB
JavaScript
Raw Normal View History

docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
import assert from 'node:assert/strict';
import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import {
keepCurrentTocLinkVisible,
syncTocCurrentToHash,
} from '../src/components/toc-scroll.mjs';
const docsRoot = fileURLToPath(new URL('..', import.meta.url));
const dist = path.join(docsRoot, 'dist');
const publicRoutes = [
'/',
'/claude-code/',
'/code-signing/',
'/config/',
'/extending/',
'/faq/',
'/hook/',
'/list/',
'/llm-commits/',
'/merge/',
'/remove/',
'/step/',
'/switch/',
'/tips-patterns/',
];
async function htmlFiles(directory = dist) {
const entries = await readdir(directory, { withFileTypes: true });
const nested = await Promise.all(entries.map((entry) => {
const file = path.join(directory, entry.name);
return entry.isDirectory() ? htmlFiles(file) : entry.name.endsWith('.html') ? [file] : [];
}));
return nested.flat();
}
async function allFiles(directory = dist) {
const entries = await readdir(directory, { withFileTypes: true });
const nested = await Promise.all(entries.map((entry) => {
const file = path.join(directory, entry.name);
return entry.isDirectory() ? allFiles(file) : [file];
}));
return nested.flat();
}
function routeFile(pathname) {
if (pathname === '/') return path.join(dist, 'index.html');
if (pathname === '/404/') return path.join(dist, '404.html');
if (pathname.endsWith('/')) return path.join(dist, pathname, 'index.html');
if (pathname.endsWith('.html')) return path.join(dist, pathname);
}
const renderedPages = [
'/404/',
...publicRoutes,
'/worktrunk/',
].map(routeFile);
function fileRoute(file) {
const relative = path.relative(dist, file);
if (relative === 'index.html') return '/';
if (relative === '404.html') return '/404/';
return `/${path.dirname(relative)}/`;
}
function tableSummaries(html) {
const text = (value) => value
.replace(/<[^>]*>/g, '')
.replaceAll('&#x26;', '&')
.replaceAll('&amp;', '&')
.replace(/\s+/g, ' ')
.trim();
return [...html.matchAll(/<table\b([^>]*)>([\s\S]*?)<\/table>/g)].map((match) => {
const body = match[2].match(/<tbody>([\s\S]*?)<\/tbody>/)?.[1] ?? '';
const rows = [...body.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/g)];
return {
attributes: match[1],
headers: [...match[2].matchAll(/<th\b[^>]*>([\s\S]*?)<\/th>/g)]
.map((header) => text(header[1])),
rowLabels: rows.map((row) => (
[...row[1].matchAll(/<span\b([^>]*)>([\s\S]*?)<\/span>/g)]
.filter((label) => (
/\baria-hidden="true"/.test(label[1])
&& /\bclass="[^"]*\bwt-responsive-record-label\b[^"]*"/.test(label[1])
))
.map((label) => text(label[2]))
)),
};
});
}
function renderedText(html) {
return html
.replace(/<[^>]*>/g, '')
.replace(/&#x([\da-f]+);/giu, (_, value) => String.fromCodePoint(Number.parseInt(value, 16)))
.replace(/&#(\d+);/gu, (_, value) => String.fromCodePoint(Number(value)))
.replaceAll('&quot;', '"')
.replaceAll('&gt;', '>')
.replaceAll('&lt;', '<')
.replaceAll('&amp;', '&');
}
test('build preserves the public route contract', async () => {
const files = new Set(await htmlFiles());
const buildFiles = new Set(await allFiles());
const expected = [
path.join(dist, '404.html'),
...publicRoutes.map(routeFile),
routeFile('/worktrunk/'),
];
for (const file of expected) assert.ok(files.has(file), `missing ${file}`);
const sitemap = await readFile(path.join(dist, 'sitemap-0.xml'), 'utf8');
for (const route of publicRoutes) {
assert.match(sitemap, new RegExp(`<loc>https://worktrunk\\.dev${route}</loc>`));
}
assert.doesNotMatch(sitemap, /worktrunk\.dev\/(?:404|worktrunk)\//);
const robots = await readFile(path.join(dist, 'robots.txt'), 'utf8');
for (const match of robots.matchAll(/^Sitemap:\s+(\S+)$/gm)) {
const url = new URL(match[1]);
assert.equal(url.origin, 'https://worktrunk.dev');
assert.ok(buildFiles.has(path.join(dist, url.pathname)), `robots.txt references missing ${url.pathname}`);
}
const compatibilityPage = await readFile(routeFile('/worktrunk/'), 'utf8');
assert.match(compatibilityPage, /<link rel="canonical" href="https:\/\/worktrunk\.dev\/"/);
assert.match(compatibilityPage, /<meta name="robots" content="noindex"/);
assert.match(compatibilityPage, /<meta property="og:url" content="https:\/\/worktrunk\.dev\/"/);
assert.doesNotMatch(compatibilityPage, /<meta property="og:url" content="https:\/\/worktrunk\.dev\/worktrunk\/"/);
const homepage = await readFile(routeFile('/'), 'utf8');
assert.match(homepage, /<title>Worktrunk — Git worktree management for parallel AI agent workflows<\/title>/);
assert.doesNotMatch(homepage, /data-contract="home-hero"/);
assert.doesNotMatch(homepage, /data-has-hero/);
assert.doesNotMatch(homepage, />\s*Learn More\s*</);
assert.match(
homepage,
/<a\b[^>]*data-contract="github-stars"[^>]*href="https:\/\/github\.com\/max-sixty\/worktrunk"[^>]*>[\s\S]*?<img\b[^>]*src="https:\/\/img\.shields\.io\/github\/stars\/max-sixty\/worktrunk\?style=social"[^>]*>[\s\S]*?<\/a>/,
);
assert.doesNotMatch(homepage, />\s*GitHub stars\s*</);
assert.match(
homepage,
/<div\b[^>]*class="wt-home"[^>]*>[\s\S]*?<p>Worktrunk is a CLI for Git worktree management, designed for <strong>parallel AI agent\s+workflows<\/strong>\.<\/p>/,
);
assert.match(homepage, /<button\b[^>]*aria-label="Menu"[^>]*aria-controls="starlight__sidebar"/);
assert.match(homepage, /<div id="starlight__sidebar" class="sidebar-pane\b/);
assert.match(homepage, /<a\b[^>]*href="\/switch\/"[^>]*><span\b[^>]*>wt switch<\/span><\/a>/);
assert.match(homepage, /<a\b[^>]*href="\/faq\/"[^>]*><span\b[^>]*>FAQ<\/span><\/a>/);
assert.doesNotMatch(homepage, /data-has-toc/);
assert.doesNotMatch(homepage, /<nav aria-labelledby="starlight__on-this-page">/);
assert.doesNotMatch(homepage, /<starlight-theme-select>/);
assert.match(homepage, /<div\b[^>]*class="wt-home"[^>]*data-contract="home-content-rail"/);
assert.match(homepage, /<h1 id="_top"[^>]*><span id="worktrunk"[^>]*>Worktrunk<\/span><\/h1>/);
assert.match(
homepage,
/<source srcset="\/assets\/docs\/dark\/wt-core-mobile\.gif"[^>]*data-demo-theme="dark"[^>]*data-demo-media="\(max-width: 42rem\)"/,
);
assert.match(
homepage,
/<source srcset="\/assets\/docs\/light\/wt-core-mobile\.gif"[^>]*data-demo-theme="light"[^>]*data-demo-media="\(max-width: 42rem\)"/,
);
assert.match(homepage, /<img src="\/assets\/docs\/light\/wt-core\.gif"/);
assert.equal(homepage.match(/<h1\b/g)?.length, 1);
assert.match(
homepage,
/<pre[^>]*><code><span class="wt-shell-command">git<\/span> <span class="wt-shell-argument">worktree<\/span> <span class="wt-shell-argument">add<\/span> <span class="wt-shell-option">-b<\/span>/,
);
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
const comparison = homepage.match(/<table class="cmd-compare">([\s\S]*?)<\/table>/)?.[1];
assert.ok(comparison, 'homepage is missing the command comparison table');
assert.equal(comparison.match(/<td data-label="Worktrunk">/g)?.length, 4);
assert.equal(comparison.match(/<td data-label="Plain git">/g)?.length, 4);
assert.equal(comparison.match(/class="wt-shell-command"/g)?.length, 12);
assert.equal(comparison.match(/class="wt-shell-option"/g)?.length, 4);
assert.equal(comparison.match(/class="wt-shell-argument"/g)?.length, 21);
assert.match(homepage, /class="wt-terminal-green"/);
assert.match(homepage, /class="wt-terminal-red"/);
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
const switchPage = await readFile(routeFile('/switch/'), 'utf8');
assert.match(switchPage, /<nav aria-labelledby="starlight__on-this-page">/);
assert.match(
switchPage,
/<a\b[^>]*data-contract="github-stars"[^>]*href="https:\/\/github\.com\/max-sixty\/worktrunk"[^>]*>[\s\S]*?<img\b[^>]*src="https:\/\/img\.shields\.io\/github\/stars\/max-sixty\/worktrunk\?style=social"[^>]*>[\s\S]*?<\/a>/,
);
assert.doesNotMatch(switchPage, /<starlight-theme-select>/);
const listPage = await readFile(routeFile('/list/'), 'utf8');
assert.match(listPage, /href="https:\/\/github\.com\/max-sixty\/worktrunk\/edit\/main\/docs\/src\/content\/docs\/list\.md"/);
assert.doesNotMatch(listPage, /docs\/src\/content\/docs\/src\/content\/docs/);
assert.match(listPage, /<span class="wt-terminal-green">\+54<\/span>/);
assert.match(listPage, /<span class="wt-terminal-red">-5<\/span>/);
assert.match(listPage, /<span class="wt-terminal-blue wt-terminal-dim">#412<\/span>/);
Restore docs code-block hierarchy (#3938) The Astro site kept the documentation content but lost some of the old site's visual hierarchy: shell commands flattened into one color, generated Command References had no semantic roles, dim terminal rows were barely distinguishable, and file excerpts read like generic untitled code. This restores those distinctions at their natural rendering boundaries. ## What changed - Add paired Worktrunk Shiki themes for source and shell syntax, preserving the established warm palette with accessible light and dark contrast. - Mark generated Command Reference fences at their producer and render Clap commands, headings, options, values, metadata, possible-value rows, continued Usage signatures, and variadic metavariables semantically. - Replace opacity-based terminal dimming with explicit accessible colors while retaining ANSI hues. - Present file excerpts as compact attached path tabs with a restrained craft accent. - Cover all 30 generated references, every public route at 320px and 393px in both themes, unrelated-fence false positives, command-only copying, horizontal containment, and WCAG contrast in real WebKit rendering. ## Mobile demos The light and dark mobile homepage demos were already re-recorded and published in `max-sixty/worktrunk-assets` at `4de7bde` by #3936. This change verifies responsive source selection and viewport containment against those current recordings, so another lossy re-record is unnecessary. ## Testing - `cargo run -- hook pre-merge --yes` — 4,722 passed, 1 skipped - `cargo test --test integration test_docs_are_in_sync -- --nocapture` - `npm --prefix docs test` - `npm --prefix docs run build` - `npm --prefix docs run test:site` - `npm --prefix docs run check` - independent systematic, adversarial, and from-scratch architecture reviews > _This was written by Codex on behalf of max-sixty_
2026-08-27 16:07:46 -07:00
assert.match(
listPage,
/<div class="ec-line wt-command"><div class="code"><span[^>]*>wt<\/span><span[^>]*> <\/span><span[^>]*>list<\/span>/,
);
assert.match(listPage, /<figure class="frame wt-command-reference not-content">/);
assert.match(listPage, /<span class="wt-help-heading">Usage:<\/span>/);
assert.match(listPage, /<span class="wt-help-option">--format<\/span>/);
assert.match(listPage, /<span class="wt-help-value">\[OPTIONS\]<\/span>/);
assert.match(
listPage,
/<span class="wt-help-command">wt list<\/span> <span class="wt-help-value">&#x3C;COMMAND><\/span>/,
);
const llmCommitsPage = await readFile(routeFile('/llm-commits/'), 'utf8');
assert.match(llmCommitsPage, /<span class="wt-terminal-cyan">◎<\/span>/);
assert.match(
llmCommitsPage,
/<span class="wt-terminal-bold">feat\(validation\): add input validation utilities<\/span>/,
);
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
const configPage = await readFile(routeFile('/config/'), 'utf8');
Restore docs code-block hierarchy (#3938) The Astro site kept the documentation content but lost some of the old site's visual hierarchy: shell commands flattened into one color, generated Command References had no semantic roles, dim terminal rows were barely distinguishable, and file excerpts read like generic untitled code. This restores those distinctions at their natural rendering boundaries. ## What changed - Add paired Worktrunk Shiki themes for source and shell syntax, preserving the established warm palette with accessible light and dark contrast. - Mark generated Command Reference fences at their producer and render Clap commands, headings, options, values, metadata, possible-value rows, continued Usage signatures, and variadic metavariables semantically. - Replace opacity-based terminal dimming with explicit accessible colors while retaining ANSI hues. - Present file excerpts as compact attached path tabs with a restrained craft accent. - Cover all 30 generated references, every public route at 320px and 393px in both themes, unrelated-fence false positives, command-only copying, horizontal containment, and WCAG contrast in real WebKit rendering. ## Mobile demos The light and dark mobile homepage demos were already re-recorded and published in `max-sixty/worktrunk-assets` at `4de7bde` by #3936. This change verifies responsive source selection and viewport containment against those current recordings, so another lossy re-record is unnecessary. ## Testing - `cargo run -- hook pre-merge --yes` — 4,722 passed, 1 skipped - `cargo test --test integration test_docs_are_in_sync -- --nocapture` - `npm --prefix docs test` - `npm --prefix docs run build` - `npm --prefix docs run test:site` - `npm --prefix docs run check` - independent systematic, adversarial, and from-scratch architecture reviews > _This was written by Codex on behalf of max-sixty_
2026-08-27 16:07:46 -07:00
assert.match(
configPage,
/<figure class="frame has-title not-content"><figcaption class="header"><span class="title">~\/.config\/worktrunk\/config\.toml<\/span><\/figcaption>/,
);
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
const configToc = configPage.match(
/<nav aria-labelledby="starlight__on-this-page">([\s\S]*?)<\/nav>/,
)?.[1];
assert.ok(configToc, 'config page is missing its desktop table of contents');
assert.equal(
[...configToc.matchAll(/<a href="#[^"]+"/g)].length,
33,
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
'config table of contents should expose overview and structural section headings only',
);
for (const [id, title] of [
['user-configuration', 'User Configuration'],
['project-configuration', 'Project Configuration'],
['shell-integration', 'Shell Integration'],
['other', 'Other'],
['subcommands', 'Subcommands'],
]) {
assert.match(
configToc,
new RegExp(`<a href="#${id}"[^>]*>[\\s\\S]*?<span[^>]*>${title}</span></a>`),
);
}
assert.doesNotMatch(configToc, /href="#claude-code"/);
Scope docs heading anchors under each subcommand section (#4079) Command pages on the site append every subcommand's help, so headings such as "Examples", "Options", and "Command reference" repeat down `/step/` and `/config/`. The heading-id plugin numbered the repeats by position (`/step/#examples-3`, `/step/#hooks-1`, `/step/#command-reference-7`), so an anchor pointed somewhere else once a same-named heading was added above it. `docs/src/plugins/stable-heading-ids.mjs` now scopes ids below each subcommand's H2 (a heading starting `wt `) by that section's id: "Examples" under `wt step push` is `/step/#wt-step-push--examples`. Page-level headings and the subcommand headings keep their ids, so the table of contents and links like `/step/#wt-step-copy-ignored` are unchanged. A slug never contains `--`, so a scoped id can't equal another heading's slug — a "Cache" heading under `wt config state` stays distinct from the `wt config state cache` section. With the site handling it, the CLI help keeps unqualified headings. #4000 renamed six `wt config` subcommand headings to "Approval examples", "Alias examples", and so on to avoid the numbering; they're back to "Examples", in `--help` as well. Every anchor inside a subcommand section changes once: `/step/#min-age-guard` is now `/step/#wt-step-prune--min-age-guard`. The two internal links that pointed at such anchors — the FAQ's copy-on-write link and `wt config approvals`' "Reading approval state" — are updated, and `test:site` checks every internal fragment. External links to the old anchors land at the top of the page. The search-index plugin (`pagefind-command-references.mjs`) now imports the subcommand-heading check from the heading-id plugin rather than keeping its own copy. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01HUmx2Jd5mTK5TznqGDmLGp Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 16:57:33 -07:00
assert.doesNotMatch(configToc, /href="#wt-config-[^"]*--command-reference"/);
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
const structuralSections = new Map([
['/hook/', ['hook-types', 'security', 'configuration', 'running-hooks-manually', 'recipes']],
['/list/', ['subcommands']],
['/step/', ['subcommands']],
]);
for (const [route, ids] of structuralSections) {
const page = await readFile(routeFile(route), 'utf8');
const toc = page.match(/<nav aria-labelledby="starlight__on-this-page">([\s\S]*?)<\/nav>/)?.[1];
assert.ok(toc, `${route} is missing its desktop table of contents`);
for (const id of ids) assert.match(toc, new RegExp(`<a href="#${id}"`));
}
});
test('built pages omit low-value footer navigation', async () => {
for (const page of renderedPages) {
const html = await readFile(page, 'utf8');
assert.doesNotMatch(
html,
/class="[^"]*\bpagination-links\b/,
`${page} renders previous/next pagination`,
);
assert.doesNotMatch(
html,
/data-contract="project-links"/,
`${page} renders duplicate project links`,
);
}
});
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
test('nested command references expose unique search-only fragment titles', async () => {
const stepPage = await readFile(routeFile('/step/'), 'utf8');
Restore docs code-block hierarchy (#3938) The Astro site kept the documentation content but lost some of the old site's visual hierarchy: shell commands flattened into one color, generated Command References had no semantic roles, dim terminal rows were barely distinguishable, and file excerpts read like generic untitled code. This restores those distinctions at their natural rendering boundaries. ## What changed - Add paired Worktrunk Shiki themes for source and shell syntax, preserving the established warm palette with accessible light and dark contrast. - Mark generated Command Reference fences at their producer and render Clap commands, headings, options, values, metadata, possible-value rows, continued Usage signatures, and variadic metavariables semantically. - Replace opacity-based terminal dimming with explicit accessible colors while retaining ANSI hues. - Present file excerpts as compact attached path tabs with a restrained craft accent. - Cover all 30 generated references, every public route at 320px and 393px in both themes, unrelated-fence false positives, command-only copying, horizontal containment, and WCAG contrast in real WebKit rendering. ## Mobile demos The light and dark mobile homepage demos were already re-recorded and published in `max-sixty/worktrunk-assets` at `4de7bde` by #3936. This change verifies responsive source selection and viewport containment against those current recordings, so another lossy re-record is unnecessary. ## Testing - `cargo run -- hook pre-merge --yes` — 4,722 passed, 1 skipped - `cargo test --test integration test_docs_are_in_sync -- --nocapture` - `npm --prefix docs test` - `npm --prefix docs run build` - `npm --prefix docs run test:site` - `npm --prefix docs run check` - independent systematic, adversarial, and from-scratch architecture reviews > _This was written by Codex on behalf of max-sixty_
2026-08-27 16:07:46 -07:00
assert.match(stepPage, /<span class="wt-help-value">&#x3C;COMMAND>\.\.\.<\/span>/);
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
assert.match(stepPage, /<h2 id="command-reference">Command reference<\/h2>/);
const references = [...stepPage.matchAll(
Scope docs heading anchors under each subcommand section (#4079) Command pages on the site append every subcommand's help, so headings such as "Examples", "Options", and "Command reference" repeat down `/step/` and `/config/`. The heading-id plugin numbered the repeats by position (`/step/#examples-3`, `/step/#hooks-1`, `/step/#command-reference-7`), so an anchor pointed somewhere else once a same-named heading was added above it. `docs/src/plugins/stable-heading-ids.mjs` now scopes ids below each subcommand's H2 (a heading starting `wt `) by that section's id: "Examples" under `wt step push` is `/step/#wt-step-push--examples`. Page-level headings and the subcommand headings keep their ids, so the table of contents and links like `/step/#wt-step-copy-ignored` are unchanged. A slug never contains `--`, so a scoped id can't equal another heading's slug — a "Cache" heading under `wt config state` stays distinct from the `wt config state cache` section. With the site handling it, the CLI help keeps unqualified headings. #4000 renamed six `wt config` subcommand headings to "Approval examples", "Alias examples", and so on to avoid the numbering; they're back to "Examples", in `--help` as well. Every anchor inside a subcommand section changes once: `/step/#min-age-guard` is now `/step/#wt-step-prune--min-age-guard`. The two internal links that pointed at such anchors — the FAQ's copy-on-write link and `wt config approvals`' "Reading approval state" — are updated, and `test:site` checks every internal fragment. External links to the old anchors land at the top of the page. The search-index plugin (`pagefind-command-references.mjs`) now imports the subcommand-heading check from the heading-id plugin rather than keeping its own copy. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01HUmx2Jd5mTK5TznqGDmLGp Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 16:57:33 -07:00
/<h3 id="([^"]+)"><span class="wt-pagefind-fragment-title" hidden aria-hidden="true">(wt step [^<]+) — <\/span>Command reference<\/h3>/g,
)];
for (const [, id, command] of references) {
assert.equal(id, `${command.replaceAll(' ', '-')}--command-reference`);
}
assert.deepEqual(references.map((match) => match[2]), [
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
'wt step commit',
'wt step squash',
'wt step rebase',
'wt step push',
'wt step diff',
'wt step copy-ignored',
'wt step eval',
'wt step for-each',
'wt step promote',
'wt step prune',
'wt step relocate',
'wt step tether',
]);
const listPage = await readFile(routeFile('/list/'), 'utf8');
assert.match(
listPage,
Scope docs heading anchors under each subcommand section (#4079) Command pages on the site append every subcommand's help, so headings such as "Examples", "Options", and "Command reference" repeat down `/step/` and `/config/`. The heading-id plugin numbered the repeats by position (`/step/#examples-3`, `/step/#hooks-1`, `/step/#command-reference-7`), so an anchor pointed somewhere else once a same-named heading was added above it. `docs/src/plugins/stable-heading-ids.mjs` now scopes ids below each subcommand's H2 (a heading starting `wt `) by that section's id: "Examples" under `wt step push` is `/step/#wt-step-push--examples`. Page-level headings and the subcommand headings keep their ids, so the table of contents and links like `/step/#wt-step-copy-ignored` are unchanged. A slug never contains `--`, so a scoped id can't equal another heading's slug — a "Cache" heading under `wt config state` stays distinct from the `wt config state cache` section. With the site handling it, the CLI help keeps unqualified headings. #4000 renamed six `wt config` subcommand headings to "Approval examples", "Alias examples", and so on to avoid the numbering; they're back to "Examples", in `--help` as well. Every anchor inside a subcommand section changes once: `/step/#min-age-guard` is now `/step/#wt-step-prune--min-age-guard`. The two internal links that pointed at such anchors — the FAQ's copy-on-write link and `wt config approvals`' "Reading approval state" — are updated, and `test:site` checks every internal fragment. External links to the old anchors land at the top of the page. The search-index plugin (`pagefind-command-references.mjs`) now imports the subcommand-heading check from the heading-id plugin rather than keeping its own copy. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01HUmx2Jd5mTK5TznqGDmLGp Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 16:57:33 -07:00
/<h3 id="wt-list-statusline--command-reference"><span class="wt-pagefind-fragment-title" hidden aria-hidden="true">wt list statusline — <\/span>Command reference<\/h3>/,
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
);
});
Restore docs code-block hierarchy (#3938) The Astro site kept the documentation content but lost some of the old site's visual hierarchy: shell commands flattened into one color, generated Command References had no semantic roles, dim terminal rows were barely distinguishable, and file excerpts read like generic untitled code. This restores those distinctions at their natural rendering boundaries. ## What changed - Add paired Worktrunk Shiki themes for source and shell syntax, preserving the established warm palette with accessible light and dark contrast. - Mark generated Command Reference fences at their producer and render Clap commands, headings, options, values, metadata, possible-value rows, continued Usage signatures, and variadic metavariables semantically. - Replace opacity-based terminal dimming with explicit accessible colors while retaining ANSI hues. - Present file excerpts as compact attached path tabs with a restrained craft accent. - Cover all 30 generated references, every public route at 320px and 393px in both themes, unrelated-fence false positives, command-only copying, horizontal containment, and WCAG contrast in real WebKit rendering. ## Mobile demos The light and dark mobile homepage demos were already re-recorded and published in `max-sixty/worktrunk-assets` at `4de7bde` by #3936. This change verifies responsive source selection and viewport containment against those current recordings, so another lossy re-record is unnecessary. ## Testing - `cargo run -- hook pre-merge --yes` — 4,722 passed, 1 skipped - `cargo test --test integration test_docs_are_in_sync -- --nocapture` - `npm --prefix docs test` - `npm --prefix docs run build` - `npm --prefix docs run test:site` - `npm --prefix docs run check` - independent systematic, adversarial, and from-scratch architecture reviews > _This was written by Codex on behalf of max-sixty_
2026-08-27 16:07:46 -07:00
test('every command reference receives semantic syntax roles', async () => {
let headingCount = 0;
let frameCount = 0;
for (const page of renderedPages) {
const html = await readFile(page, 'utf8');
headingCount += [...html.matchAll(/>Command reference<\/h[23]>/g)].length;
const frames = [...html.matchAll(
/<figure class="frame wt-command-reference not-content">([\s\S]*?)<\/figure>/g,
)];
frameCount += frames.length;
for (const [, frame] of frames) {
const renderedCode = frame.match(/<code>([\s\S]*?)<\/code>/)?.[1] ?? '';
assert.match(frame, /class="wt-help-command"/, `${page} leaves the command unstyled`);
assert.match(frame, /class="wt-help-heading"/, `${page} leaves help headings unstyled`);
assert.match(frame, /class="wt-help-(?:option|value)"/, `${page} leaves help syntax unstyled`);
assert.doesNotMatch(
frame,
/<div class="code">- [^<]/,
`${page} leaves a possible-value row unstyled`,
);
const experimentalCount = [...renderedCode.matchAll(/\[experimental\]/g)].length;
const styledExperimentalCount = [
...renderedCode.matchAll(/class="wt-help-meta">\[experimental\]<\/span>/g),
].length;
assert.equal(
styledExperimentalCount,
experimentalCount,
`${page} leaves an experimental annotation unstyled`,
);
}
}
assert.ok(headingCount > 0, 'expected rendered command references');
assert.equal(frameCount, headingCount, 'some command references remain plain code blocks');
});
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
test('built pages only reference emitted local assets', async () => {
const files = new Set(await allFiles());
for (const page of renderedPages) {
const html = await readFile(page, 'utf8');
const assertAsset = (reference) => {
const url = new URL(reference.replaceAll('&amp;', '&'), `https://worktrunk.dev${fileRoute(page)}`);
if (url.origin !== 'https://worktrunk.dev' || url.pathname.endsWith('/')) return;
const target = path.join(dist, decodeURIComponent(url.pathname));
assert.ok(files.has(target), `${page} references missing asset ${url.pathname}`);
};
for (const match of html.matchAll(/\s(href|src|srcset)="([^"]+)"/g)) {
const references = match[1] === 'srcset'
? match[2].split(',').map((candidate) => candidate.trim().split(/\s+/, 1)[0])
: [match[2]];
for (const reference of references) assertAsset(reference);
}
for (const match of html.matchAll(/<meta (?:property="og:image"|name="twitter:image") content="([^"]+)"/g)) {
assertAsset(match[1]);
}
}
});
test('output-only console blocks do not expose copy controls', async () => {
let outputOnlyBlocks = 0;
for (const page of renderedPages) {
const html = await readFile(page, 'utf8');
docs: fix misplaced copy buttons by dropping terminal frames (#4090) On every shell code block the copy button sat 40px below the top of the code: on the second line of multi-line blocks, and hanging below one-line blocks such as `wt merge --no-ff` on /merge/. Shell blocks were Expressive Code terminal frames, and the terminal plugin deleted their title bar, but Expressive Code still offsets the copy button by that bar's height. ## Changes - `docs/astro.config.mjs` sets `defaultProps: { frame: 'code' }`, so no block is a terminal frame and there is no title bar to remove. The plugin's header-removal hook and its `props.frame = 'terminal'` go, and so does the `wt-commands-only` class: mobile wrapping in `custom.css` now keys on whether a block contains captured output (`:has(.wt-output)`). - Blocks with several commands had a whole-block copy button and per-line buttons in the same corner, handed over on hover by opacity. A hidden button still takes clicks, so clicking a line's button could copy the whole block, and on touch screens one button covered another. Most of these blocks are lists of alternatives, so they now get only per-line buttons, and the hover rule is deleted. - Those blocks wrap (Expressive Code's `wrap` prop) unless they carry captured output, since a per-line button sits at the end of its line and scrolled out of view on the long `jq` examples on /list/. A per-line button is no taller than its line, so buttons on adjacent lines no longer overlap. - The two multi-command blocks that only work run in order, the FAQ's stash recipe and the `Equivalent to:` block under `wt step diff`'s **How it works** (in `src/cli/step.rs`), are now `bash` fences, the form `docs/CLAUDE.md` prescribes for a copyable recipe, so they keep one copy button for the whole recipe. Terminal `--help` renders `console` and `bash` fences alike (it strips `$ `), and the help snapshots are unchanged. ## Side effect Expressive Code strips comment lines only from what terminal frames copy. The eight `bash` and `powershell` blocks with comments on /shell-integration/ now copy them, as console blocks already did. Pasted into zsh without `interactivecomments`, each comment line prints `command not found: #`; the commands still run. ## Tests A browser test checks every copy button on every page, at 393px with touch and at 1376px, for three things: it sits on the line it copies, it is inside the visible code, and it doesn't overlap another button. These checks fail against worktrunk.dev and against a build without the wrap and the height cap. The built-site and plugin tests now expect per-line payloads and no block payload on multi-command blocks, and a plugin test pins that blocks carrying output never wrap. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013U96NY8qKtZhavBSwnfCYq --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 10:00:25 -07:00
for (const match of html.matchAll(/<figure class="frame[^"]*">([\s\S]*?)<\/figure>/g)) {
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
const frame = match[1];
if (!/class="ec-line wt-output"/.test(frame)) continue;
if (/class="ec-line wt-(?:command|copyable)"/.test(frame)) continue;
outputOnlyBlocks += 1;
assert.doesNotMatch(frame, /class="copy"/, `${page} exposes a copy control for captured output`);
}
}
assert.equal(outputOnlyBlocks, 1, 'expected the FAQ approval transcript to exercise this case');
});
test('command-bearing console blocks emit command-only copy payloads', async () => {
let commandBearingBlocks = 0;
Publish shell integration, add a footer, dedupe global options (#4000) Guides and site presentation: one home per topic, a shell-integration page, a footer, and three rendering fixes. **Global options were emitted once per command reference** clap repeats the same ~20-line `Global Options:` block in every reference it renders, so a page assembled from subdocs stacked 11 copies on `/config/` and 13 on `/step/`. That padded the pages and gave site search that many near-identical hits — "squash" returned both `#command-reference` and `#command-reference-2`. `take_global_options` cuts each reference at the heading as it is built, keeping only the first; one `kept` flag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal `--help` renders through clap directly and is unchanged. The config page also carried colliding anchors — two "Hooks" (`#hooks`, `#hooks-1`), two "Aliases", and seven "Examples" (`#examples` … `#examples-6`) — now qualified at their source in `src/cli/config.rs`: User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples. `/step/` still has its own set (eight "Examples", two "Options", two "Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves existing `/step/#examples-N` anchors, so it wants a pass of its own with the inbound links audited; the deduplication above already removes 13 Global Options blocks from that page. **Shell integration has a page** Shell-integration debugging was skill-only: five named warning messages, a PowerShell checklist, and the wrapper mechanism, with no site page — while the FAQ's answer to "`wt switch` didn't cd" was to install the Claude Code plugin. It is now `/shell-integration/`, offered first, with the plugin as the second route. The `llms.txt` listing serves every page as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the listing still advertised; the symlink is added and the sync now fails when a listed page has none. **Presentation** - A site footer carries the version (read from `Cargo.toml` at build time), releases, changelog and license. No page named any of them, and `/code-signing/` was reachable only from inside a collapsed block on the homepage. Starlight's `Footer` is wrapped rather than replaced. - `wt list --full` renders 1157px inside an 800px content column, so 40% of it sat behind a horizontal scrollbar with the pane beside the column empty. A terminal frame now takes the whole pane where there is slack, measured with a query container rather than recomputed from Starlight's layout formula. - The `wt-command-reference` frames offered a copy button for 3,877 characters of generated help text; they now expose no copy control. A console block listing several commands is as often a menu of alternatives as a recipe, and nothing in the markup tells them apart, so every command line in such a block carries its own copy control alongside the block's. - The four command demos and the two hand-written figures get captions; the 2.33 MB homepage GIF below the fold loads lazily. **Sidebar order is pinned** `site-navigation.mjs` told readers a `test_sidebar_matches_frontmatter_order` would fail when the authored sidebar and the pages' `sidebar.order` disagreed. No such test existed, and the disagreement it describes is exactly what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and the `llms.txt` ordering derived from the frontmatter can't drift apart again. <details> <summary>Guide corrections</summary> - Tips & patterns was 26 flat H2 recipes in no order, all 26 in the sidebar. They group under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — with each recipe demoted to H3. Anchors are level-independent, so existing `/tips-patterns/#…` fragments still resolve. - `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program: it is `-x opencode -- run '<task>'`. - The branch-summary preview moved from tab 5 to 6 when the unified-diff tab landed; the recipe names the `summary` tab instead of a number. - The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is `fix-auth`'s port. It is 18283. - `_` in `wt list` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. - `wt step prune` removes branches with no worktree too, and the min-age guard ages a worktree by its creation time and a bare branch by its oldest reflog entry. - `wt step eval -v` prints fifteen variables; the example showed two under a lead calling them "the available template variables". - A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}`, filter gone. - The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. - `skills/worktrunk/reference/README.md` was a symlink to the repo README that `SKILL.md` never referenced, and the plugin mirror dereferenced it into a 262-line copy carrying the star-history token, share links, and a logo path resolving nowhere. Nothing generated it, so deleting the symlink is the whole fix. - One home per topic: agent handoffs stay in tips-patterns, activity markers in `claude-code.md`, alias-template deferral in `extending.md`, and the `codename` filter's two `worktree-path` recipes give way to the config page that owns path templates. The FAQ's "Running tests" and "How can I contribute?" duplicated the README's Contributing block down to the share URLs. - The FAQ linked `/worktrunk/#install`, the `noindex` compatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use `/#install`, where the new sidebar Install entry goes. - Example names settle on `myproject` / `feature-auth`; "sibling to main repo" becomes "sibling to the main worktree", and `wt remove`'s "target worktree" becomes "the worktree being removed" per the project's own terminology rule. </details> UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`, `#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`. Reviewable files: the hand-written pages under `docs/src/content/docs/` (notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`), `docs/src/components/Footer.astro`, `docs/src/plugins/worktrunk-terminal.mjs`, `docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`, `plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are regenerated. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
let perLineBlocks = 0;
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
for (const page of renderedPages) {
const html = await readFile(page, 'utf8');
docs: fix misplaced copy buttons by dropping terminal frames (#4090) On every shell code block the copy button sat 40px below the top of the code: on the second line of multi-line blocks, and hanging below one-line blocks such as `wt merge --no-ff` on /merge/. Shell blocks were Expressive Code terminal frames, and the terminal plugin deleted their title bar, but Expressive Code still offsets the copy button by that bar's height. ## Changes - `docs/astro.config.mjs` sets `defaultProps: { frame: 'code' }`, so no block is a terminal frame and there is no title bar to remove. The plugin's header-removal hook and its `props.frame = 'terminal'` go, and so does the `wt-commands-only` class: mobile wrapping in `custom.css` now keys on whether a block contains captured output (`:has(.wt-output)`). - Blocks with several commands had a whole-block copy button and per-line buttons in the same corner, handed over on hover by opacity. A hidden button still takes clicks, so clicking a line's button could copy the whole block, and on touch screens one button covered another. Most of these blocks are lists of alternatives, so they now get only per-line buttons, and the hover rule is deleted. - Those blocks wrap (Expressive Code's `wrap` prop) unless they carry captured output, since a per-line button sits at the end of its line and scrolled out of view on the long `jq` examples on /list/. A per-line button is no taller than its line, so buttons on adjacent lines no longer overlap. - The two multi-command blocks that only work run in order, the FAQ's stash recipe and the `Equivalent to:` block under `wt step diff`'s **How it works** (in `src/cli/step.rs`), are now `bash` fences, the form `docs/CLAUDE.md` prescribes for a copyable recipe, so they keep one copy button for the whole recipe. Terminal `--help` renders `console` and `bash` fences alike (it strips `$ `), and the help snapshots are unchanged. ## Side effect Expressive Code strips comment lines only from what terminal frames copy. The eight `bash` and `powershell` blocks with comments on /shell-integration/ now copy them, as console blocks already did. Pasted into zsh without `interactivecomments`, each comment line prints `command not found: #`; the commands still run. ## Tests A browser test checks every copy button on every page, at 393px with touch and at 1376px, for three things: it sits on the line it copies, it is inside the visible code, and it doesn't overlap another button. These checks fail against worktrunk.dev and against a build without the wrap and the height cap. The built-site and plugin tests now expect per-line payloads and no block payload on multi-command blocks, and a plugin test pins that blocks carrying output never wrap. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013U96NY8qKtZhavBSwnfCYq --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 10:00:25 -07:00
for (const match of html.matchAll(/<figure class="frame[^"]*">([\s\S]*?)<\/figure>/g)) {
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
const frame = match[1];
const lines = [...frame.matchAll(
Publish shell integration, add a footer, dedupe global options (#4000) Guides and site presentation: one home per topic, a shell-integration page, a footer, and three rendering fixes. **Global options were emitted once per command reference** clap repeats the same ~20-line `Global Options:` block in every reference it renders, so a page assembled from subdocs stacked 11 copies on `/config/` and 13 on `/step/`. That padded the pages and gave site search that many near-identical hits — "squash" returned both `#command-reference` and `#command-reference-2`. `take_global_options` cuts each reference at the heading as it is built, keeping only the first; one `kept` flag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal `--help` renders through clap directly and is unchanged. The config page also carried colliding anchors — two "Hooks" (`#hooks`, `#hooks-1`), two "Aliases", and seven "Examples" (`#examples` … `#examples-6`) — now qualified at their source in `src/cli/config.rs`: User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples. `/step/` still has its own set (eight "Examples", two "Options", two "Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves existing `/step/#examples-N` anchors, so it wants a pass of its own with the inbound links audited; the deduplication above already removes 13 Global Options blocks from that page. **Shell integration has a page** Shell-integration debugging was skill-only: five named warning messages, a PowerShell checklist, and the wrapper mechanism, with no site page — while the FAQ's answer to "`wt switch` didn't cd" was to install the Claude Code plugin. It is now `/shell-integration/`, offered first, with the plugin as the second route. The `llms.txt` listing serves every page as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the listing still advertised; the symlink is added and the sync now fails when a listed page has none. **Presentation** - A site footer carries the version (read from `Cargo.toml` at build time), releases, changelog and license. No page named any of them, and `/code-signing/` was reachable only from inside a collapsed block on the homepage. Starlight's `Footer` is wrapped rather than replaced. - `wt list --full` renders 1157px inside an 800px content column, so 40% of it sat behind a horizontal scrollbar with the pane beside the column empty. A terminal frame now takes the whole pane where there is slack, measured with a query container rather than recomputed from Starlight's layout formula. - The `wt-command-reference` frames offered a copy button for 3,877 characters of generated help text; they now expose no copy control. A console block listing several commands is as often a menu of alternatives as a recipe, and nothing in the markup tells them apart, so every command line in such a block carries its own copy control alongside the block's. - The four command demos and the two hand-written figures get captions; the 2.33 MB homepage GIF below the fold loads lazily. **Sidebar order is pinned** `site-navigation.mjs` told readers a `test_sidebar_matches_frontmatter_order` would fail when the authored sidebar and the pages' `sidebar.order` disagreed. No such test existed, and the disagreement it describes is exactly what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and the `llms.txt` ordering derived from the frontmatter can't drift apart again. <details> <summary>Guide corrections</summary> - Tips & patterns was 26 flat H2 recipes in no order, all 26 in the sidebar. They group under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — with each recipe demoted to H3. Anchors are level-independent, so existing `/tips-patterns/#…` fragments still resolve. - `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program: it is `-x opencode -- run '<task>'`. - The branch-summary preview moved from tab 5 to 6 when the unified-diff tab landed; the recipe names the `summary` tab instead of a number. - The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is `fix-auth`'s port. It is 18283. - `_` in `wt list` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. - `wt step prune` removes branches with no worktree too, and the min-age guard ages a worktree by its creation time and a bare branch by its oldest reflog entry. - `wt step eval -v` prints fifteen variables; the example showed two under a lead calling them "the available template variables". - A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}`, filter gone. - The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. - `skills/worktrunk/reference/README.md` was a symlink to the repo README that `SKILL.md` never referenced, and the plugin mirror dereferenced it into a 262-line copy carrying the star-history token, share links, and a logo path resolving nowhere. Nothing generated it, so deleting the symlink is the whole fix. - One home per topic: agent handoffs stay in tips-patterns, activity markers in `claude-code.md`, alias-template deferral in `extending.md`, and the `codename` filter's two `worktree-path` recipes give way to the config page that owns path templates. The FAQ's "Running tests" and "How can I contribute?" duplicated the README's Contributing block down to the share URLs. - The FAQ linked `/worktrunk/#install`, the `noindex` compatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use `/#install`, where the new sidebar Install entry goes. - Example names settle on `myproject` / `feature-auth`; "sibling to main repo" becomes "sibling to the main worktree", and `wt remove`'s "target worktree" becomes "the worktree being removed" per the project's own terminology rule. </details> UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`, `#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`. Reviewable files: the hand-written pages under `docs/src/content/docs/` (notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`), `docs/src/components/Footer.astro`, `docs/src/plugins/worktrunk-terminal.mjs`, `docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`, `plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are regenerated. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
/<div class="ec-line wt-(command|copyable|output)"><div class="code">([\s\S]*?)<\/div>/g,
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
)];
const expected = lines
.filter((line) => line[1] !== 'output')
.map((line) => renderedText(line[2]).replace(/\n$/u, ''));
if (expected.length === 0) continue;
commandBearingBlocks += 1;
Publish shell integration, add a footer, dedupe global options (#4000) Guides and site presentation: one home per topic, a shell-integration page, a footer, and three rendering fixes. **Global options were emitted once per command reference** clap repeats the same ~20-line `Global Options:` block in every reference it renders, so a page assembled from subdocs stacked 11 copies on `/config/` and 13 on `/step/`. That padded the pages and gave site search that many near-identical hits — "squash" returned both `#command-reference` and `#command-reference-2`. `take_global_options` cuts each reference at the heading as it is built, keeping only the first; one `kept` flag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal `--help` renders through clap directly and is unchanged. The config page also carried colliding anchors — two "Hooks" (`#hooks`, `#hooks-1`), two "Aliases", and seven "Examples" (`#examples` … `#examples-6`) — now qualified at their source in `src/cli/config.rs`: User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples. `/step/` still has its own set (eight "Examples", two "Options", two "Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves existing `/step/#examples-N` anchors, so it wants a pass of its own with the inbound links audited; the deduplication above already removes 13 Global Options blocks from that page. **Shell integration has a page** Shell-integration debugging was skill-only: five named warning messages, a PowerShell checklist, and the wrapper mechanism, with no site page — while the FAQ's answer to "`wt switch` didn't cd" was to install the Claude Code plugin. It is now `/shell-integration/`, offered first, with the plugin as the second route. The `llms.txt` listing serves every page as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the listing still advertised; the symlink is added and the sync now fails when a listed page has none. **Presentation** - A site footer carries the version (read from `Cargo.toml` at build time), releases, changelog and license. No page named any of them, and `/code-signing/` was reachable only from inside a collapsed block on the homepage. Starlight's `Footer` is wrapped rather than replaced. - `wt list --full` renders 1157px inside an 800px content column, so 40% of it sat behind a horizontal scrollbar with the pane beside the column empty. A terminal frame now takes the whole pane where there is slack, measured with a query container rather than recomputed from Starlight's layout formula. - The `wt-command-reference` frames offered a copy button for 3,877 characters of generated help text; they now expose no copy control. A console block listing several commands is as often a menu of alternatives as a recipe, and nothing in the markup tells them apart, so every command line in such a block carries its own copy control alongside the block's. - The four command demos and the two hand-written figures get captions; the 2.33 MB homepage GIF below the fold loads lazily. **Sidebar order is pinned** `site-navigation.mjs` told readers a `test_sidebar_matches_frontmatter_order` would fail when the authored sidebar and the pages' `sidebar.order` disagreed. No such test existed, and the disagreement it describes is exactly what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and the `llms.txt` ordering derived from the frontmatter can't drift apart again. <details> <summary>Guide corrections</summary> - Tips & patterns was 26 flat H2 recipes in no order, all 26 in the sidebar. They group under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — with each recipe demoted to H3. Anchors are level-independent, so existing `/tips-patterns/#…` fragments still resolve. - `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program: it is `-x opencode -- run '<task>'`. - The branch-summary preview moved from tab 5 to 6 when the unified-diff tab landed; the recipe names the `summary` tab instead of a number. - The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is `fix-auth`'s port. It is 18283. - `_` in `wt list` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. - `wt step prune` removes branches with no worktree too, and the min-age guard ages a worktree by its creation time and a bare branch by its oldest reflog entry. - `wt step eval -v` prints fifteen variables; the example showed two under a lead calling them "the available template variables". - A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}`, filter gone. - The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. - `skills/worktrunk/reference/README.md` was a symlink to the repo README that `SKILL.md` never referenced, and the plugin mirror dereferenced it into a 262-line copy carrying the star-history token, share links, and a logo path resolving nowhere. Nothing generated it, so deleting the symlink is the whole fix. - One home per topic: agent handoffs stay in tips-patterns, activity markers in `claude-code.md`, alias-template deferral in `extending.md`, and the `codename` filter's two `worktree-path` recipes give way to the config page that owns path templates. The FAQ's "Running tests" and "How can I contribute?" duplicated the README's Contributing block down to the share URLs. - The FAQ linked `/worktrunk/#install`, the `noindex` compatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use `/#install`, where the new sidebar Install entry goes. - Example names settle on `myproject` / `feature-auth`; "sibling to main repo" becomes "sibling to the main worktree", and `wt remove`'s "target worktree" becomes "the worktree being removed" per the project's own terminology rule. </details> UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`, `#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`. Reviewable files: the hand-written pages under `docs/src/content/docs/` (notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`), `docs/src/components/Footer.astro`, `docs/src/plugins/worktrunk-terminal.mjs`, `docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`, `plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are regenerated. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
// The block control carries the bare `copy` class; per-line controls add
docs: fix misplaced copy buttons by dropping terminal frames (#4090) On every shell code block the copy button sat 40px below the top of the code: on the second line of multi-line blocks, and hanging below one-line blocks such as `wt merge --no-ff` on /merge/. Shell blocks were Expressive Code terminal frames, and the terminal plugin deleted their title bar, but Expressive Code still offsets the copy button by that bar's height. ## Changes - `docs/astro.config.mjs` sets `defaultProps: { frame: 'code' }`, so no block is a terminal frame and there is no title bar to remove. The plugin's header-removal hook and its `props.frame = 'terminal'` go, and so does the `wt-commands-only` class: mobile wrapping in `custom.css` now keys on whether a block contains captured output (`:has(.wt-output)`). - Blocks with several commands had a whole-block copy button and per-line buttons in the same corner, handed over on hover by opacity. A hidden button still takes clicks, so clicking a line's button could copy the whole block, and on touch screens one button covered another. Most of these blocks are lists of alternatives, so they now get only per-line buttons, and the hover rule is deleted. - Those blocks wrap (Expressive Code's `wrap` prop) unless they carry captured output, since a per-line button sits at the end of its line and scrolled out of view on the long `jq` examples on /list/. A per-line button is no taller than its line, so buttons on adjacent lines no longer overlap. - The two multi-command blocks that only work run in order, the FAQ's stash recipe and the `Equivalent to:` block under `wt step diff`'s **How it works** (in `src/cli/step.rs`), are now `bash` fences, the form `docs/CLAUDE.md` prescribes for a copyable recipe, so they keep one copy button for the whole recipe. Terminal `--help` renders `console` and `bash` fences alike (it strips `$ `), and the help snapshots are unchanged. ## Side effect Expressive Code strips comment lines only from what terminal frames copy. The eight `bash` and `powershell` blocks with comments on /shell-integration/ now copy them, as console blocks already did. Pasted into zsh without `interactivecomments`, each comment line prints `command not found: #`; the commands still run. ## Tests A browser test checks every copy button on every page, at 393px with touch and at 1376px, for three things: it sits on the line it copies, it is inside the visible code, and it doesn't overlap another button. These checks fail against worktrunk.dev and against a build without the wrap and the height cap. The built-site and plugin tests now expect per-line payloads and no block payload on multi-command blocks, and a plugin test pins that blocks carrying output never wrap. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013U96NY8qKtZhavBSwnfCYq --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 10:00:25 -07:00
// `wt-line-copy`.
Publish shell integration, add a footer, dedupe global options (#4000) Guides and site presentation: one home per topic, a shell-integration page, a footer, and three rendering fixes. **Global options were emitted once per command reference** clap repeats the same ~20-line `Global Options:` block in every reference it renders, so a page assembled from subdocs stacked 11 copies on `/config/` and 13 on `/step/`. That padded the pages and gave site search that many near-identical hits — "squash" returned both `#command-reference` and `#command-reference-2`. `take_global_options` cuts each reference at the heading as it is built, keeping only the first; one `kept` flag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal `--help` renders through clap directly and is unchanged. The config page also carried colliding anchors — two "Hooks" (`#hooks`, `#hooks-1`), two "Aliases", and seven "Examples" (`#examples` … `#examples-6`) — now qualified at their source in `src/cli/config.rs`: User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples. `/step/` still has its own set (eight "Examples", two "Options", two "Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves existing `/step/#examples-N` anchors, so it wants a pass of its own with the inbound links audited; the deduplication above already removes 13 Global Options blocks from that page. **Shell integration has a page** Shell-integration debugging was skill-only: five named warning messages, a PowerShell checklist, and the wrapper mechanism, with no site page — while the FAQ's answer to "`wt switch` didn't cd" was to install the Claude Code plugin. It is now `/shell-integration/`, offered first, with the plugin as the second route. The `llms.txt` listing serves every page as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the listing still advertised; the symlink is added and the sync now fails when a listed page has none. **Presentation** - A site footer carries the version (read from `Cargo.toml` at build time), releases, changelog and license. No page named any of them, and `/code-signing/` was reachable only from inside a collapsed block on the homepage. Starlight's `Footer` is wrapped rather than replaced. - `wt list --full` renders 1157px inside an 800px content column, so 40% of it sat behind a horizontal scrollbar with the pane beside the column empty. A terminal frame now takes the whole pane where there is slack, measured with a query container rather than recomputed from Starlight's layout formula. - The `wt-command-reference` frames offered a copy button for 3,877 characters of generated help text; they now expose no copy control. A console block listing several commands is as often a menu of alternatives as a recipe, and nothing in the markup tells them apart, so every command line in such a block carries its own copy control alongside the block's. - The four command demos and the two hand-written figures get captions; the 2.33 MB homepage GIF below the fold loads lazily. **Sidebar order is pinned** `site-navigation.mjs` told readers a `test_sidebar_matches_frontmatter_order` would fail when the authored sidebar and the pages' `sidebar.order` disagreed. No such test existed, and the disagreement it describes is exactly what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and the `llms.txt` ordering derived from the frontmatter can't drift apart again. <details> <summary>Guide corrections</summary> - Tips & patterns was 26 flat H2 recipes in no order, all 26 in the sidebar. They group under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — with each recipe demoted to H3. Anchors are level-independent, so existing `/tips-patterns/#…` fragments still resolve. - `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program: it is `-x opencode -- run '<task>'`. - The branch-summary preview moved from tab 5 to 6 when the unified-diff tab landed; the recipe names the `summary` tab instead of a number. - The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is `fix-auth`'s port. It is 18283. - `_` in `wt list` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. - `wt step prune` removes branches with no worktree too, and the min-age guard ages a worktree by its creation time and a bare branch by its oldest reflog entry. - `wt step eval -v` prints fifteen variables; the example showed two under a lead calling them "the available template variables". - A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}`, filter gone. - The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. - `skills/worktrunk/reference/README.md` was a symlink to the repo README that `SKILL.md` never referenced, and the plugin mirror dereferenced it into a 262-line copy carrying the star-history token, share links, and a logo path resolving nowhere. Nothing generated it, so deleting the symlink is the whole fix. - One home per topic: agent handoffs stay in tips-patterns, activity markers in `claude-code.md`, alias-template deferral in `extending.md`, and the `codename` filter's two `worktree-path` recipes give way to the config page that owns path templates. The FAQ's "Running tests" and "How can I contribute?" duplicated the README's Contributing block down to the share URLs. - The FAQ linked `/worktrunk/#install`, the `noindex` compatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use `/#install`, where the new sidebar Install entry goes. - Example names settle on `myproject` / `feature-auth`; "sibling to main repo" becomes "sibling to the main worktree", and `wt remove`'s "target worktree" becomes "the worktree being removed" per the project's own terminology rule. </details> UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`, `#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`. Reviewable files: the hand-written pages under `docs/src/content/docs/` (notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`), `docs/src/components/Footer.astro`, `docs/src/plugins/worktrunk-terminal.mjs`, `docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`, `plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are regenerated. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
const encodedPayload = frame.match(
/<div class="copy">[\s\S]*?<button\b[^>]*\bdata-code="([^"]*)"/,
)?.[1];
const commands = lines
.filter((line) => line[1] === 'command')
.map((line) => renderedText(line[2]).replace(/\n$/u, ''));
const perLine = [...frame.matchAll(
/<div class="copy wt-line-copy">[\s\S]*?<button\b[^>]*\bdata-code="([^"]*)"/g,
)].map((line) => renderedText(line[1]));
docs: fix misplaced copy buttons by dropping terminal frames (#4090) On every shell code block the copy button sat 40px below the top of the code: on the second line of multi-line blocks, and hanging below one-line blocks such as `wt merge --no-ff` on /merge/. Shell blocks were Expressive Code terminal frames, and the terminal plugin deleted their title bar, but Expressive Code still offsets the copy button by that bar's height. ## Changes - `docs/astro.config.mjs` sets `defaultProps: { frame: 'code' }`, so no block is a terminal frame and there is no title bar to remove. The plugin's header-removal hook and its `props.frame = 'terminal'` go, and so does the `wt-commands-only` class: mobile wrapping in `custom.css` now keys on whether a block contains captured output (`:has(.wt-output)`). - Blocks with several commands had a whole-block copy button and per-line buttons in the same corner, handed over on hover by opacity. A hidden button still takes clicks, so clicking a line's button could copy the whole block, and on touch screens one button covered another. Most of these blocks are lists of alternatives, so they now get only per-line buttons, and the hover rule is deleted. - Those blocks wrap (Expressive Code's `wrap` prop) unless they carry captured output, since a per-line button sits at the end of its line and scrolled out of view on the long `jq` examples on /list/. A per-line button is no taller than its line, so buttons on adjacent lines no longer overlap. - The two multi-command blocks that only work run in order, the FAQ's stash recipe and the `Equivalent to:` block under `wt step diff`'s **How it works** (in `src/cli/step.rs`), are now `bash` fences, the form `docs/CLAUDE.md` prescribes for a copyable recipe, so they keep one copy button for the whole recipe. Terminal `--help` renders `console` and `bash` fences alike (it strips `$ `), and the help snapshots are unchanged. ## Side effect Expressive Code strips comment lines only from what terminal frames copy. The eight `bash` and `powershell` blocks with comments on /shell-integration/ now copy them, as console blocks already did. Pasted into zsh without `interactivecomments`, each comment line prints `command not found: #`; the commands still run. ## Tests A browser test checks every copy button on every page, at 393px with touch and at 1376px, for three things: it sits on the line it copies, it is inside the visible code, and it doesn't overlap another button. These checks fail against worktrunk.dev and against a build without the wrap and the height cap. The built-site and plugin tests now expect per-line payloads and no block payload on multi-command blocks, and a plugin test pins that blocks carrying output never wrap. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013U96NY8qKtZhavBSwnfCYq --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 10:00:25 -07:00
// Most blocks listing several commands are menus of alternatives, so
// each command line offers its own payload in place of the block's.
Publish shell integration, add a footer, dedupe global options (#4000) Guides and site presentation: one home per topic, a shell-integration page, a footer, and three rendering fixes. **Global options were emitted once per command reference** clap repeats the same ~20-line `Global Options:` block in every reference it renders, so a page assembled from subdocs stacked 11 copies on `/config/` and 13 on `/step/`. That padded the pages and gave site search that many near-identical hits — "squash" returned both `#command-reference` and `#command-reference-2`. `take_global_options` cuts each reference at the heading as it is built, keeping only the first; one `kept` flag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal `--help` renders through clap directly and is unchanged. The config page also carried colliding anchors — two "Hooks" (`#hooks`, `#hooks-1`), two "Aliases", and seven "Examples" (`#examples` … `#examples-6`) — now qualified at their source in `src/cli/config.rs`: User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples. `/step/` still has its own set (eight "Examples", two "Options", two "Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves existing `/step/#examples-N` anchors, so it wants a pass of its own with the inbound links audited; the deduplication above already removes 13 Global Options blocks from that page. **Shell integration has a page** Shell-integration debugging was skill-only: five named warning messages, a PowerShell checklist, and the wrapper mechanism, with no site page — while the FAQ's answer to "`wt switch` didn't cd" was to install the Claude Code plugin. It is now `/shell-integration/`, offered first, with the plugin as the second route. The `llms.txt` listing serves every page as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the listing still advertised; the symlink is added and the sync now fails when a listed page has none. **Presentation** - A site footer carries the version (read from `Cargo.toml` at build time), releases, changelog and license. No page named any of them, and `/code-signing/` was reachable only from inside a collapsed block on the homepage. Starlight's `Footer` is wrapped rather than replaced. - `wt list --full` renders 1157px inside an 800px content column, so 40% of it sat behind a horizontal scrollbar with the pane beside the column empty. A terminal frame now takes the whole pane where there is slack, measured with a query container rather than recomputed from Starlight's layout formula. - The `wt-command-reference` frames offered a copy button for 3,877 characters of generated help text; they now expose no copy control. A console block listing several commands is as often a menu of alternatives as a recipe, and nothing in the markup tells them apart, so every command line in such a block carries its own copy control alongside the block's. - The four command demos and the two hand-written figures get captions; the 2.33 MB homepage GIF below the fold loads lazily. **Sidebar order is pinned** `site-navigation.mjs` told readers a `test_sidebar_matches_frontmatter_order` would fail when the authored sidebar and the pages' `sidebar.order` disagreed. No such test existed, and the disagreement it describes is exactly what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and the `llms.txt` ordering derived from the frontmatter can't drift apart again. <details> <summary>Guide corrections</summary> - Tips & patterns was 26 flat H2 recipes in no order, all 26 in the sidebar. They group under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — with each recipe demoted to H3. Anchors are level-independent, so existing `/tips-patterns/#…` fragments still resolve. - `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program: it is `-x opencode -- run '<task>'`. - The branch-summary preview moved from tab 5 to 6 when the unified-diff tab landed; the recipe names the `summary` tab instead of a number. - The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is `fix-auth`'s port. It is 18283. - `_` in `wt list` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. - `wt step prune` removes branches with no worktree too, and the min-age guard ages a worktree by its creation time and a bare branch by its oldest reflog entry. - `wt step eval -v` prints fifteen variables; the example showed two under a lead calling them "the available template variables". - A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}`, filter gone. - The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. - `skills/worktrunk/reference/README.md` was a symlink to the repo README that `SKILL.md` never referenced, and the plugin mirror dereferenced it into a 262-line copy carrying the star-history token, share links, and a logo path resolving nowhere. Nothing generated it, so deleting the symlink is the whole fix. - One home per topic: agent handoffs stay in tips-patterns, activity markers in `claude-code.md`, alias-template deferral in `extending.md`, and the `codename` filter's two `worktree-path` recipes give way to the config page that owns path templates. The FAQ's "Running tests" and "How can I contribute?" duplicated the README's Contributing block down to the share URLs. - The FAQ linked `/worktrunk/#install`, the `noindex` compatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use `/#install`, where the new sidebar Install entry goes. - Example names settle on `myproject` / `feature-auth`; "sibling to main repo" becomes "sibling to the main worktree", and `wt remove`'s "target worktree" becomes "the worktree being removed" per the project's own terminology rule. </details> UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`, `#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`. Reviewable files: the hand-written pages under `docs/src/content/docs/` (notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`), `docs/src/components/Footer.astro`, `docs/src/plugins/worktrunk-terminal.mjs`, `docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`, `plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are regenerated. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
if (commands.length > 1) {
perLineBlocks += 1;
docs: fix misplaced copy buttons by dropping terminal frames (#4090) On every shell code block the copy button sat 40px below the top of the code: on the second line of multi-line blocks, and hanging below one-line blocks such as `wt merge --no-ff` on /merge/. Shell blocks were Expressive Code terminal frames, and the terminal plugin deleted their title bar, but Expressive Code still offsets the copy button by that bar's height. ## Changes - `docs/astro.config.mjs` sets `defaultProps: { frame: 'code' }`, so no block is a terminal frame and there is no title bar to remove. The plugin's header-removal hook and its `props.frame = 'terminal'` go, and so does the `wt-commands-only` class: mobile wrapping in `custom.css` now keys on whether a block contains captured output (`:has(.wt-output)`). - Blocks with several commands had a whole-block copy button and per-line buttons in the same corner, handed over on hover by opacity. A hidden button still takes clicks, so clicking a line's button could copy the whole block, and on touch screens one button covered another. Most of these blocks are lists of alternatives, so they now get only per-line buttons, and the hover rule is deleted. - Those blocks wrap (Expressive Code's `wrap` prop) unless they carry captured output, since a per-line button sits at the end of its line and scrolled out of view on the long `jq` examples on /list/. A per-line button is no taller than its line, so buttons on adjacent lines no longer overlap. - The two multi-command blocks that only work run in order, the FAQ's stash recipe and the `Equivalent to:` block under `wt step diff`'s **How it works** (in `src/cli/step.rs`), are now `bash` fences, the form `docs/CLAUDE.md` prescribes for a copyable recipe, so they keep one copy button for the whole recipe. Terminal `--help` renders `console` and `bash` fences alike (it strips `$ `), and the help snapshots are unchanged. ## Side effect Expressive Code strips comment lines only from what terminal frames copy. The eight `bash` and `powershell` blocks with comments on /shell-integration/ now copy them, as console blocks already did. Pasted into zsh without `interactivecomments`, each comment line prints `command not found: #`; the commands still run. ## Tests A browser test checks every copy button on every page, at 393px with touch and at 1376px, for three things: it sits on the line it copies, it is inside the visible code, and it doesn't overlap another button. These checks fail against worktrunk.dev and against a build without the wrap and the height cap. The built-site and plugin tests now expect per-line payloads and no block payload on multi-command blocks, and a plugin test pins that blocks carrying output never wrap. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013U96NY8qKtZhavBSwnfCYq --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 10:00:25 -07:00
assert.equal(encodedPayload, undefined, `${page} offers a block copy beside per-line copies`);
Publish shell integration, add a footer, dedupe global options (#4000) Guides and site presentation: one home per topic, a shell-integration page, a footer, and three rendering fixes. **Global options were emitted once per command reference** clap repeats the same ~20-line `Global Options:` block in every reference it renders, so a page assembled from subdocs stacked 11 copies on `/config/` and 13 on `/step/`. That padded the pages and gave site search that many near-identical hits — "squash" returned both `#command-reference` and `#command-reference-2`. `take_global_options` cuts each reference at the heading as it is built, keeping only the first; one `kept` flag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal `--help` renders through clap directly and is unchanged. The config page also carried colliding anchors — two "Hooks" (`#hooks`, `#hooks-1`), two "Aliases", and seven "Examples" (`#examples` … `#examples-6`) — now qualified at their source in `src/cli/config.rs`: User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples. `/step/` still has its own set (eight "Examples", two "Options", two "Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves existing `/step/#examples-N` anchors, so it wants a pass of its own with the inbound links audited; the deduplication above already removes 13 Global Options blocks from that page. **Shell integration has a page** Shell-integration debugging was skill-only: five named warning messages, a PowerShell checklist, and the wrapper mechanism, with no site page — while the FAQ's answer to "`wt switch` didn't cd" was to install the Claude Code plugin. It is now `/shell-integration/`, offered first, with the plugin as the second route. The `llms.txt` listing serves every page as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the listing still advertised; the symlink is added and the sync now fails when a listed page has none. **Presentation** - A site footer carries the version (read from `Cargo.toml` at build time), releases, changelog and license. No page named any of them, and `/code-signing/` was reachable only from inside a collapsed block on the homepage. Starlight's `Footer` is wrapped rather than replaced. - `wt list --full` renders 1157px inside an 800px content column, so 40% of it sat behind a horizontal scrollbar with the pane beside the column empty. A terminal frame now takes the whole pane where there is slack, measured with a query container rather than recomputed from Starlight's layout formula. - The `wt-command-reference` frames offered a copy button for 3,877 characters of generated help text; they now expose no copy control. A console block listing several commands is as often a menu of alternatives as a recipe, and nothing in the markup tells them apart, so every command line in such a block carries its own copy control alongside the block's. - The four command demos and the two hand-written figures get captions; the 2.33 MB homepage GIF below the fold loads lazily. **Sidebar order is pinned** `site-navigation.mjs` told readers a `test_sidebar_matches_frontmatter_order` would fail when the authored sidebar and the pages' `sidebar.order` disagreed. No such test existed, and the disagreement it describes is exactly what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and the `llms.txt` ordering derived from the frontmatter can't drift apart again. <details> <summary>Guide corrections</summary> - Tips & patterns was 26 flat H2 recipes in no order, all 26 in the sidebar. They group under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — with each recipe demoted to H3. Anchors are level-independent, so existing `/tips-patterns/#…` fragments still resolve. - `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program: it is `-x opencode -- run '<task>'`. - The branch-summary preview moved from tab 5 to 6 when the unified-diff tab landed; the recipe names the `summary` tab instead of a number. - The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is `fix-auth`'s port. It is 18283. - `_` in `wt list` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. - `wt step prune` removes branches with no worktree too, and the min-age guard ages a worktree by its creation time and a bare branch by its oldest reflog entry. - `wt step eval -v` prints fifteen variables; the example showed two under a lead calling them "the available template variables". - A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}`, filter gone. - The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. - `skills/worktrunk/reference/README.md` was a symlink to the repo README that `SKILL.md` never referenced, and the plugin mirror dereferenced it into a 262-line copy carrying the star-history token, share links, and a logo path resolving nowhere. Nothing generated it, so deleting the symlink is the whole fix. - One home per topic: agent handoffs stay in tips-patterns, activity markers in `claude-code.md`, alias-template deferral in `extending.md`, and the `codename` filter's two `worktree-path` recipes give way to the config page that owns path templates. The FAQ's "Running tests" and "How can I contribute?" duplicated the README's Contributing block down to the share URLs. - The FAQ linked `/worktrunk/#install`, the `noindex` compatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use `/#install`, where the new sidebar Install entry goes. - Example names settle on `myproject` / `feature-auth`; "sibling to main repo" becomes "sibling to the main worktree", and `wt remove`'s "target worktree" becomes "the worktree being removed" per the project's own terminology rule. </details> UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`, `#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`. Reviewable files: the hand-written pages under `docs/src/content/docs/` (notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`), `docs/src/components/Footer.astro`, `docs/src/plugins/worktrunk-terminal.mjs`, `docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`, `plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are regenerated. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
assert.deepEqual(perLine, commands, `${page} per-line copy payloads do not match its commands`);
} else {
docs: fix misplaced copy buttons by dropping terminal frames (#4090) On every shell code block the copy button sat 40px below the top of the code: on the second line of multi-line blocks, and hanging below one-line blocks such as `wt merge --no-ff` on /merge/. Shell blocks were Expressive Code terminal frames, and the terminal plugin deleted their title bar, but Expressive Code still offsets the copy button by that bar's height. ## Changes - `docs/astro.config.mjs` sets `defaultProps: { frame: 'code' }`, so no block is a terminal frame and there is no title bar to remove. The plugin's header-removal hook and its `props.frame = 'terminal'` go, and so does the `wt-commands-only` class: mobile wrapping in `custom.css` now keys on whether a block contains captured output (`:has(.wt-output)`). - Blocks with several commands had a whole-block copy button and per-line buttons in the same corner, handed over on hover by opacity. A hidden button still takes clicks, so clicking a line's button could copy the whole block, and on touch screens one button covered another. Most of these blocks are lists of alternatives, so they now get only per-line buttons, and the hover rule is deleted. - Those blocks wrap (Expressive Code's `wrap` prop) unless they carry captured output, since a per-line button sits at the end of its line and scrolled out of view on the long `jq` examples on /list/. A per-line button is no taller than its line, so buttons on adjacent lines no longer overlap. - The two multi-command blocks that only work run in order, the FAQ's stash recipe and the `Equivalent to:` block under `wt step diff`'s **How it works** (in `src/cli/step.rs`), are now `bash` fences, the form `docs/CLAUDE.md` prescribes for a copyable recipe, so they keep one copy button for the whole recipe. Terminal `--help` renders `console` and `bash` fences alike (it strips `$ `), and the help snapshots are unchanged. ## Side effect Expressive Code strips comment lines only from what terminal frames copy. The eight `bash` and `powershell` blocks with comments on /shell-integration/ now copy them, as console blocks already did. Pasted into zsh without `interactivecomments`, each comment line prints `command not found: #`; the commands still run. ## Tests A browser test checks every copy button on every page, at 393px with touch and at 1376px, for three things: it sits on the line it copies, it is inside the visible code, and it doesn't overlap another button. These checks fail against worktrunk.dev and against a build without the wrap and the height cap. The built-site and plugin tests now expect per-line payloads and no block payload on multi-command blocks, and a plugin test pins that blocks carrying output never wrap. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013U96NY8qKtZhavBSwnfCYq --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 10:00:25 -07:00
assert.notEqual(encodedPayload, undefined, `${page} is missing a terminal copy payload`);
assert.equal(renderedText(encodedPayload), expected.join('\u007f'), `${page} copies captured output`);
Publish shell integration, add a footer, dedupe global options (#4000) Guides and site presentation: one home per topic, a shell-integration page, a footer, and three rendering fixes. **Global options were emitted once per command reference** clap repeats the same ~20-line `Global Options:` block in every reference it renders, so a page assembled from subdocs stacked 11 copies on `/config/` and 13 on `/step/`. That padded the pages and gave site search that many near-identical hits — "squash" returned both `#command-reference` and `#command-reference-2`. `take_global_options` cuts each reference at the heading as it is built, keeping only the first; one `kept` flag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal `--help` renders through clap directly and is unchanged. The config page also carried colliding anchors — two "Hooks" (`#hooks`, `#hooks-1`), two "Aliases", and seven "Examples" (`#examples` … `#examples-6`) — now qualified at their source in `src/cli/config.rs`: User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples. `/step/` still has its own set (eight "Examples", two "Options", two "Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves existing `/step/#examples-N` anchors, so it wants a pass of its own with the inbound links audited; the deduplication above already removes 13 Global Options blocks from that page. **Shell integration has a page** Shell-integration debugging was skill-only: five named warning messages, a PowerShell checklist, and the wrapper mechanism, with no site page — while the FAQ's answer to "`wt switch` didn't cd" was to install the Claude Code plugin. It is now `/shell-integration/`, offered first, with the plugin as the second route. The `llms.txt` listing serves every page as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the listing still advertised; the symlink is added and the sync now fails when a listed page has none. **Presentation** - A site footer carries the version (read from `Cargo.toml` at build time), releases, changelog and license. No page named any of them, and `/code-signing/` was reachable only from inside a collapsed block on the homepage. Starlight's `Footer` is wrapped rather than replaced. - `wt list --full` renders 1157px inside an 800px content column, so 40% of it sat behind a horizontal scrollbar with the pane beside the column empty. A terminal frame now takes the whole pane where there is slack, measured with a query container rather than recomputed from Starlight's layout formula. - The `wt-command-reference` frames offered a copy button for 3,877 characters of generated help text; they now expose no copy control. A console block listing several commands is as often a menu of alternatives as a recipe, and nothing in the markup tells them apart, so every command line in such a block carries its own copy control alongside the block's. - The four command demos and the two hand-written figures get captions; the 2.33 MB homepage GIF below the fold loads lazily. **Sidebar order is pinned** `site-navigation.mjs` told readers a `test_sidebar_matches_frontmatter_order` would fail when the authored sidebar and the pages' `sidebar.order` disagreed. No such test existed, and the disagreement it describes is exactly what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and the `llms.txt` ordering derived from the frontmatter can't drift apart again. <details> <summary>Guide corrections</summary> - Tips & patterns was 26 flat H2 recipes in no order, all 26 in the sidebar. They group under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — with each recipe demoted to H3. Anchors are level-independent, so existing `/tips-patterns/#…` fragments still resolve. - `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program: it is `-x opencode -- run '<task>'`. - The branch-summary preview moved from tab 5 to 6 when the unified-diff tab landed; the recipe names the `summary` tab instead of a number. - The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is `fix-auth`'s port. It is 18283. - `_` in `wt list` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. - `wt step prune` removes branches with no worktree too, and the min-age guard ages a worktree by its creation time and a bare branch by its oldest reflog entry. - `wt step eval -v` prints fifteen variables; the example showed two under a lead calling them "the available template variables". - A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}`, filter gone. - The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. - `skills/worktrunk/reference/README.md` was a symlink to the repo README that `SKILL.md` never referenced, and the plugin mirror dereferenced it into a 262-line copy carrying the star-history token, share links, and a logo path resolving nowhere. Nothing generated it, so deleting the symlink is the whole fix. - One home per topic: agent handoffs stay in tips-patterns, activity markers in `claude-code.md`, alias-template deferral in `extending.md`, and the `codename` filter's two `worktree-path` recipes give way to the config page that owns path templates. The FAQ's "Running tests" and "How can I contribute?" duplicated the README's Contributing block down to the share URLs. - The FAQ linked `/worktrunk/#install`, the `noindex` compatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use `/#install`, where the new sidebar Install entry goes. - Example names settle on `myproject` / `feature-auth`; "sibling to main repo" becomes "sibling to the main worktree", and `wt remove`'s "target worktree" becomes "the worktree being removed" per the project's own terminology rule. </details> UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`, `#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`. Reviewable files: the hand-written pages under `docs/src/content/docs/` (notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`), `docs/src/components/Footer.astro`, `docs/src/plugins/worktrunk-terminal.mjs`, `docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`, `plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are regenerated. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
assert.deepEqual(perLine, [], `${page} adds per-line copy to a single-command block`);
}
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
}
}
assert.ok(commandBearingBlocks > 0, 'expected command-bearing console blocks');
Publish shell integration, add a footer, dedupe global options (#4000) Guides and site presentation: one home per topic, a shell-integration page, a footer, and three rendering fixes. **Global options were emitted once per command reference** clap repeats the same ~20-line `Global Options:` block in every reference it renders, so a page assembled from subdocs stacked 11 copies on `/config/` and 13 on `/step/`. That padded the pages and gave site search that many near-identical hits — "squash" returned both `#command-reference` and `#command-reference-2`. `take_global_options` cuts each reference at the heading as it is built, keeping only the first; one `kept` flag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal `--help` renders through clap directly and is unchanged. The config page also carried colliding anchors — two "Hooks" (`#hooks`, `#hooks-1`), two "Aliases", and seven "Examples" (`#examples` … `#examples-6`) — now qualified at their source in `src/cli/config.rs`: User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples. `/step/` still has its own set (eight "Examples", two "Options", two "Arguments", plus "Staging" and "Dry run" pairs). Qualifying those moves existing `/step/#examples-N` anchors, so it wants a pass of its own with the inbound links audited; the deduplication above already removes 13 Global Options blocks from that page. **Shell integration has a page** Shell-integration debugging was skill-only: five named warning messages, a PowerShell checklist, and the wrapper mechanism, with no site page — while the FAQ's answer to "`wt switch` didn't cd" was to install the Claude Code plugin. It is now `/shell-integration/`, offered first, with the plugin as the second route. The `llms.txt` listing serves every page as `/<slug>.md` from a hand-created symlink, so a new page was a 404 the listing still advertised; the symlink is added and the sync now fails when a listed page has none. **Presentation** - A site footer carries the version (read from `Cargo.toml` at build time), releases, changelog and license. No page named any of them, and `/code-signing/` was reachable only from inside a collapsed block on the homepage. Starlight's `Footer` is wrapped rather than replaced. - `wt list --full` renders 1157px inside an 800px content column, so 40% of it sat behind a horizontal scrollbar with the pane beside the column empty. A terminal frame now takes the whole pane where there is slack, measured with a query container rather than recomputed from Starlight's layout formula. - The `wt-command-reference` frames offered a copy button for 3,877 characters of generated help text; they now expose no copy control. A console block listing several commands is as often a menu of alternatives as a recipe, and nothing in the markup tells them apart, so every command line in such a block carries its own copy control alongside the block's. - The four command demos and the two hand-written figures get captions; the 2.33 MB homepage GIF below the fold loads lazily. **Sidebar order is pinned** `site-navigation.mjs` told readers a `test_sidebar_matches_frontmatter_order` would fail when the authored sidebar and the pages' `sidebar.order` disagreed. No such test existed, and the disagreement it describes is exactly what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and the `llms.txt` ordering derived from the frontmatter can't drift apart again. <details> <summary>Guide corrections</summary> - Tips & patterns was 26 flat H2 recipes in no order, all 26 in the sidebar. They group under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — with each recipe demoted to H3. Anchors are level-independent, so existing `/tips-patterns/#…` fragments still resolve. - `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program: it is `-x opencode -- run '<task>'`. - The branch-summary preview moved from tab 5 to 6 when the unified-diff tab landed; the recipe names the `summary` tab instead of a number. - The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is `fix-auth`'s port. It is 18283. - `_` in `wt list` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. - `wt step prune` removes branches with no worktree too, and the min-age guard ages a worktree by its creation time and a bare branch by its oldest reflog entry. - `wt step eval -v` prints fifteen variables; the example showed two under a lead calling them "the available template variables". - A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}`, filter gone. - The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. - `skills/worktrunk/reference/README.md` was a symlink to the repo README that `SKILL.md` never referenced, and the plugin mirror dereferenced it into a 262-line copy carrying the star-history token, share links, and a logo path resolving nowhere. Nothing generated it, so deleting the symlink is the whole fix. - One home per topic: agent handoffs stay in tips-patterns, activity markers in `claude-code.md`, alias-template deferral in `extending.md`, and the `codename` filter's two `worktree-path` recipes give way to the config page that owns path templates. The FAQ's "Running tests" and "How can I contribute?" duplicated the README's Contributing block down to the share URLs. - The FAQ linked `/worktrunk/#install`, the `noindex` compatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use `/#install`, where the new sidebar Install entry goes. - Example names settle on `myproject` / `feature-auth`; "sibling to main repo" becomes "sibling to the main worktree", and `wt remove`'s "target worktree" becomes "the worktree being removed" per the project's own terminology rule. </details> UX survey items: `#36`, `#37`, `#38`, `#39`, `#45`, `#47`, `#48`, `#49`, `#50`, `#51`, `#52`, `#61`, `#94`, `#95`, `#96`, `#97`, `#99`, `#100`. Reviewable files: the hand-written pages under `docs/src/content/docs/` (notably the new `shell-integration.md`, `tips-patterns.md`, `faq.md`), `docs/src/components/Footer.astro`, `docs/src/plugins/worktrunk-terminal.mjs`, `docs/src/site-navigation.mjs`, `docs/tests/*.mjs`, `src/help.rs`, `plugins/worktrunk/hooks/wt.sh`. Generated mirrors and snapshots are regenerated. > _This was written by Claude Code on behalf of max-sixty_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
assert.ok(perLineBlocks > 0, 'expected blocks listing several commands');
});
test('generated command references expose no copy control', async () => {
let references = 0;
for (const page of renderedPages) {
const html = await readFile(page, 'utf8');
for (const match of html.matchAll(
/<figure class="frame[^"]*\bwt-command-reference\b[^"]*">([\s\S]*?)<\/figure>/g,
)) {
references += 1;
assert.doesNotMatch(
match[1],
/class="copy/,
`${page} offers to copy a page of generated help text`,
);
}
}
assert.ok(references > 0, 'expected generated command references');
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
});
test('titleless code frames do not render decorative headers', async () => {
for (const page of renderedPages) {
const html = await readFile(page, 'utf8');
assert.doesNotMatch(
html,
/<figcaption class="header"><span class="title"><\/span>/,
`${page} renders chrome for an untitled code block`,
);
}
});
test('long desktop TOCs keep the current link inside their own scrollport', () => {
const scrollport = {
clientHeight: 1_000,
scrollHeight: 1_961,
scrollTop: 0,
getBoundingClientRect: () => ({ top: 0, bottom: 1_000 }),
};
const visibleToc = {
closest: () => scrollport,
getClientRects: () => [{}],
};
const current = {
closest: () => visibleToc,
getBoundingClientRect: () => ({ top: 1_386.84375, bottom: 1_411.09375 }),
};
assert.equal(keepCurrentTocLinkVisible(current), true);
assert.equal(scrollport.scrollTop, 459.09375);
current.getBoundingClientRect = () => ({ top: 500, bottom: 525 });
assert.equal(keepCurrentTocLinkVisible(current), false);
assert.equal(scrollport.scrollTop, 459.09375);
current.getBoundingClientRect = () => ({ top: 20, bottom: 45 });
assert.equal(keepCurrentTocLinkVisible(current), true);
assert.equal(scrollport.scrollTop, 431.09375);
visibleToc.getClientRects = () => [];
current.getBoundingClientRect = () => ({ top: 1_386.84375, bottom: 1_411.09375 });
assert.equal(keepCurrentTocLinkVisible(current), false);
assert.equal(scrollport.scrollTop, 431.09375);
visibleToc.getClientRects = () => [{}];
scrollport.scrollHeight = scrollport.clientHeight;
assert.equal(keepCurrentTocLinkVisible(current), false);
assert.equal(scrollport.scrollTop, 431.09375);
});
test('hash navigation selects the requested section in every rendered TOC', () => {
const link = (hash, current = false) => ({
hash,
current,
getAttribute(name) {
return name === 'aria-current' && this.current ? 'true' : null;
},
setAttribute(name, value) {
if (name === 'aria-current') this.current = value === 'true';
},
removeAttribute(name) {
if (name === 'aria-current') this.current = false;
},
textContent: hash.slice(1).replaceAll('-', ' '),
});
const root = (mobile = false) => {
const overview = link('#_top', true);
const requested = link('#reporting-a-problem');
const display = mobile ? { textContent: '' } : null;
return {
display,
links: [overview, requested],
querySelector(selector) {
return selector === '.display-current' ? display : null;
},
querySelectorAll(selector) {
if (selector === 'a') return this.links;
if (selector === 'a[aria-current="true"]') {
return this.links.filter((candidate) => candidate.current);
}
return [];
},
};
};
const desktop = root();
const mobile = root(true);
const doc = {
querySelectorAll: () => [desktop, mobile],
};
assert.equal(syncTocCurrentToHash(doc, '#reporting-a-problem'), true);
for (const toc of [desktop, mobile]) {
assert.equal(toc.links[0].current, false);
assert.equal(toc.links[1].current, true);
}
assert.equal(mobile.display.textContent, 'reporting a problem');
assert.equal(syncTocCurrentToHash(doc, '#missing'), false);
});
test('every structural content heading appears in the page outline', async () => {
for (const route of publicRoutes.filter((candidate) => candidate !== '/')) {
const page = await readFile(routeFile(route), 'utf8');
const headings = [...page.matchAll(/<h[12]\b[^>]*\bid="([^"]+)"/g)]
.map((match) => match[1])
.filter((id) => id !== 'starlight__on-this-page');
const toc = page.match(/<nav aria-labelledby="starlight__on-this-page">([\s\S]*?)<\/nav>/)?.[1];
assert.ok(toc, `${route} is missing its desktop table of contents`);
const links = [...toc.matchAll(/<a href="#([^"]+)"/g)].map((match) => match[1]);
assert.deepEqual(links, headings, `${route} outline does not match its structural headings`);
}
});
test('short wide tables become labeled records without capturing dense tables', async () => {
const pages = new Map(await Promise.all(publicRoutes.map(async (route) => (
[route, await readFile(routeFile(route), 'utf8')]
))));
const tables = new Map([...pages].map(([route, html]) => [route, tableSummaries(html)]));
const findTable = (route, headers) => {
const table = tables.get(route).find((candidate) => (
candidate.headers.join('|') === headers.join('|')
));
assert.ok(table, `${route} is missing table ${headers.join(' | ')}`);
return table;
};
const configFiles = findTable(
'/config/',
['File', 'Location', 'Contains', 'Committed & shared'],
);
assert.match(configFiles.attributes, /class="wt-responsive-records"/);
for (const pageTables of tables.values()) {
for (const table of pageTables) {
if (!/\bwt-responsive-records\b/.test(table.attributes)) continue;
assert.ok(table.headers.length >= 3);
assert.ok(table.rowLabels.length >= 1 && table.rowLabels.length <= 3);
assert.deepEqual(
table.rowLabels,
Array.from({ length: table.rowLabels.length }, () => table.headers),
);
}
}
assert.doesNotMatch(
findTable('/extending/', ['', 'Hooks', 'Aliases', 'Custom subcommands']).attributes,
/\bwt-responsive-records\b/,
);
assert.doesNotMatch(
findTable('/claude-code/', ['Capability', 'Claude Code', 'Codex', 'OpenCode', 'Pi', 'Gemini CLI']).attributes,
docs: rebuild the site with Astro and Starlight (#3866) The docs now build with Astro and Starlight instead of Zola. This removes the Tera 2 migration blocker from #3827 while keeping the published routes, anchors, generated references, metadata, and crawler URLs stable. It replaces the approach closed in #3840. ## What changed - Move the site into Starlight, with a custom Worktrunk homepage and a shared copper, gold, paper, and ink design system. - Keep generated docs as portable Markdown. The Rust sync pipeline no longer needs Zola shortcodes, template escaping, ANSI-to-HTML conversion, or reverse transforms for skill and README output. - Preserve terminal semantics and command-only copying, add responsive handling for short wide tables, retain stable heading IDs, and give repeated command-reference headings useful search labels. - Move docs CI and publishing to Node, Astro, and built-site contract tests. The tests cover public routes, links, assets, metadata, navigation, tables, terminal frames, and compatibility aliases. - Record demo themes from isolated environments, add a mobile core demo, and test the recording contract. The matching mobile assets are published in `max-sixty/worktrunk-assets`. Closes #3827. ## Verification - `cargo run -- hook pre-merge --yes` (4,660 tests) - `npm --prefix docs run check` - `npm --prefix docs test` (9 tests) - `npm --prefix docs run build` (16 pages) - `npm --prefix docs run test:site` (12 tests) - `pytest docs/demos/tests/test_recording.py` (4 tests) > _This was written by Codex on behalf of max-sixty_
2026-08-22 00:01:58 -07:00
/\bwt-responsive-records\b/,
);
assert.doesNotMatch(
findTable('/hook/', ['Kind', 'Variable', 'Description']).attributes,
/\bwt-responsive-records\b/,
);
assert.doesNotMatch(
findTable('/list/', ['Column', 'Shows']).attributes,
/\bwt-responsive-records\b/,
);
assert.doesNotMatch(
findTable('/', ['Task', 'Worktrunk', 'Plain git']).attributes,
/\bwt-responsive-records\b/,
);
});
test('mobile menu control is hidden until its script is available', async () => {
const faqPage = await readFile(routeFile('/faq/'), 'utf8');
assert.match(faqPage, /<starlight-menu-button\b/);
assert.match(
faqPage,
/<style>\s*starlight-menu-button:not\(:defined\)\s*\{\s*display:\s*none;\s*\}\s*<\/style>/,
);
});
test('built pages have unique IDs and valid internal page links', async () => {
const pages = await htmlFiles();
const contents = new Map(await Promise.all(pages.map(async (file) => [file, await readFile(file, 'utf8')])));
for (const [source, html] of contents) {
const ids = [...html.matchAll(/\sid="([^"]+)"/g)].map((match) => match[1]);
assert.equal(new Set(ids).size, ids.length, `duplicate ID in ${source}`);
for (const match of html.matchAll(/\shref="([^"]+)"/g)) {
const href = match[1].replaceAll('&amp;', '&');
const url = new URL(href, `https://worktrunk.dev${fileRoute(source)}`);
if (url.origin !== 'https://worktrunk.dev') continue;
const targetFile = routeFile(decodeURIComponent(url.pathname));
if (!targetFile) continue;
const target = contents.get(targetFile);
assert.ok(target, `${source} links to missing page ${url.pathname}`);
if (!url.hash) continue;
const id = decodeURIComponent(url.hash.slice(1));
assert.match(target, new RegExp(`\\sid="${id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`), `${source} links to missing ${url.hash}`);
}
}
});