Requested in [review feedback on
#3770](https://github.com/max-sixty/worktrunk/pull/3770#discussion_r3740664250):
> this is way too verbose; create another PR to add guidance to have the
share of the docs proportional to the share of the feature.
>
> in this case it's a tiny share of the feature and probably have zero
docs; it's implicit
The existing content principles in `docs/CLAUDE.md` govern what a piece
of help text says and where it starts, but nothing says how *much* a
behavior earns. That gap is what produced the paragraph the review was
reading: a narrow refusal in `wt remove` got a full paragraph on the
command's help page, ahead of behavior every user of the command meets.
New principle 5 makes the sizing rule explicit, including the zero case
— an edge case whose own error message states it at the moment it
matters is left implicit.
The paragraph that prompted this is already gone from #3770 (30ba949).
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
17 KiB
Docs Site
This is the Zola-based documentation site for Worktrunk, published at worktrunk.dev.
Development workflow
The docs dev server starts automatically via the post-start hook. Find the port with wt list statusline.
For static builds with local URLs (e.g., testing with a simple HTTP server):
zola build --base-url "http://127.0.0.1:PORT"
Verifying changes
Text-only changes (prose edits, content rewrites): Run pre-commit and provide the dev server link. Playwright verification is not required.
Visual changes (CSS, layout, templates, responsive breakpoints): Use Playwright MCP to verify before returning. Visual bugs often hide in CSS specificity, template inheritance, and responsive behavior.
Playwright workflow for visual changes:
- Navigate to affected page(s)
- Take a snapshot to verify rendered output
- Iterate if the result doesn't match expectations
Common visual issues to check:
- Text positioning and visibility
- Spacing and alignment
- Regressions on nearby elements
- Responsive behavior (use
browser_resize)
Always include the dev server link when returning after doc changes:
View changes: http://127.0.0.1:<port>
Theme architecture
The docs use a standalone "warm workbench" theme. Key files:
| File | Purpose |
|---|---|
templates/_variables.html |
CSS custom properties (colors, layout, typography) |
sass/custom.scss |
All styling, organized by section |
templates/base.html |
Head overrides, iOS viewport polyfill |
templates/index.html |
Homepage hero and animations |
templates/page.html |
Doc page TOC rendering |
Layout system
The sticky header and TOC use definitional CSS variables so positions are always in sync:
--wt-header-height: 60px (includes border via box-sizing: border-box)
--wt-main-padding-top: 40px
TOC sticks at: calc(header + padding) = 100px
Anchor scroll-margin: same calculation
When either variable changes (including via media queries), all dependent values update automatically. This prevents the TOC from "jumping" when transitioning to sticky mode.
Key technical decisions
box-sizing: border-boxon header - Border is included in height, simplifying calculationsscrollbar-gutter: stable- Reserves scrollbar space to prevent layout shift on navigation- IntersectionObserver intercept - Ensures scroll-spy doesn't conflict with TOC styling
- Logo preload - Prevents flash when navigating between pages
- WCAG AA colors -
--wt-color-text-softis #78716a for 4.5:1 contrast - iOS viewport polyfill - Sets stable
--vh-fullvariable for Firefox iOS/Chrome iOS (seetemplates/base.htmlfor details on the jank issue and what we tried)
Responsive breakpoints
Variables are overridden in media queries to maintain definitional correctness:
- ≤1024px:
--wt-main-padding-top: 30px - ≤768px:
--wt-header-height: 50px,--wt-main-padding-top: 20px, TOC hidden
Extending the theme
When adding new positioned elements:
- Use the layout variables rather than hardcoding pixel values
- Test anchor navigation to verify no visual jumps
- Check both with and without page scroll
Doc sync taxonomy
Three categories, kept in sync by test_docs_are_in_sync (run it after any doc change; it auto-updates out-of-sync pages):
- Command pages (config, hook, list, merge, remove, step, switch):
dev/*.example.toml(viainclude_str!) →src/cli/mod.rs(PRIMARY SOURCE) →docs/content/{command}.md→skills/worktrunk/reference/{command}.md. Withinsrc/cli/mod.rs,after_long_helpcarries the conceptual prose; clap attributes (about,long_about, doc comments on args) carry usage, options, and examples. Mechanism details: "Command page generation" below. - Non-command docs (claude-code, code-signing, extending, faq, llm-commits, tips-patterns, worktrunk):
docs/content/*.mdis PRIMARY; edit it directly, the skill reference auto-syncs. - Skill-only files (shell-integration.md, troubleshooting.md): edit
skills/worktrunk/reference/directly, no docs equivalent. When adding one, also add alinguist-generated=falseline to.gitattributes— the broadskills/worktrunk/reference/*.md linguist-generated=truerule otherwise marks it generated, collapsing real edits in GitHub PR diffs.
Never hand-edit a generated mirror.
Help text authoring
Help text renders in three contexts; check all three when editing:
- Terminal (
wt step X --help):aboutandsubtitleat the top,after_long_helpbelow the Options block — separated by distance. - Web docs:
combine_command_docs()concatenatesabout+ optionalsubtitle+after_long_helpas consecutive paragraphs. - Skill reference: mirrors web docs.
Because web docs concatenate everything, the after_long_help opener must not restate about/subtitle — start with new information (see "Command documentation structure" below for opener patterns). Link text must stand alone when the URL is stripped (terminal help drops the URL, keeping only the text): use [`wt foo`](...) for commands (the backticks signal a --help lookup) or a descriptive phrase for doc sections; avoid bare labels that match the destination's heading.
After editing after_long_help, also refresh the help snapshots: cargo insta test --accept --test integration -- test_help.
Config doc TOML blocks
Config docs (USER_CONFIG_START / PROJECT_CONFIG_START sections in src/cli/mod.rs) generate dev/*.example.toml files where every line is #-commented, so TOML comments inside code blocks become double-commented (# # comment). Use plain-text descriptions ending with a colon before each code block; inline end-of-line comments (key = "value" # explanation) are fine.
Command page generation
Each command page in docs/content/ is a frontmatter skeleton with an AUTO-GENERATED region populated from wt <command> --help-page:
+++
title = "wt list"
weight = 11
[extra]
group = "Commands"
+++
<!-- ⚠️ AUTO-GENERATED from `wt list --help-page` — edit src/cli/mod.rs to update -->
[generated content here]
<!-- END AUTO-GENERATED -->
The bare close marker is paired with the open via non-greedy regex matching. test_no_nested_auto_generated_markers enforces that no AUTO-GENERATED open ever appears inside a region in docs/content/*.md, which is the precondition for non-greedy pairing to be safe.
The frontmatter (title, weight, group) is preserved; everything between the markers is regenerated as after_long_help (conceptual prose) followed by a Command Reference section from --help.
Command documentation structure
Each command has three documentation pieces in src/cli/mod.rs:
| Piece | Source | Purpose |
|---|---|---|
| Definition | First /// line |
Short identifier for command lists |
| Subdefinition | Second /// line (optional) |
Adds context when relevant |
| after_long_help | #[command(after_long_help = "...")] |
Full documentation |
Where each appears:
| Context | What's shown |
|---|---|
Command list (wt --help) |
Definition only |
Terminal -h |
Definition in header |
Terminal --help |
Definition (header) + Subdefinition (under header) + after_long_help (after options) |
| Web docs | "Definition. Subdefinition." as lead paragraph, then after_long_help |
Content principles:
-
Definition must be short — It appears in command lists; keep it to a noun phrase or brief imperative.
-
Subdefinition adds context — Only include if it provides information the definition doesn't. If the definition is complete, omit the subdefinition.
-
after_long_help must not repeat — Start with NEW information that expands on or provides context for the definition. Each piece should add information the previous pieces didn't provide.
-
after_long_help should mostly stand alone — The opener should give context or purpose, not just continue with details that only make sense after reading the definition. Avoid non-sequiturs.
-
Give a behavior the share of the docs that matches its share of the feature — every paragraph is read by everyone who runs
--help, so length is a claim on that attention. A guard or edge case that is a tiny share of the command usually gets zero words: its own error message states it at the moment it matters, and the help page leaves it implicit. Reserve prose for what most users of the command meet.
Good patterns for after_long_help openers:
- Explain the mental model: "Worktrees are addressed by branch name..."
- Contrast with similar tools: "Unlike
git merge, this merges current into target..." - State the use case: "Use when you're done with a feature branch."
- Explain what something is: "Shell commands that run at key points..."
- Describe relationship to other commands: "The building blocks of
wt merge: commit, squash, rebase, push."
Patterns to avoid:
- Repeating the definition with different words
- Starting with details that assume context ("The table shows..." when there's no prior mention of a table)
- Leading with configuration defaults before establishing what the command does
- Non-sequiturs that jump to side-effects without context
Example output expansion (wt list)
The wt list examples use HTML comments + code blocks that expand to full snapshot output. In src/cli/mod.rs, you write:
<!-- wt list -->
```console
wt list
```
This renders differently in each context:
- Terminal help (
--help): HTML comment skipped, code block shows as dimmedwt list - Web docs (
--help-page): Both are replaced with the standard template format:
<!-- ⚠️ AUTO-GENERATED from tests/snapshots/<snapshot_file> — edit source to update -->
{% terminal() %}
<span class="prompt">$</span> <span class="cmd">wt list</span>
<output...>
{% end %}
<!-- END AUTO-GENERATED -->
The mapping (in tests/integration_tests/readme_sync.rs):
| Placeholder | Snapshot File |
|---|---|
<!-- wt list --> |
readme_example_list.snap |
<!-- wt list --full --> |
readme_example_list_full.snap |
<!-- wt list --branches --full --> |
readme_example_list_branches.snap |
To update example output:
- Edit test setup in
tests/integration_tests/list.rs→setup_readme_example_repo() - Run tests to regenerate snapshots:
cargo test --test integration readme_example_list - Accept snapshots:
cargo insta accept - Sync docs:
cargo test --test integration test_docs_are_in_sync
The examples in src/cli/mod.rs are just command stubs — the actual output comes from snapshots generated by integration tests. This ensures docs always match real CLI behavior.
Code block convention
All shell commands use $ prefix in ```console blocks. convert_dollar_console_to_terminal() (src/docs.rs, shared library function) converts them to terminal shortcodes. The sync test also runs this on hand-written docs.
| Detected pattern | Web output | Highlighting |
|---|---|---|
$ commands |
cmd parameter with ||| delimiter |
Full Syntect |
No $ |
console → bash conversion |
Syntect (no $ prompt) |
All $ commands go through the cmd parameter path for consistent Syntect highlighting. Multiple commands are joined by |||; the template splits and highlights each individually. Commands are wrapped in <span class="cmd"> (CSS ::before adds $ ). Comment lines (#) are highlighted but not wrapped (no prompt).
Placeholders in cmd values: Tera (Zola's template engine) would interpret {{ }} in the cmd parameter as template expressions, and " would close the parameter string. Since Tera has no backslash-escape mechanism for string literals, these characters are replaced with text placeholders (__WT_OPEN__, __WT_CLOSE__, __WT_QUOT__) in the Rust conversion function. The terminal shortcode template replaces them back before Syntect highlighting. The sync test also strips them when generating skill reference files.
Hand-written docs can use either console fences with $ (auto-converted by the sync test) or shortcodes directly.
CLI and web compatibility
Content in after_long_help must work in both the terminal (--help) and the web docs:
- Tables — Work in both. Prefer tables over bullet lists for structured data.
- Markdown links — Work in both (
[text](/path/)) - Code blocks — Work in both
- Raw HTML — Avoid. Renders as raw text in terminal help.
Post-processing for web docs
The --help-page generator in src/help.rs applies post-processing to transform CLI-friendly content into web-friendly HTML:
| CLI Source | Web Output |
|---|---|
```console with $ |
terminal shortcode with cmd parameter |
```console (no $ ) |
```bash |
`●` green |
<span style='color:#0a0'>●</span> green |
`●` blue |
<span style='color:#00a'>●</span> blue |
`●` red |
<span style='color:#a00'>●</span> red |
`●` magenta |
<span style='color:#a0a'>●</span> magenta |
`●` cyan |
<span style='color:#0aa'>●</span> cyan |
`●` yellow |
<span style='color:#a60'>●</span> yellow |
`●` gray |
<span style='color:#888'>●</span> gray |
[experimental] |
<span class="badge-experimental"></span> (text via CSS ::after) |
To add web-only styling for new content, edit post_process_for_html() in src/help.rs — not the markdown files.
Similarly, md_help::colorize_status_symbols() applies ANSI colors for terminal --help output.
Subdoc expansion
Include subcommand documentation as H2 sections within a parent command's docs page using subdoc placeholders:
<!-- subdoc: subcommand-name -->
In src/cli/mod.rs, add the placeholder anywhere in the parent command's after_long_help:
after_long_help = r#"...main documentation...
<!-- subdoc: create -->
...more documentation..."#
This expands during --help-page generation to:
## wt config create
### User config
[subcommand's after_long_help content with heading levels increased]
---
### Command reference
[subcommand's usage and options]
How it works:
- The placeholder is invisible in terminal
--helpoutput (HTML comment) - Heading levels in the subcommand's
after_long_helpare increased by one (## → ###) - The subcommand's help reference is formatted as a nested
### Command reference
Use cases:
wt list statusline— Surfaces output formats that would otherwise be terminal-onlywt config state marker— Shows per-key examples not in the parent
All AUTO-GENERATED markers use a consistent format with START and END tags:
<!-- ⚠️ AUTO-GENERATED from <source> — edit <file> to update -->
[content]
<!-- END AUTO-GENERATED -->
All AUTO-GENERATED regions use the bare close — sync test
test_no_nested_auto_generated_markers enforces that no region ever nests
inside another in docs/content/*.md, which is the precondition for the
outer-region regex to safely pair the open with the close via non-greedy
matching. If you ever need nested regions, restore a disambiguating close
form on the outer marker rather than relying on the regex.
Template examples in documentation
All template examples must have corresponding tests in tests/integration_tests/doc_templates.rs. This catches issues like operator precedence bugs (PR #373).
When adding template examples, add a test that verifies the template produces expected output. See existing tests for patterns.
Demo GIF workflow
Demo GIFs (~2MB each) are stored in a separate worktrunk-assets repo to avoid bloating git history. Both build and fetch output to docs/static/assets/ (gitignored), so local builds override fetched assets.
For local development:
task fetch-assets # Download published assets
To regenerate demos (required after CLI output changes):
./docs/demos/build docs # Doc site demos (light + dark)
./docs/demos/build social # Social media demos (light only)
task publish-assets # Publish to assets repo
Deploy runs fetch-assets before building.
For detailed demo development guidelines (timing, debugging, environment setup), see docs/demos/CLAUDE.md.
Social card workflow
Social cards follow the same assets pattern as demos.
Source files:
social-card.svg(1200×630) — Open Graph/Twitter link previews, referenced inbase.htmlgithub-social-card.svg(1280×640) — GitHub repository preview, uploaded manually in repo Settings → Social preview
To regenerate (after changing tagline, logo, or layout):
task build-social-cards # SVG → PNG (downloads fonts if needed)
task publish-assets # Publish to assets repo
The build script automatically downloads Inter and Plus Jakarta Sans fonts from GitHub if not installed locally. Requires rsvg-convert (from librsvg).
Referenced in: docs/templates/base.html (og:image, twitter:image meta tags)
Light/dark theme variants
In markdown, use <picture> with media queries:
<figure class="demo">
<picture>
<source srcset="/assets/docs/dark/wt-switch-picker.gif" media="(prefers-color-scheme: dark)">
<img src="/assets/docs/light/wt-switch-picker.gif" alt="wt switch picker demo" width="1600" height="800">
</picture>
</figure>
The browser automatically shows the appropriate variant based on system preference.