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>
10 KiB
Documentation Site
The Worktrunk site is built with Astro and Starlight and published at https://worktrunk.dev.
Development
The project hook installs the site dependencies, fetches the demo assets, and
starts the Astro dev server. Find this worktree's URL with wt list.
To run it yourself:
npm --prefix docs install
npm --prefix docs exec playwright install webkit
npm --prefix docs run dev -- --host 127.0.0.1 --port 4321
The local production checks are:
npm --prefix docs run check
npm --prefix docs test
npm --prefix docs run build
npm --prefix docs run test:site
npm run build clears Astro's content cache, writes docs/dist/, and builds
the Pagefind search index. The forced content rebuild is intentional: renderer
plugin changes affect generated asset hashes but are not part of Astro's
content-cache key.
Verifying changes
Text-only edits need the docs sync test and a production build. Visual changes also need browser verification with Playwright at desktop and mobile widths. Check the changed page, one command-reference page, search, the mobile menu, theme switching, anchor navigation, code copying, wide tables, and demo images.
Always include the local dev-server link when handing off a docs change:
View changes: http://127.0.0.1:<port>
Site architecture
| Path | Responsibility |
|---|---|
astro.config.mjs |
Starlight integration, navigation, metadata, and code rendering |
src/content/docs/ |
Canonical documentation Markdown |
src/pages/index.astro |
Homepage route; renders the canonical worktrunk.md body |
src/styles/custom.css |
Worktrunk's visual system and narrow Starlight adjustments |
src/plugins/stable-heading-ids.mjs |
Stable public anchor IDs for Markdown headings |
src/plugins/worktrunk-terminal.mjs |
Shell syntax, homepage comparison roles, prompts, copy behavior, Clap help roles, and ANSI-derived output styling |
src/themes/worktrunk-code.mjs |
Light and dark syntax themes for source and command blocks |
src/generated/terminal-styles.json |
Generated site-only span styles from ANSI command snapshots |
src/components/Head.astro |
Social metadata, structured data, and analytics |
tests/ |
Renderer unit tests plus built-site and WebKit mobile checks |
public/ |
Files served unchanged at the site root |
demos/ |
VHS sources and scripts for the external demo assets |
The site uses Starlight's own navigation, table of contents, responsive shell, search, code frames, copy controls, and theme selector. Keep overrides narrow. Before replacing a Starlight component, check whether configuration or CSS can express the change. Every full component override becomes an upstream merge surface.
The visual direction is a warm technical field manual: ivory paper, dark ink, one orange accent, strong typography, and terminal output as the main visual material. Avoid generic product-site devices such as gradient headline text, glass cards, feature-card grids, decorative blobs, and animation without a clear navigational or explanatory purpose.
Content and route contract
Public documentation routes are stable. Existing pages should keep their root
paths, for example src/content/docs/switch.md is /switch/. The homepage
renders worktrunk.md; /worktrunk/ remains available for compatibility and
has a canonical link to /.
Use root-relative links in canonical Markdown:
[hook templates](/hook/#template-variables)
The sync pipeline expands them to full https://worktrunk.dev/... URLs for
README and agent-skill copies. Do not add framework-specific link syntax.
stable-heading-ids.mjs preserves the site's established anchor scheme, and
test:site verifies every built internal page link and fragment.
Images and demos use root-relative paths into public/:
<figure class="demo">
<picture>
<source srcset="/assets/docs/dark/wt-switch.gif" media="(prefers-color-scheme: dark)">
<img src="/assets/docs/light/wt-switch.gif" alt="wt switch demo" width="1600" height="900">
</picture>
</figure>
Documentation sync taxonomy
cargo test --test integration test_docs_are_in_sync owns the complete sync
pipeline. It updates generated files and then fails so the changes are visible.
Run it again after reviewing those changes; the second run must pass.
There are three source categories:
- Command pages:
src/cli/mod.rsis primary forconfig,hook,list,merge,remove,step, andswitch. The sync test writes the generated region indocs/src/content/docs/{command}.md, then generates the matchingskills/worktrunk/reference/page. - Non-command pages: files such as
claude-code.md,extending.md,faq.md,llm-commits.md,shell-integration.md,tips-patterns.md, andworktrunk.mdare primary indocs/src/content/docs/. The sync test derives the skill copy. - Skill-only pages:
troubleshooting.mdis primary inskills/worktrunk/reference/and has no site page. When adding one, add alinguist-generated=falseentry to.gitattributes.
Never hand-edit a generated mirror.
Command-page generation
Each command page keeps YAML frontmatter outside one generated region:
---
title: "wt list"
description: "List worktrees and their status."
sidebar:
order: 11
---
<!-- ⚠️ AUTO-GENERATED from `wt list --help-page` — edit src/cli/mod.rs to update -->
[generated content]
<!-- END AUTO-GENERATED -->
The bare close marker is paired with the open marker using a non-greedy match.
test_no_nested_auto_generated_markers enforces the required invariant: an
auto-generated region may not contain another auto-generated region.
Each command has three pieces in src/cli/mod.rs:
| Piece | Source | Purpose |
|---|---|---|
| Definition | First /// line |
Short text for command lists |
| Subdefinition | Second /// line, when useful |
Context below the terminal help header |
| Full guide | after_long_help |
Mental model, workflow, examples, and reference links |
Terminal help displays the full guide after the options. The web page combines
the definition and subdefinition as its lead, followed by the full guide. Do
not repeat the lead at the start of after_long_help.
Link text must still make sense when terminal help removes the URL. Prefer
[`wt merge`](/merge/) or a descriptive phrase over a bare destination
heading.
Config examples between USER_CONFIG_START / USER_CONFIG_END and
PROJECT_CONFIG_START / PROJECT_CONFIG_END also generate commented TOML
files. Put prose before a code block instead of adding standalone TOML comment
lines that would become double-commented. End-of-line comments are fine.
After changing help text, refresh both generated pages and help snapshots:
cargo test --test integration test_docs_are_in_sync
cargo insta test --accept --test integration -- test_help
Snapshot examples
A command placeholder in src/cli/mod.rs expands from an integration snapshot:
<!-- wt list -->
```console
$ wt list
```
The mapping lives in tests/integration_tests/readme_sync.rs. The generated
site page uses the real command plus ANSI-stripped output in one console
fence. This keeps the Markdown readable on GitHub and lets the site renderer
add prompts and command-only copy behavior without changing the source. The
same sync pass writes src/generated/terminal-styles.json from the snapshot's
ANSI spans, so the website preserves the CLI's exact semantic colors and text
attributes while every portable Markdown surface remains plain text.
To update an example:
- Change the test setup that owns the snapshot.
- Run the focused integration test.
- Accept the snapshot with
cargo insta accept. - Run
test_docs_are_in_synctwice, reviewing the first run's edits.
Code-block convention
Use ordinary fenced Markdown. There are no template shortcodes or encoded command parameters.
- Use
bashfor commands a reader can copy as a complete recipe. - Use
consolewhen commands and captured output share a block. Prefix command lines with$; leave output unprefixed. - Use the actual data language (
toml,json,yaml, and so on) for files and structured output.
Example:
```console
$ wt switch --create feature-auth
✓ Created branch feature-auth from main
```
Starlight and Expressive Code create the frame and copy button. The Worktrunk
plugin highlights console commands as Bash, renders $ as a prompt, and
makes mixed blocks copy only their commands. Comment lines and blank recipe
separators remain copyable; captured output does not. Snapshot-backed output
gets its exact ANSI roles from the generated style manifest, while hand-written
output uses a conservative marker fallback. Generated Clap help fences carry
the wt-command-reference marker, which the plugin expands into semantic
command, option, value, and metadata roles. Committed Markdown must remain
useful without the plugin.
Web-only post-processing
post_process_for_html() in src/help.rs handles the few semantic differences
between terminal and site output: experimental badges, CI color labels, demo
figures, and a linked issue-report phrase. Do not route general Markdown
through it and do not add pre-rendered ANSI HTML.
Subdocument expansion
Use a subdocument placeholder to include a subcommand as a section of its parent page:
<!-- subdoc: create -->
The generator raises the subcommand heading levels and appends its command reference. The comment is invisible in terminal help.
Template examples
Every Worktrunk template expression shown in documentation needs a matching
test in tests/integration_tests/doc_templates.rs. This is what catches parser
and operator-precedence changes before they make an example misleading.
Demo assets
Large GIFs and rendered social cards live in the separate
max-sixty/worktrunk-assets repository. task fetch-assets copies published
assets to docs/public/assets/, which is gitignored. The publish workflow does
the same before building.
To regenerate and publish demos:
./docs/demos/build docs
./docs/demos/build social
task publish-assets
See docs/demos/CLAUDE.md for timing, terminal setup, validation, and recording
guidance.
Social-card SVG sources remain in docs/public/:
social-card.svgis the 1200 by 630 Open Graph source.github-social-card.svgis the 1280 by 640 repository-preview source.
Build their PNG outputs with task build-social-cards, then publish them with
task publish-assets. src/components/Head.astro points social metadata at
/assets/social/social-card.png.