# 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):
```bash
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:
1. Navigate to affected page(s)
2. Take a snapshot to verify rendered output
3. 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
1. **`box-sizing: border-box` on header** - Border is included in height, simplifying calculations
2. **`scrollbar-gutter: stable`** - Reserves scrollbar space to prevent layout shift on navigation
3. **IntersectionObserver intercept** - Ensures scroll-spy doesn't conflict with TOC styling
4. **Logo preload** - Prevents flash when navigating between pages
5. **WCAG AA colors** - `--wt-color-text-soft` is #78716a for 4.5:1 contrast
6. **iOS viewport polyfill** - Sets stable `--vh-full` variable for Firefox iOS/Chrome iOS (see `templates/base.html` for 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
## Command documentation
Command pages (e.g., `switch.md`, `merge.md`, `list.md`) are **generated from the CLI source code**. Each file has a **skeleton** (frontmatter) with a marker region that gets replaced by generated content.
### Generation mechanism
Each command page has this structure:
```markdown
+++
title = "wt list"
weight = 11
[extra]
group = "Commands"
+++
<!-- ⚠️ AUTO-GENERATED from `wt list --help-page` — edit cli.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 in each file. Everything between the START and END markers is generated by:
```bash
wt <command> --help-page
```
The generated content includes:
1. **Conceptual documentation** — from `after_long_help` in `src/cli.rs`
2. **Command Reference section** — the standard `--help` output
### Editing command docs
**To update command documentation, edit `src/cli.rs`**, not the markdown files directly.
- **Conceptual content** — Edit the `after_long_help` attribute on the command
- **Usage/options/examples** — Edit the clap attributes (`about`, `long_about`, doc comments on args)
After editing, run the sync test (which auto-updates out-of-sync pages):
```bash
cargo test --test integration test_docs_are_in_sync
```
### Command documentation structure
Each command has three documentation pieces in `src/cli.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:**
1. **Definition must be short** — It appears in command lists; keep it to a noun phrase or brief imperative.
2. **Subdefinition adds context** — Only include if it provides information the definition doesn't. If the definition is complete, omit the subdefinition.
3. **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.
4. **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.
**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 `cli.rs`, you write:
`````
<!-- wt list -->
```console
wt list
```
`````
This renders differently in each context:
- **Terminal help (`--help`)**: HTML comment skipped, code block shows as dimmed `wt 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:**
1. Edit test setup in `tests/integration_tests/list.rs` → `setup_readme_example_repo()`
2. Run tests to regenerate snapshots: `cargo test --test integration readme_example_list`
3. Accept snapshots: `cargo insta accept`
4. Sync docs: `cargo test --test integration test_docs_are_in_sync`
The examples in `cli.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/main.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` |
| `` `●` 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:
```markdown
<!-- subdoc: subcommand-name -->
```
In `cli.rs`, add the placeholder anywhere in the parent command's `after_long_help`:
```rust
after_long_help = r#"...main documentation...
<!-- subdoc: create -->
...more documentation..."#
```
This expands during `--help-page` generation to:
```markdown
## 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 `--help` output (HTML comment)
- Heading levels in the subcommand's `after_long_help` are increased by one (## → ###)
- The subcommand's help reference is formatted as a nested `### Command reference`
**Use cases:**
- `wt config create` — Shows the actual config file templates (via `include_str!`)
- `wt config state marker` — Shows per-key examples not in the parent
All AUTO-GENERATED markers use a consistent format with START and END tags:
```html
<!-- ⚠️ 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:**
```bash
task fetch-assets # Download published assets
```
**To regenerate demos** (required after CLI output changes):
```bash
./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 in `base.html`
- `github-social-card.svg` (1280×640) — GitHub repository preview, uploaded manually in repo Settings → Social preview
**To regenerate** (after changing tagline, logo, or layout):
```bash
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:
```html
<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.