Publish shell integration, add a footer, dedupe global options - #4000
Publish shell integration, add a footer, dedupe global options#4000max-sixty wants to merge 19 commits into
Conversation
#36 — 26 flat H2 recipes in no order, all 26 in the sidebar (the ToC caps at H2). Group them under five H2s — setup and layout, aliases and hooks, per-worktree services, working with agents, status/commits/logs — and demote each recipe to H3. Anchors are level-independent, so every existing `/tips-patterns/#…` fragment still resolves. #49 — `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal program. `-x` names the program and arguments after `--` are passed to it: `-x opencode -- run '<task>'`. #50 — the unified-diff tab moved branch summaries from preview tab 5 to 6. Drop the number and name the `summary` tab, linking the picker's tab list. #52 — the Caddy recipe claimed `feature-auth` hashes to port 16460; that is `fix-auth`'s port. `feature-auth` is 18283. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#37 — 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. Promote it to /shell-integration/ (a category-2 page, so the skill copy is now generated) and point the FAQ at it, keeping the plugin as a second option. Correct troubleshooting.md's `wt list` timeout quote to the real text, `Listing worktrees timed out after 120s (N results received); blocked tasks:`. #38 — 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 that resolves nowhere under the mirror. Nothing generated it, so deleting the symlink is the whole fix; the mirror drops its copy on the next sync, and the lychee exclusion loses the rationale it was written for. #39, #48 — one home per topic. Agent handoffs stay in tips-patterns and the skill points there; activity markers stay in claude-code.md and tips-patterns points there; alias-template deferral stays in extending.md, with the FAQ and troubleshooting reduced to links; shell-install file paths move to the new page. SKILL.md also drops the hook-type list sitting three lines below its pointer to reference/hook.md, and stops using this repo's own name in the sub-agent example. #45, #94 — the sidebar listed `remove` before `merge` and put Agent integration ahead of pages numbered lower, while each page's `sidebar.order` said otherwise. `sidebar.order` is load-bearing (it orders llms.txt), so reconcile the frontmatter to the authored sidebar rather than dropping it. `code-signing.md`'s `hidden: true` was dead config under an explicit sidebar array; the page keeps its order for llms.txt. Add an Install entry linking /#install. #47 — the FAQ linked /worktrunk/#install, the noindex compatibility route. Its "Running tests" and "How can I contribute?" sections duplicated the README's Contributing block down to the share URLs; the test commands move to the README and the FAQ links to it. #51, #61 — `_` is same-commit *and clean*; the same-commit-with-changes glyph is `–`, which is not safe to delete. `/wt-switch-create` is a skill that ships to Codex and Gemini too, inert there. The `.git/wt/cache/` table was missing `picker-preview`, and `wt config state clear` prompts unless `--yes`. 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". Skill-only headings move to sentence case. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…dings #95 — clap repeats the same ~20-line `Global Options:` block in every command reference it renders, so a page built from subdocs stacked 11 copies on /config/ and 13 on /step/. That padded the pages and gave the site search that many near-identical hits ("squash" returned both /step/#command-reference and #command-reference-2). `handle_help_page` now assembles the page as one string and keeps only the first global-options section; later references end at their own options. Terminal `--help` renders through clap directly and is unchanged. The same page carried colliding anchors: two "Hooks" (#hooks, #hooks-1), two "Aliases", and seven "Examples" (#examples … #examples-6). Qualify them at their source in src/cli/mod.rs and src/cli/config.rs — User/Project hooks, User/Project aliases, and Approval/Alias/State/Cache/Log/Variable examples — and repoint the links that named the old anchors. `SITE_LINK_PATTERN` gained an optional page segment so a homepage-anchor link like `[installation](/#install)` expands to a full URL on the skill and README surfaces instead of tripping the untransformed-link guardrail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#96 — `wt list --full` renders 1157px inside the 800px content column on /list/, so 40% of it sits behind a horizontal scrollbar while the pane beside the column is empty. A terminal frame now takes the whole pane on viewports that have slack there. `.content-panel` becomes the query container so the room available is measured rather than recomputed from Starlight's main-pane formula. #99 — the 2.33 MB `wt-zellij-omnibus.gif` sits well below the fold on the homepage and loaded eagerly; it is now `loading="lazy"`. The `/switch/` demo opened on a bare prompt with nothing saying what it shows, so demo placeholders take an optional `| caption` that becomes a `<figcaption>`, and all four command demos get one. Caption styling, which was scoped to the homepage, now covers every `figure.demo`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#97 — no page named the version, linked the changelog or releases, or said what the license is, and /code-signing/ was reachable only from inside a collapsed block on the homepage. Wrap Starlight's Footer rather than replacing it, keeping the upstream merge surface to the one element added. The version is read from Cargo.toml at build time, so a release doesn't leave a second copy to update. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#100 — the `wt-command-reference` frames carried a copy button offering 3,877 characters of generated help text, which nobody pastes anywhere; those frames now expose no copy control. /switch/'s first Examples block copies six alternatives as one payload. A 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 now carries its own copy control alongside the block's. The block control still copies the whole recipe; hovering a line hands the corner to that line instead, so the two never overlap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#37 — llms.txt links every page as `https://worktrunk.dev/<slug>.md`, served from a hand-created symlink in `docs/public/` into the skill reference. A new page reaches llms.txt from its frontmatter alone, so /shell-integration.md was a 404 the listing still advertised. Add the symlink, and have the sync fail when a listed page has none. #95 — `wt config create --help` prints the generated config example, so it carries the renamed User/Project headings too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#95 — the first pass assembled the whole page as one string and then swept it for repeated `Global Options:` sections, ending each at the next closing fence. The fence is a property of the surrounding page, not of the block being cut, so the sweep only worked while the page was one buffer. `take_global_options` cuts each reference as it is built instead, at the heading itself: `help_reference_inner` already ends `after_long_help` at the first non-indented line, so the section runs to the end of the block. One `kept` flag threads through the page's subdoc expansion, and the page streams out again rather than accumulating. Also repoint two link-rewrite test cases at `/config/#project-hooks`; the `#hooks` anchor they named was split into User/Project hooks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#37 — the section now offers the shell-integration checklist first and the Claude Code plugin second, so "If Claude can't fix it" named only the second. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#61 — four stale claims, all in help text. `wt step prune` removes branches that have no worktree as well, so its one-liner says so; the min-age guard now says what each kind of candidate is aged by (a worktree by its creation time, a bare branch by its oldest reflog entry). `wt step eval -v` prints fifteen variables; the example showed two, under a lead that called them "the available template variables". Show the first few and elide the rest, and settle the names on myproject / feature-auth. A filter applied to `{{ vars.<key> }}` acts on the placeholder the preview substitutes, not a value, so `{{ vars.port | default('8080') }}` previews as `{{ vars.port }}` with the filter gone. The hook page said only that the reference renders as itself. The `StepCommand` ordering comment called eval, for-each and prune experimental; only promote, relocate and tether carry the badge. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#39 — the `codename` filter's entry on the hook page carried two `worktree-path` recipes byte-identical to the config page's worktree-path examples. The config page owns path templates, so the filter entry keeps the explanation and points there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#36 — tips-patterns' description advertised shell integration, which now has its own page, and Zellij layouts, which the page never carried. Name the five groups the recipes now sit under; the description is also the page's llms.txt entry. #37 — the repo's own guidance used `shell-integration.md` as its example of a skill-only page, which it stopped being when it was promoted to the site. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#99 — the caption support added for `<!-- demo: -->` placeholders covered the four command demos; the statusline and LLM-commit figures are written directly in their pages and still opened unlabeled. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#38 — both copies of the Codex copier rationale used `reference/README.md` as their example of a nested symlink, which is the file that deletion removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#45, #94 — `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 what the survey found: `remove` listed before `merge`, Agent integration ahead of lower-numbered pages. Write it, so the sidebar and the llms.txt ordering derived from the frontmatter can't drift apart again. A page may carry an order without a sidebar entry — `code-signing.md` does, so llms.txt still places it — so only pages the sidebar names are checked. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… anchor #47 — the hook shim's message sent users to https://worktrunk.dev/worktrunk/#install, the noindex compatibility route the FAQ stopped linking. The homepage carries the anchor at /#install, which is also where the sidebar's Install entry points. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb
`src/help.rs` had no tests. The two functions this branch adds or changes carry four branches between them, and three were unexercised: a reference with no `Global Options:` heading, and a demo placeholder with no caption (which is the documented optional form, so it has to keep rendering a figure without an empty `<figcaption>`). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb
worktrunk-bot
left a comment
There was a problem hiding this comment.
Checked the generator change by auditing what actually left the generated pages: across config.md, step.md, list.md, switch.md, merge.md and remove.md the only removed lines are Global Options blocks (12 on /step/, 10 on /config/, 1 on /list/) — nothing else is cut, and wt config show's Output: group survives, so the "section is last" assumption holds where it's exercised. Two things below.
/step/ still carries the anchor collisions /config/ just lost. The qualified headings landed in src/cli/config.rs only, so on the current head /config/ is down to the structural #command-reference repeats while /step/ is unchanged from the base: eight Examples (#examples … #examples-7, at the page top plus rebase, push, eval, for-each, prune, relocate, tether), two Options (commit, squash), two Arguments (for-each, tether), and pairs for Staging and Dry run. That's the same near-identical-search-hit problem the description opens with, on the page it names as the worse of the two, and the description reads as though both were covered. The fix is the same shape as the config.rs one — qualify at the after_long_help source in src/cli/step.rs.
Not approving. src/cli/mod.rs and src/cli/step.rs hold the wt remove --force-delete and wt step prune definitions, which this repo's Data-Loss Surface: Hold for Human Review puts outside what I approve even when the diff is help text. For what it's worth the edits there run the safe direction and check out against the code: _ really is same-commit and clean (MainState::Empty vs SameCommit in src/commands/list/model/state.rs), prune really does age a bare branch by its oldest reflog entry (orphan_branch_age takes .lines().last()), and feature-auth really does hash to 18283, not fix-auth's 16460.
| ```bash | ||
| zellij run -- wt switch --create <branch-name> -x <agent-cli> -- '<task description>' | ||
| ``` | ||
| When the user requests spawning a worktree with an agent in a background session ("spawn a worktree for...", "hand off to another agent"), use the tmux or Zellij command from [Agent handoffs](https://worktrunk.dev/tips-patterns/#agent-handoffs), substituting the CLI you are running as for `claude`. |
There was a problem hiding this comment.
This is the file's only pointer that leaves the skill: every other one is a bundled path (reference/config.md#user-hooks, reference/step.md#wt-step-for-each), and this content ships beside it at reference/tips-patterns.md#agent-handoffs — the section the inlined tmux/Zellij commands moved to. An agent reading the skill without network access loses the handoff commands entirely, which is the one place they were guaranteed to have them.
| When the user requests spawning a worktree with an agent in a background session ("spawn a worktree for...", "hand off to another agent"), use the tmux or Zellij command from [Agent handoffs](https://worktrunk.dev/tips-patterns/#agent-handoffs), substituting the CLI you are running as for `claude`. | |
| When the user requests spawning a worktree with an agent in a background session ("spawn a worktree for...", "hand off to another agent"), use the tmux or Zellij command from `reference/tips-patterns.md#agent-handoffs`, substituting the CLI you are running as for `claude`. |
Seven conflicts, all from #3997 editing the same hook and step help text. Took main's for six: the `vars`-preview paragraph (main's is the corrected one — a filter that transforms its input still runs against the placeholder, which this branch's wording denied), the `codename` recipe pointer, the `StepCommand` ordering comment, the `wt step eval -v` lead and sample block (main's names it abridged and uses the settled myproject / feature-auth names), and the `--min-age` flag description, which is read standalone in `--help` where "candidates" has no antecedent. Kept this branch's min-age prose, which carries both facts main's does plus where each kind of candidate gets its age from. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb
|
Right on both counts. The description did read as though The data-loss hold is understood and correct —
|
|
One item from the review sits outside "both counts": the inline suggestion on |
|
#4001 (mine, opened this morning) re-derived the One piece of it may be worth carrying over, in a weaker form than my review of #4001 claimed. That review said this PR leaves the widened regex untested. It doesn't: reverting This test closes both gaps. It passes on this branch at /// A link naming the site root carries no page segment. Both generated
/// surfaces have to rewrite it, or the only spelling the docs can use is
/// `/worktrunk/` — the route the home page itself marks `noindex` with a
/// canonical pointing back at `/`.
///
/// `](/page)` (a page segment without its trailing slash) stays unmatched, so
/// the guardrail keeps catching it instead of the widened pattern quietly
/// absorbing it.
#[test]
fn test_site_root_links_rewrite_to_the_canonical_url() {
let rewrite = |content: &str| {
SITE_LINK_PATTERN
.replace_all(content, expand_site_link)
.into_owned()
};
assert_eq!(
rewrite("See [installation](/#install) and [hooks](/hook/#types)."),
"See [installation](https://worktrunk.dev/#install) and \
[hooks](https://worktrunk.dev/hook/#types)."
);
assert_eq!(
rewrite("[Worktrunk](/) is a CLI."),
"[Worktrunk](https://worktrunk.dev/) is a CLI."
);
// Both surfaces, since each applies the rewrite on its own path.
assert!(
transform_docs_to_github("[install](/#install)").contains("https://worktrunk.dev/#install")
);
assert!(
finalize_skill_content("[install](/#install)").contains("https://worktrunk.dev/#install")
);
// Unchanged: a slash-less page segment is still not a site link.
assert_eq!(rewrite("[hooks](/hook)"), "[hooks](/hook)");
}Take it or leave it — the fix itself is complete here either way. What I ranIn a worktree at
|
…example (#4006) ## Problem The `worktree-path` examples in the user-config guide are introduced as being **"for repo at `~/code/myproject`"**, and every example states the path it produces. For all but one, that arithmetic checks out. The bare-repository example doesn't: ```toml worktree-path = "{{ repo_path }}/../{{ branch | sanitize }}" ``` Heading claimed `~/code/myproject/feature-auth`. With `repo_path` at `~/code/myproject` as the section says, `{{ repo_path }}/../feature-auth` resolves to `~/code/feature-auth` — one directory up from the stated result. ## Fix The template is right; the heading silently switched the repo's location without saying so. `{{ repo_path }}` for a bare repo is the bare directory itself (as the variable list a few lines above states), so the claimed result holds only when that directory is a hidden child — the `myproject/.git` layout that [tips-patterns.md documents](https://github.com/max-sixty/worktrunk/blob/main/docs/src/content/docs/tips-patterns.md#bare-repository-layout), and that `wt switch`'s bare-repo offer writes this exact template for. So the heading now names it: > Bare repository cloned to `~/code/myproject/.git` (`~/code/myproject/feature-auth`): Edited in `src/cli/mod.rs` (the primary source); the four generated mirrors and two `--help` snapshots are regenerated output. ## Testing No regression test — this is a documentation string with no behavior attached. The generated mirrors are pinned by the existing sync tests, which is what caught them here: - `cargo test --test integration readme_sync` — 18 passed (regenerates `dev/config.example.toml`, `docs/src/content/docs/config.md`, and both `skills/.../reference/config.md` mirrors). - `cargo insta test --accept --test integration -- test_help` — 47 passed (`help_config_create`, `help_config_long`). - `cargo fmt --check` — clean. - `cargo test --test integration` — 2048 passed, 1 failed. <details><summary>The one integration failure is a sandbox artifact, not a regression</summary> `step_copy_ignored::test_copy_ignored_preserves_file_executable_permissions` expects `0644` and gets `0664`. The tend sandbox runs with `umask 0002` (group-writable) rather than the `0022` the test assumes. Confirmed unrelated: it reproduces identically with this branch's changes stashed, i.e. on the merge base. Both `ci` and `coverage` on `main` are green at `2026-09-03T10:17:26Z`. This diff touches only doc strings and snapshot files and cannot reach file-permission code. </details> <details><summary>Checked against the in-flight docs PRs</summary> #4000, #3999, and #3998 each touch the same five files (`src/cli/mod.rs` plus the four config mirrors), so I checked for the duplication that sank #4001. None of them edits the `worktree-path` examples region, and `git merge-tree` against each reports a clean merge with this branch. </details> Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
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-referenceand#command-reference-2.take_global_optionscuts each reference at the heading as it is built, keeping only the first; onekeptflag threads through the subdoc expansion and the page streams out rather than accumulating. Terminal--helprenders 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 insrc/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-Nanchors, 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 switchdidn't cd" was to install the Claude Code plugin. It is now/shell-integration/, offered first, with the plugin as the second route. Thellms.txtlisting serves every page as/<slug>.mdfrom 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
Cargo.tomlat 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'sFooteris wrapped rather than replaced.wt list --fullrenders 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.wt-command-referenceframes 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.Sidebar order is pinned
site-navigation.mjstold readers atest_sidebar_matches_frontmatter_orderwould fail when the authored sidebar and the pages'sidebar.orderdisagreed. No such test existed, and the disagreement it describes is exactly what the survey found:removelisted beforemerge, Agent integration ahead of lower-numbered pages. The test is written, so the sidebar and thellms.txtordering derived from the frontmatter can't drift apart again.Guide corrections
/tips-patterns/#…fragments still resolve.-x 'opencode run'has been broken since 0.75.0 made-xa literal program: it is-x opencode -- run '<task>'.summarytab instead of a number.feature-authhashes to port 16460 — that isfix-auth's port. It is 18283._inwt listis same-commit and clean; the same-commit-with-changes glyph is–, which is not safe to delete.wt step pruneremoves 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 -vprints fifteen variables; the example showed two under a lead calling them "the available template variables".{{ vars.<key> }}acts on the placeholder the preview substitutes, so{{ vars.port | default('8080') }}previews as{{ vars.port }}, filter gone..git/wt/cache/table was missingpicker-preview, andwt config state clearprompts unless--yes.skills/worktrunk/reference/README.mdwas a symlink to the repo README thatSKILL.mdnever 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.claude-code.md, alias-template deferral inextending.md, and thecodenamefilter's twoworktree-pathrecipes 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./worktrunk/#install, thenoindexcompatibility route; the plugin hook shim's Windows Terminal hint pointed there too. Both use/#install, where the new sidebar Install entry goes.myproject/feature-auth; "sibling to main repo" becomes "sibling to the main worktree", andwt remove's "target worktree" becomes "the worktree being removed" per the project's own terminology rule.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 newshell-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.🤖 Generated with Claude Code
https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb