Files
Maximilian Roos 5dec423110 docs: leave trailing shell comments out of copied commands (#4091)
Copy buttons copied shell commands with their trailing `#` comments,
e.g. `wt hook pre-merge user: # Run all user hooks`. In zsh without
`interactivecomments`, which is the default, a pasted trailing comment
isn't a comment: the command runs as `wt hook pre-merge user: '#' Run
all user hooks`.

`withoutTrailingShellComments` in
`docs/src/plugins/worktrunk-terminal.mjs` applies the shell's rule, that
a `#` beginning an unquoted word starts a comment, to what shell copy
buttons copy: each per-line button in multi-command `console` blocks,
the command lines of single-command `console` blocks, and whole `bash`
and `sh` blocks. A line that is only a comment stays, as
`docs/CLAUDE.md` already specifies for console blocks. 56 of the site's
339 copy payloads change (47 per-line buttons and 9 `bash` blocks).
TOML, PowerShell and other payloads are unchanged, and a quoted `#`
survives.

Tests: unit tests for the scanner (quotes, escapes, `$#`, operators,
quotes spanning lines) and for a single-command `console` block with a
trailing comment, plus a built-site test that no `bash` or `sh` payload
carries a trailing comment, which fails on the build before this change.

> _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:57:35 -07:00

295 lines
11 KiB
Markdown

# 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:
```bash
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:
```bash
npm --prefix docs run check
npm --prefix docs test
npm --prefix docs run build
npm --prefix docs run test:site
```
`npm run build` writes `docs/dist/` and builds the Pagefind search index. Both
`build` and `dev` pass `--force`, which clears Astro's content cache before
rendering. Cached pages carry the URL of the Expressive Code stylesheet, whose
hash comes from the code-rendering configuration and plugins, and Astro's cache
key covers neither: a stale page links a stylesheet the server no longer serves,
and every code block renders unstyled.
The cache is cleared only when the server starts. When `astro.config.mjs`
changes under a running dev server, through an edit or a merge, Astro restarts
the server in place but keeps rendering content with the renderer it started
with, and pages break the same way. Restart it with `wt hook post-start docs`;
the new `astro dev --force` replaces the running server.
### 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:
```text
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:
```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` defines the heading-anchor scheme, including the ids
scoped under each subcommand section, and `test:site` verifies every built
internal page link and fragment.
Images and demos use root-relative paths into `public/`:
```html
<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:
1. **Command pages**: `src/cli/mod.rs` is primary for `config`, `hook`, `list`,
`merge`, `remove`, `step`, and `switch`. The sync test writes the generated
region in `docs/src/content/docs/{command}.md`, then generates the matching
`skills/worktrunk/reference/` page.
2. **Non-command pages**: files such as `claude-code.md`, `extending.md`,
`faq.md`, `llm-commits.md`, `shell-integration.md`, `tips-patterns.md`, and
`worktrunk.md` are primary in `docs/src/content/docs/`. The sync test derives
the skill copy.
3. **Skill-only pages**: `troubleshooting.md` is primary in
`skills/worktrunk/reference/` and has no site page. When adding one, add a
`linguist-generated=false` entry to `.gitattributes`.
Never hand-edit a generated mirror.
### Command-page generation
Each command page keeps YAML frontmatter outside one generated region:
```markdown
---
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:
```bash
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:
````markdown
<!-- 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:
1. Change the test setup that owns the snapshot.
2. Run the focused integration test.
3. Accept the snapshot with `cargo insta accept`.
4. Run `test_docs_are_in_sync` twice, reviewing the first run's edits.
### Code-block convention
Use ordinary fenced Markdown. There are no template shortcodes or encoded
command parameters.
- Use `bash` for commands a reader can copy as a complete recipe.
- Use `console` when 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:
````markdown
```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 blocks copy their commands rather than their output. A block with one
command copies it with its comment lines and blank recipe separators; a block
with several gives each command its own copy button. Copied shell commands
leave out trailing `#` comments, which zsh passes as arguments unless
`interactivecomments` is set. 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:
```markdown
<!-- 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:
```bash
./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.svg` is the 1200 by 630 Open Graph source.
- `github-social-card.svg` is 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`.