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 ( '&' , '&' )
. replaceAll ( '&' , '&' )
. 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 ( '"' , '"' )
. replaceAll ( '>' , '>' )
. replaceAll ( '<' , '<' )
. replaceAll ( '&' , '&' ) ;
}
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 ) ;
2026-08-27 17:40:02 -07:00
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 ) ;
2026-08-27 17:40:02 -07:00
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 ) ;
2026-08-27 12:14:39 -07:00
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/ ) ;
2026-08-27 12:14:39 -07:00
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"><COMMAND><\/span>/ ,
) ;
2026-08-27 12:14:39 -07:00
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 ,
Replace config update --print with --output (#4021)
`wt config update` now uses a destination-based output interface:
`--output <path>` atomically writes the migration artifact, while
`--output=-` emits the same bytes to stdout. This replaces `--print`,
keeps output mode read-only, and includes project-config migrations when
invoked from a linked worktree.
Relative destinations honor the global `-C` directory. When there is
nothing to migrate, stdout stays silent and an existing file destination
remains untouched. If both user and project configs need migration,
stdout emits a labeled inspection artifact while file output fails
before writing. Help text also notes that output artifacts omit legacy
`approved-commands`; the in-place mode remains responsible for moving
those entries to `approvals.toml`.
Integration coverage exercises file replacement, preservation and write
failures, multi-config rejection, stdout piping and broken consumers,
clean configs, linked worktrees, source-file preservation, help text,
and rejection of the removed flag. The full pre-merge gate passed all
4,770 tests, targeted instrumentation covers both file-error branches,
and the production docs build passed all 16 built-site and browser
tests.
> _This was written by Codex on behalf of max-sixty_
2026-09-06 15:07:29 -07:00
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 } " ` ) ) ;
}
} ) ;
2026-08-22 19:39:20 -07:00
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"><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 ( '&' , '&' ) , ` 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 (
2026-09-06 23:22:18 +05:30
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 ( '&' , '&' ) ;
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 } ` ) ;
}
}
} ) ;