Repository navigation
docs: rebuild the site with Astro and Starlight - #3866
Conversation
361d005 to
9e2e9cd
Compare
# Conflicts: # docs/src/content/docs/switch.md # src/cli/mod.rs
worktrunk-bot
left a comment
There was a problem hiding this comment.
Reviewed the merged tree at 282fbf8c. The shape is good — the Rust side is a net −620 lines, and dropping the ANSI→HTML round trip fixes a real defect: the skill reference files were shipping raw <span class=g> markup to agents (skills/worktrunk/reference/llm-commits.md was almost entirely HTML), and they're now clean text. I built the site locally and walked the route/asset/link contract; the .md companions and the .well-known/agent-skills/worktrunk symlink both dereference correctly into dist/, so those published URLs survive the move.
Two things I'd fix before merge, one inline each.
link-check is red on this head, and it's this PR's doing — 164 errors in run 32554191286, all of the shape file:///…/docs/public/config#wt-config-state-vars | File not found. The old config excluded "/@/" because Zola's internal-link syntax was validated by zola check rather than lychee; the replacement root-relative form (/config/#anchor) has no equivalent exclusion, so lychee resolves every one of them under root_dir and 404s. The coverage isn't actually lost — npm run test:site's "built pages have unique IDs and valid internal page links" checks every internal page link and fragment against the built site, which is stricter than what lychee could do on the sources.
Prose in captured output picks up state colors. semanticOutputSegments matches a bare + ([+↑⇡]\d*, zero digits allowed — asymmetric with the - branch's \d+), so cargo's own output renders a green bold +; [!?](?=\s|$) has no left boundary, so the approval prompt's ? renders amber. Both are visible in the current build:
$ grep -o 'unoptimized <span class="wt-positive">+</span>' dist/hook/index.html | head -1
unoptimized <span class="wt-positive">+</span>
$ grep -o 'remember<span class="wt-warning">?</span>' dist/faq/index.html | head -1
remember<span class="wt-warning">?</span>Non-blocking:
sync_llms_txtnow hardcodes the site title, description, and base URL thatdocs/config.toml'stitle/extra.site_descriptionused to single-source for both the site andllms.txt. The description string is byte-identical todocs/src/content/docs/worktrunk.md's frontmatterdescription:and nothing enforces that — edit the homepage description andllms.txtkeeps the old one silently. The loop already parses that page's frontmatter (it computesfm.description, thencontinues onslug == "worktrunk"), so lifting the value from there instead of a literal would close it. Related:Cargo.toml's affected rule now listsdocs/astro.config.mjsunder a comment saying the test "reads each of these at runtime (viafs::read_to_string)", butreadme_sync.rsno longer reads it — harmless, just no longer true. AndHead.astrocarries a third copy of a site description (const description = 'Git worktree management for parallel AI agent workflows.'), distinct from the two above.- With
PageMode::color()now returningColorChoice::Neverfor both variants andtransform_raw()an identity function, both are constant inself— the enum is still doing real work inprocess_body/emit_header/experimental_marker, but these two look like they want to be plain values rather than methods. docs/scripts/parity.mjsdefaults--referencetohttps://worktrunk.dev/, which is exactly what this PR replaces. Nothing references it (no CI job, anddocs/CLAUDE.md's architecture table doesn't listscripts/), so after merge it compares the site against itself. Worth deleting with the migration unless it's meant to survive.- The built-site suite pins some counts that unrelated content edits will move —
assert.equal(…configToc…length, 32)andassert.equal(outputOnlyBlocks, 1). Both carry good messages, so it's a judgment call, but adding a heading toconfig.mdwill fail a test about tables of contents.
I checked the deletion surface per this repo's review guidance: remove.md, faq.md, remove.rs, and the feat_custom_remove.json entry are all touched, but only as path strings and ANSI-stripped help rendering — no destructive command is added or widened, so no hold on that account. Holding the approval purely on the red link-check.
worktrunk-bot
left a comment
There was a problem hiding this comment.
All three threads from the last review are closed. I re-verified each at this head rather than taking the commit messages for it:
- Built the site and ran the pinned lychee (0.24.2,
--offline) over the same tracked.md/.txtset the hook selects: nofile://errors from the PR's files, against 164 on282fbf8c. (The two hits my run does show are onCLAUDE.md, which this review checkout reverts to the base version; the PR head's copy already carries the updateddocs/src/content/docs/faq.mdpaths.)root_dir = docs/distresolves directory routes —/config/#anchor→dist/config/index.html— and a planted/nonexistent-page/still errors, so the links are genuinely checked rather than skipped. Better than the exclusion I'd suggested. - Rebuilt after the regex change: the
wt-positive+census goes 181 → 178 andwt-warning?6 → 5, with both offenders gone (unoptimized + debuginfoon/hook/,Allow and remember?on/faq/) and every diffstat and status-column marker still colored.npm test9/9. readme_sync16/16 includingtest_docs_are_in_sync;worktrunk.md's frontmatter title and description are byte-identical to the literals they replaced, sollms.txtis unchanged by the switch to single-sourcing.cargo clippy --all-targets --all-features -- -D warningsis clean after thePageModetrim, andmodestill does real work in both call sites.
One non-blocking thing on the new link-check steps: they're a verbatim copy of publish-docs.yaml's four (same Node 24, same worktrunk-assets clone and cp target), and nothing keeps the two in step. If one side moves, link-check starts validating links against a site that isn't the one that ships, with nothing to catch the drift. The repo already has .github/actions/{test,tend}-setup, so a docs-build composite would make the two builds identical by construction.
…#3867) The Codex setup section in the LLM-commits docs describes the pinned model as "the fast mini model", but the command it sits under pins `gpt-5.6-luna` — which is not a `-mini` model. The prose was written in #837 when the pin was a mini variant and was left behind when #3430 bumped `gpt-5.4-mini` → `gpt-5.6-luna`. This rewords the sentence to describe what the command actually pins, following #3430's own framing of `gpt-5.6-luna` as "the fast/low-cost variant of the current recommended (5.6) family". `docs/src/content/docs/llm-commits.md` is the primary (non-command doc, per the sync taxonomy in `docs/CLAUDE.md`); the two mirrors under `skills/` and `plugins/` were regenerated by `cargo test --test integration test_docs_are_in_sync`, which now passes. The branch was rebased onto `main` after #3866 relocated the docs tree, so the edit lands at the post-#3866 path rather than the old `docs/content/` one. No regression test: the change is a single prose sentence with no behavior behind it. The sync test already pins the mirrors to the primary, which is the only mechanical invariant here. <details><summary>Nightly sweep context</summary> Found during the rolling survey (bucket 23/28), reviewing `skills/worktrunk/reference/llm-commits.md`. Provenance: ``` $ git log --oneline -1 -S 'Uses the fast mini model' -- docs/content/llm-commits.md cbe65a6 docs: add Codex config and optimize Claude Code for LLM commits (#837) $ git log --oneline -1 -S 'gpt-5.6-luna' -- docs/content/llm-commits.md c532d2d docs(llm-commits): bump Codex commit-generation model to gpt-5.6-luna (#3430) ``` A repo-wide `grep -i mini` turned up no other stale reference to the old pin — the model string itself is consistent across `src/cli/mod.rs`, `dev/config.example.toml`, `Taskfile.yaml`, and `src/output/commit_generation.rs`; only this one sentence describes it. </details> Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
…ght ANSI values The docstring said no value is a verbatim copy and to expect small deltas. Neither holds: dark black/red/cyan are byte-identical to --wt-terminal-gutter, --wt-terminal-red, and --wt-terminal-cyan, while the light ANSI hues sit 21-64 per-channel units from theirs because they still carry the light palette from the pre-Starlight _variables.html (deleted in #3866). Split the docstring's claim by group so it is true of every entry, and give each light ANSI entry the site's current hex. The two parentheticals that said 'desaturated/muted from' lose that wording: #357a59 and #3d7f7f were derived from the old --green #1b7f4b and --cyan #0a8080, not from today's values. Whether to resync the light values themselves is left open - that would change recorded GIFs.
The dark green, yellow, blue, and magenta values are byte-identical to the dark --green/--yellow/--blue/--magenta in the docs/templates/_variables.html #3866 deleted, and --wt-terminal-* did not exist until #3936 authored today's hexes. They are the same stale palette the light block carries, not values derived from the current properties, so they take the same annotation. The docstring now states the rule the file follows: an ANSI hue quotes the site's current hex where the site moved and the theme did not.
…and palette file (#4068) Two references in `docs/demos/CLAUDE.md` point at files and directories that do not exist, so anyone following them to change a demo's colors or find a recorded GIF lands nowhere. - The **Light/dark theme variants** section lists the outputs as `docs/light/wt-core.gif` and friends. The build writes them to `docs/public/assets/<target>/<theme>/<name>.gif` (`OUT_DIR` in `docs/demos/build`), which is exactly what the directory-structure block at the top of the same file already says — the two sections contradicted each other. - The same section points at `_variables.html` as the palette source. That file was removed in #3866 when the site moved to Astro and Starlight; the `--wt-*` custom properties now live in `docs/src/styles/custom.css`, which is also what `docs/demos/shared/themes.py` names in its own docstring. Documentation-only, so there is no regression test to add; the claims are verified against `docs/demos/build` and against the file's own structure block. Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
The recorder's VHS theme still carried the Zola site's ANSI palette, which #3866 deleted and #3936 replaced with --wt-terminal-*. Zellij, the starship prompt, and Claude Code were configured light for both themes. Dark GIFs drew Worktrunk's gutter as a near-white bar under a light Zellij tab bar, and the light GIFs' yellow had 2.65:1 contrast against the page. themes.py now reads the hex --wt-* properties from custom.css when the build runs. The VHS theme maps ANSI colors the way the site renders snapshot output, and DemoEnv carries the recording's theme so Zellij, starship, and Claude Code follow it. This replaces the per-value "pre-Starlight" annotations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQPDxs2csvGFGBr2j4MtrA
The demo recorder kept its own copies of the docs site's colors, and they had drifted from `docs/src/styles/custom.css`: - The VHS terminal's ANSI colors were still the Zola site's palette, which #3866 deleted and #3936 replaced with `--wt-terminal-*`. Light yellow had 2.65:1 contrast against the page background; the site's browser test requires 4.5:1 for its own dim terminal text. - `brightWhite`, the background of Worktrunk's gutter, used the ink color, so dark GIFs drew the gutter as a near-white bar. - Zellij, the starship prompt, and Claude Code were configured for light in both themes, so dark GIFs showed a light Zellij tab bar and Claude Code's light theme. `docs/demos/shared/themes.py` now reads the hex `--wt-*` properties from `custom.css` when the build runs. The VHS theme maps ANSI colors the way the site renders snapshot output (`terminal_color_class` and `terminal_background_class` in `tests/integration_tests/readme_sync.rs`). `DemoEnv` carries the recording's theme, so Zellij, the starship palette, and Claude Code follow it. This replaces the `(pre-Starlight; site is #…)` comments the PR first added. Recording from inside a Claude Code session also leaked into the Claude demos. The demo's Claude Code inherited that session's `CLAUDE*` variables and warned that transcript saving was off, and it started Remote Control, which printed a live claude.ai session URL into the GIF. The recorder now drops inherited `CLAUDE*` variables except `CLAUDE_CODE_OAUTH_TOKEN`, and the demo's `settings.json` sets `remoteControlAtStartup` to false. The GIFs are re-recorded locally (`./docs/demos/build docs` and `./docs/demos/build social`) and published in max-sixty/worktrunk-assets@bb3bfba. ## Verification - `pytest docs/demos/tests`: 4 passed. - OCR of every 20th frame of the Claude demos (354 frames) finds no `remote-control`, `claude.ai/code`, `rc active`, or `Transcript saving`. The same check matches on frames from the leaking recording. - Frames checked by eye in both themes for the ANSI colors, the gutter, and the Zellij chrome. - TUI validation passes for all nine Claude demo GIFs. The `wt-switch` and `wt-statusline` checkpoints named absolute frames past the end of these faster recordings (274–318 frames, against 344–465 in the previously published GIFs), so a negative checkpoint frame now counts back from the last frame; the omnibus checkpoint on its closing `wt list` uses the same anchoring. The previously published GIFs pass the new checkpoints too. Those checkpoints also forbid `claude.ai/code` and `Transcript saving`, and a GIF made from a leaking frame fails on them. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01TQPDxs2csvGFGBr2j4MtrA > _This was written by Claude Code on behalf of max-sixty_ --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com> Co-authored-by: Maximilian Roos <m@maxroos.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
worktrunk 0.75.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>## Release Notes ### Improved - **The `wt switch` picker opens on one unified diff**: local rows combine committed, staged, unstaged, and untracked changes. Tab skips empty subsidiary views, while `Alt-1` through `Alt-8` retain direct access. [Docs](https://worktrunk.dev/switch/#interactive-picker) ([#3865](max-sixty/worktrunk#3865)) - **Git 2.43 is now the minimum supported version**: older Git exits with an upgrade message before Git-dependent commands run; `wt config shell` remains available so shell startup continues. (Breaking.) ([#3895](max-sixty/worktrunk#3895)) ### Fixed - **`wt list` no longer grows `.git/objects` during advisory conflict checks**: untracked content stays visible but is excluded from synthetic trees, and all probe-only objects use temporary storage. ([#3906](max-sixty/worktrunk#3906), fixes [#3883](max-sixty/worktrunk#3883), thanks @srobroek for reporting and the original fix) - **`HEAD±` counts untracked files without inflating moves**: `wt list`, the picker, and statusline include untracked lines. Tracked deletions paired with untracked destinations count as renames, so pure moves are line-neutral and edited moves show only their edits. `HEAD±` now always detects renames, regardless of `diff.renames`. ([#3925](max-sixty/worktrunk#3925)) - **Timed child processes no longer abort `wt` in restricted sandboxes**: `wt switch --create`, picker pagers, and other bounded commands survive denied signal-handler wakes. TERM→KILL cleanup also returns promptly once the process group is gone. ([#3857](max-sixty/worktrunk#3857), [#3887](max-sixty/worktrunk#3887), fixes [#3856](max-sixty/worktrunk#3856), thanks @tomascamargo for reporting) - **JSON list output ignores display-column gates**: `[list] columns` no longer makes `wt list --format json` contact a forge or generate summaries. CI requires `--full`; summaries also require `[list] summary = true` and a configured generator. (Breaking: schema 1 loses config-driven `ci` and `summary` fields.) ([#3812](max-sixty/worktrunk#3812), thanks @emeren for the request) - **Long Windows paths compare consistently**: paths beyond 260 characters could retain a `\\?\` prefix and appear to be on another drive. `copy-ignored` then refused them, while switch, remove, merge, and relocate landed at the worktree root instead of the original subdirectory. ([#3899](max-sixty/worktrunk#3899), fixes [#3898](max-sixty/worktrunk#3898), thanks @Persedes for reporting and verifying the fix) - **Shell configuration rechecks before it writes**: overlapping installs lock and reread rc files; Fish completion installs preserve files created after preview; uninstall applies only previewed rc removals and rejects changed Worktrunk-owned files. ([#3853](max-sixty/worktrunk#3853), [#3924](max-sixty/worktrunk#3924)) - **`-vv` profiles exclude their own collector commands**: command counts and cache summaries no longer include duplicate-looking work performed only to assemble the diagnostic report; raw traces still retain it. ([#3900](max-sixty/worktrunk#3900)) - **Fenced HTML comments survive picker Markdown rendering**: PR descriptions and comments now preserve fenced `<!-- … -->` lines; fenced `<!-- wt list … -->` markers also no longer affect the following block. ([#3908](max-sixty/worktrunk#3908)) ### Documentation - **Agent CLIs without a plugin can publish activity markers**: the integration guide now specifies the session-start, turn-end, and session-end calls, working-directory requirement, error guard, and cleanup contract. [Docs](https://worktrunk.dev/claude-code/#agent-clis-without-a-plugin) ([#3848](max-sixty/worktrunk#3848), thanks @AsafMah for requesting generic-agent guidance and @ortonomy for the related Pi use case) - **Agent guidance explains worktree selection**: commands that name a branch already select its worktree; `-C` changes repository context and is needed only for commands without a worktree selector or callers outside the repository. ([#3890](max-sixty/worktrunk#3890)) - **The `wt up` recipe safely updates dirty worktrees**: it fetches all remotes, fast-forwards dirty branches without autostash, rebases clean branches, and continues past an ordinary refusal or a failed remote. [Docs](https://worktrunk.dev/extending/#recipe-rebase-every-worktree-onto-its-upstream) ([#3882](max-sixty/worktrunk#3882)) - **The docs site has a new responsive design**: rebuilt on Astro and Starlight while preserving public routes, anchors, and crawler URLs; generated reference pages remain synchronized. ([#3866](max-sixty/worktrunk#3866)) ### Internal - **Library API rework** (Breaking library API): `BranchDiffSpec` gained `working_base`, while remote-URL, shell-path, branch-push, approval, temporary-index, and repository helpers were removed. ([#3833](max-sixty/worktrunk#3833), [#3853](max-sixty/worktrunk#3853), [#3865](max-sixty/worktrunk#3865), [#3866](max-sixty/worktrunk#3866), [#3875](max-sixty/worktrunk#3875)) - **Codex loads repository maintainer skills**: `.agents/skills` now exposes the canonical `.claude/skills` tree; checkouts without symlink support keep the existing limitation. ([#3903](max-sixty/worktrunk#3903)) ## Install worktrunk 0.75.0 ### Install prebuilt binaries via shell script ```sh curl --proto '=https' --tlsv1.2 -LsSf https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-installer.sh | sh && wt config shell install ``` ### Install prebuilt binaries via powershell script ```sh powershell -ExecutionPolicy Bypass -c "irm https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-installer.ps1 | iex"; git-wt config shell install ``` ### Install prebuilt binaries via Homebrew ```sh brew install worktrunk && wt config shell install ``` ## Download worktrunk 0.75.0 | File | Platform | Checksum | |--------|----------|----------| | [worktrunk-aarch64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-aarch64-apple-darwin.tar.xz) | Apple Silicon macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-aarch64-apple-darwin.tar.xz.sha256) | | [worktrunk-x86_64-apple-darwin.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-x86_64-apple-darwin.tar.xz) | Intel macOS | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-x86_64-apple-darwin.tar.xz.sha256) | | [worktrunk-x86_64-pc-windows-msvc.zip](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-x86_64-pc-windows-msvc.zip) | x64 Windows | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-x86_64-pc-windows-msvc.zip.sha256) | | [worktrunk-aarch64-unknown-linux-musl.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-aarch64-unknown-linux-musl.tar.xz) | ARM64 MUSL Linux | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-aarch64-unknown-linux-musl.tar.xz.sha256) | | [worktrunk-x86_64-unknown-linux-musl.tar.xz](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-x86_64-unknown-linux-musl.tar.xz) | x64 MUSL Linux | [checksum](https://github.com/max-sixty/worktrunk/releases/download/v0.75.0/worktrunk-x86_64-unknown-linux-musl.tar.xz.sha256) | ### Install via Cargo ```sh cargo install worktrunk && wt config shell install ``` ### Install via Winget (Windows) ```sh winget install max-sixty.worktrunk && git-wt config shell install ``` ### Install via AUR (Arch Linux) ```sh paru worktrunk-bin && wt config shell install ``` </pre> <p>View the full release notes at <a href="https://github.com/max-sixty/worktrunk/releases/tag/v0.75.0">https://github.com/max-sixty/worktrunk/releases/tag/v0.75.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!17938
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
max-sixty/worktrunk-assets.Closes #3827.
Verification
cargo run -- hook pre-merge --yes(4,660 tests)npm --prefix docs run checknpm --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)