1073 Commits

Author SHA1 Message Date
dependabot[bot] 3132d4fd09 chore: bump the docs-site group in /docs with 3 updates (#4099)
Bumps the docs-site group in /docs with 3 updates:
[@astrojs/markdown-remark](https://github.com/withastro/astro/tree/HEAD/packages/markdown/remark),
[astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) and
[playwright](https://github.com/microsoft/playwright).

Updates `@astrojs/markdown-remark` from 7.3.0 to 7.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/releases">@​astrojs/markdown-remark's
releases</a>.</em></p>
<blockquote>
<h2><code>@​astrojs/markdown-remark</code><a
href="https://github.com/7"><code>@​7</code></a>.3.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17896">#17896</a>
<a
href="https://github.com/withastro/astro/commit/a548223607b9bb146d5d90ddda495343f9a2a739"><code>a548223</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
<code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> rendering in MDX
so that only literal content (including content injected by
remark/rehype plugins) is treated as trusted markup. A dynamic value
passed as a <code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> child
(e.g. <code>&lt;script&gt;{value}&lt;/script&gt;</code>) is now escaped
like any other element's content instead of being rendered raw. Use
<code>set:html</code> to explicitly opt a dynamic value back into raw
rendering.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/blob/main/packages/markdown/remark/CHANGELOG.md">@​astrojs/markdown-remark's
changelog</a>.</em></p>
<blockquote>
<h2>7.3.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17896">#17896</a>
<a
href="https://github.com/withastro/astro/commit/a548223607b9bb146d5d90ddda495343f9a2a739"><code>a548223</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
<code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> rendering in MDX
so that only literal content (including content injected by
remark/rehype plugins) is treated as trusted markup. A dynamic value
passed as a <code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> child
(e.g. <code>&lt;script&gt;{value}&lt;/script&gt;</code>) is now escaped
like any other element's content instead of being rendered raw. Use
<code>set:html</code> to explicitly opt a dynamic value back into raw
rendering.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/withastro/astro/commit/aa4949e425144e0d276d2ff70f01209e90fdfbe8"><code>aa4949e</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/markdown/remark/issues/17915">#17915</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/a548223607b9bb146d5d90ddda495343f9a2a739"><code>a548223</code></a>
Only treat literal script/style content as raw in MDX rendering (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/markdown/remark/issues/17896">#17896</a>)</li>
<li>See full diff in <a
href="https://github.com/withastro/astro/commits/@astrojs/markdown-remark@7.3.1/packages/markdown/remark">compare
view</a></li>
</ul>
</details>
<br />

Updates `astro` from 7.3.1 to 7.3.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/releases">astro's
releases</a>.</em></p>
<blockquote>
<h2>astro@7.3.2</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17896">#17896</a>
<a
href="https://github.com/withastro/astro/commit/a548223607b9bb146d5d90ddda495343f9a2a739"><code>a548223</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
<code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> rendering in MDX
so that only literal content (including content injected by
remark/rehype plugins) is treated as trusted markup. A dynamic value
passed as a <code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> child
(e.g. <code>&lt;script&gt;{value}&lt;/script&gt;</code>) is now escaped
like any other element's content instead of being rendered raw. Use
<code>set:html</code> to explicitly opt a dynamic value back into raw
rendering.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17931">#17931</a>
<a
href="https://github.com/withastro/astro/commit/c1a6a89efa577b8388f04c4b42d655913bb4b886"><code>c1a6a89</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes the dev toolbar returning a 504 &quot;Outdated Optimize
Dep&quot; error when a workspace-linked package imports a dependency
that Vite's initial scan did not discover</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17908">#17908</a>
<a
href="https://github.com/withastro/astro/commit/42e9188c4ba7360e5ab8ea4cd7f13d6abcf31879"><code>42e9188</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes i18n fallback routing replacing the first substring match
instead of the actual locale segment, which mangled paths like
<code>/energy/en/about</code> into <code>/esergy/en/about</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17936">#17936</a>
<a
href="https://github.com/withastro/astro/commit/4b92ddc6ab0795ac78c30aedfe43b081dcf5b6b0"><code>4b92ddc</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes sessions breaking in dev mode with the Cloudflare adapter when
middleware is present</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/withastro/astro/commit/a548223607b9bb146d5d90ddda495343f9a2a739"><code>a548223</code></a>]:</p>
<ul>
<li><code>@​astrojs/markdown-satteri</code><a
href="https://github.com/0"><code>@​0</code></a>.4.1</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md">astro's
changelog</a>.</em></p>
<blockquote>
<h2>7.3.2</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17896">#17896</a>
<a
href="https://github.com/withastro/astro/commit/a548223607b9bb146d5d90ddda495343f9a2a739"><code>a548223</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
<code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> rendering in MDX
so that only literal content (including content injected by
remark/rehype plugins) is treated as trusted markup. A dynamic value
passed as a <code>&lt;script&gt;</code>/<code>&lt;style&gt;</code> child
(e.g. <code>&lt;script&gt;{value}&lt;/script&gt;</code>) is now escaped
like any other element's content instead of being rendered raw. Use
<code>set:html</code> to explicitly opt a dynamic value back into raw
rendering.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17931">#17931</a>
<a
href="https://github.com/withastro/astro/commit/c1a6a89efa577b8388f04c4b42d655913bb4b886"><code>c1a6a89</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes the dev toolbar returning a 504 &quot;Outdated Optimize
Dep&quot; error when a workspace-linked package imports a dependency
that Vite's initial scan did not discover</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17908">#17908</a>
<a
href="https://github.com/withastro/astro/commit/42e9188c4ba7360e5ab8ea4cd7f13d6abcf31879"><code>42e9188</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes i18n fallback routing replacing the first substring match
instead of the actual locale segment, which mangled paths like
<code>/energy/en/about</code> into <code>/esergy/en/about</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17936">#17936</a>
<a
href="https://github.com/withastro/astro/commit/4b92ddc6ab0795ac78c30aedfe43b081dcf5b6b0"><code>4b92ddc</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes sessions breaking in dev mode with the Cloudflare adapter when
middleware is present</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/withastro/astro/commit/a548223607b9bb146d5d90ddda495343f9a2a739"><code>a548223</code></a>]:</p>
<ul>
<li><code>@​astrojs/markdown-satteri</code><a
href="https://github.com/0"><code>@​0</code></a>.4.1</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/withastro/astro/commit/aa4949e425144e0d276d2ff70f01209e90fdfbe8"><code>aa4949e</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17915">#17915</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/42e9188c4ba7360e5ab8ea4cd7f13d6abcf31879"><code>42e9188</code></a>
fix(i18n): replace locale segment by index in fallback routing (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17907">#17907</a>)
(<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17908">#17908</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/4b92ddc6ab0795ac78c30aedfe43b081dcf5b6b0"><code>4b92ddc</code></a>
Guard setFetchHandler call in non-runnable dev entrypoint to fix
sessions + m...</li>
<li><a
href="https://github.com/withastro/astro/commit/c1a6a89efa577b8388f04c4b42d655913bb4b886"><code>c1a6a89</code></a>
fix: include .astro files in client optimizeDeps entries to prevent 504
on la...</li>
<li><a
href="https://github.com/withastro/astro/commit/a548223607b9bb146d5d90ddda495343f9a2a739"><code>a548223</code></a>
Only treat literal script/style content as raw in MDX rendering (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17896">#17896</a>)</li>
<li>See full diff in <a
href="https://github.com/withastro/astro/commits/astro@7.3.2/packages/astro">compare
view</a></li>
</ul>
</details>
<br />

Updates `playwright` from 1.62.1 to 1.63.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/microsoft/playwright/releases">playwright's
releases</a>.</em></p>
<blockquote>
<h2>v1.63.0</h2>
<h2>🔒 Test locks</h2>
<p>Tests that access a shared resource — an external service, a global
account setting — can now declare a named <code>lock</code>.
Tests that share a lock name never run concurrently, across files,
workers and <a
href="https://playwright.dev/docs/test-projects">projects</a>, while
everything else keeps running in parallel:</p>
<pre lang="js"><code>test('update user settings', { lock:
'user-settings' }, async ({ page }) =&gt; {
  // never runs at the same time as other tests holding 'user-settings'
});
</code></pre>
<p>A test can hold multiple locks, and <a
href="https://playwright.dev/docs/api/class-test#test-describe">test.describe()</a>
accepts a <code>lock</code> for the whole group.
Learn more about <a
href="https://playwright.dev/docs/test-parallel#test-locks">test
locks</a>.</p>
<h2>🪟 Locate across frames</h2>
<p><a
href="https://playwright.dev/docs/api/class-page#page-frame-locator">page.frameLocator()</a>
and <a
href="https://playwright.dev/docs/api/class-frame#frame-frame-locator">frame.frameLocator()</a>
called without a selector search in any frame of the
subtree, so you no longer need to locate the iframe first:</p>
<pre lang="js"><code>// Finds the button in any frame on the page.
await page.frameLocator().getByRole('button').click();
</code></pre>
<p>The rest of the locator resolves inside a single frame, just like a
regular locator, and an error is thrown when it
matches elements in several frames.</p>
<h2>👁️ Visible-only locators</h2>
<p>New <a
href="https://playwright.dev/docs/api/class-locator#locator-visible">locator.visible()</a>
returns a locator that matches only visible elements. It is the
recommended
replacement for the <code>:visible</code> CSS pseudo-class:</p>
<pre lang="js"><code>await page.locator('button').visible().click();
</code></pre>
<h2>🧾 Step params and subtitles</h2>
<p>Steps now carry structured data for reporters. Playwright API steps
report the target locator and call arguments,
and <a
href="https://playwright.dev/docs/api/class-test#test-step">test.step()</a>
accepts <code>subtitle</code> and <code>params</code> options for your
own steps:</p>
<pre lang="js"><code>await test.step('Login', async () =&gt; {
  // ...
}, { subtitle: 'as admin', params: { user: 'admin' } });
</code></pre>
<p>Reporters receive them via <a
href="https://playwright.dev/docs/api/class-teststep#test-step-subtitle">testStep.subtitle</a>
and <a
href="https://playwright.dev/docs/api/class-teststep#test-step-params">testStep.params</a>.
For Playwright API</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/microsoft/playwright/commit/1b025d7e20a026371cd5f98ba0cdce48892737c8"><code>1b025d7</code></a>
chore: mark v1.63.0 (<a
href="https://redirect.github.com/microsoft/playwright/issues/42569">#42569</a>)</li>
<li><a
href="https://github.com/microsoft/playwright/commit/0b9956d2247ce88399c3c46a22c1cc0526acb340"><code>0b9956d</code></a>
cherry-pick(<a
href="https://redirect.github.com/microsoft/playwright/issues/42568">#42568</a>):
docs(test): mark test.step subtitle option as since v1.63</li>
<li><a
href="https://github.com/microsoft/playwright/commit/13dbf102b88305ed96b7791a7843246cd75d4dae"><code>13dbf10</code></a>
cherry-pick(<a
href="https://redirect.github.com/microsoft/playwright/issues/42552">#42552</a>):
docs: release notes for v1.63</li>
<li><a
href="https://github.com/microsoft/playwright/commit/e93b64ed6f9cf6ac2cc8cb13ad01514b406a7142"><code>e93b64e</code></a>
cherry-pick(<a
href="https://redirect.github.com/microsoft/playwright/issues/42566">#42566</a>):
feat(test): add subtitle option to test.step (<a
href="https://redirect.github.com/microsoft/playwright/issues/42567">#42567</a>)</li>
<li><a
href="https://github.com/microsoft/playwright/commit/2b7a5f2ea1cea04b549c5378498c1b16b6bc6a6f"><code>2b7a5f2</code></a>
test: response.body() for content-encoding:identity (<a
href="https://redirect.github.com/microsoft/playwright/issues/42537">#42537</a>)</li>
<li><a
href="https://github.com/microsoft/playwright/commit/648a67c7c1261eefe4113cba2d586417d5e3f2f2"><code>648a67c</code></a>
fix(mcp): create parent directories for explicitly named files (<a
href="https://redirect.github.com/microsoft/playwright/issues/42540">#42540</a>)</li>
<li><a
href="https://github.com/microsoft/playwright/commit/7894f5653e7f253c17aa1638c19f63e0d54d5e0e"><code>7894f56</code></a>
docs(mcp): clarify how tool file names are resolved (<a
href="https://redirect.github.com/microsoft/playwright/issues/42538">#42538</a>)</li>
<li><a
href="https://github.com/microsoft/playwright/commit/52900a1b99ca51011526afcaf8981970cc1a521a"><code>52900a1</code></a>
devops: restore npm publishing from GitHub Actions (<a
href="https://redirect.github.com/microsoft/playwright/issues/42550">#42550</a>)</li>
<li><a
href="https://github.com/microsoft/playwright/commit/8c47f596edebd2460895a5953f2ea67f8b884001"><code>8c47f59</code></a>
docs(csharp): fix nonexistent method names in guide examples (<a
href="https://redirect.github.com/microsoft/playwright/issues/42507">#42507</a>)</li>
<li><a
href="https://github.com/microsoft/playwright/commit/bd6e552a63f8cf9b0da0216f392b8e63a08bf1d2"><code>bd6e552</code></a>
chore(video): emit frames with real timestamps, drop frame number
quantizatio...</li>
<li>Additional commits viewable in <a
href="https://github.com/microsoft/playwright/compare/v1.62.1...v1.63.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-13 22:05:39 -07:00
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
dependabot[bot] 5b93f1b321 chore: bump the docs-site group in /docs with 5 updates (#4031)
Bumps the docs-site group in /docs with 5 updates:

| Package | From | To |
| --- | --- | --- |
|
[@astrojs/markdown-remark](https://github.com/withastro/astro/tree/HEAD/packages/markdown/remark)
| `7.2.4` | `7.3.0` |
|
[@astrojs/sitemap](https://github.com/withastro/astro/tree/HEAD/packages/integrations/sitemap)
| `3.7.3` | `3.7.4` |
|
[@astrojs/starlight](https://github.com/withastro/starlight/tree/HEAD/packages/starlight)
| `0.41.9` | `0.42.0` |
|
[@expressive-code/core](https://github.com/expressive-code/expressive-code/tree/HEAD/packages/@expressive-code/core)
| `0.44.1` | `0.44.2` |
| [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) |
`7.2.9` | `7.3.1` |

Updates `@astrojs/markdown-remark` from 7.2.4 to 7.3.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/releases">@​astrojs/markdown-remark's
releases</a>.</em></p>
<blockquote>
<h2><code>@​astrojs/markdown-remark</code><a
href="https://github.com/7"><code>@​7</code></a>.3.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17262">#17262</a>
<a
href="https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40"><code>f8e9458</code></a>
Thanks <a
href="https://github.com/Princesseuh"><code>@​Princesseuh</code></a>! -
Adds MDX rendering to the <code>unified()</code> and
<code>satteri()</code> processors.</p>
<p>Both processors now compile <code>.mdx</code> files themselves. You
still need to install <code>@astrojs/mdx</code> to add MDX support to
your project.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17262">#17262</a>
<a
href="https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40"><code>f8e9458</code></a>
Thanks <a
href="https://github.com/Princesseuh"><code>@​Princesseuh</code></a>! -
Adds a <code>recmaPlugins</code> option to <code>unified()</code> for
adding recma (estree/JSX) plugins to the MDX compiler.</p>
</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40"><code>f8e9458</code></a>]:
<ul>
<li><code>@​astrojs/internal-helpers</code><a
href="https://github.com/0"><code>@​0</code></a>.11.0</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/blob/main/packages/markdown/remark/CHANGELOG.md">@​astrojs/markdown-remark's
changelog</a>.</em></p>
<blockquote>
<h2>7.3.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17262">#17262</a>
<a
href="https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40"><code>f8e9458</code></a>
Thanks <a
href="https://github.com/Princesseuh"><code>@​Princesseuh</code></a>! -
Adds MDX rendering to the <code>unified()</code> and
<code>satteri()</code> processors.</p>
<p>Both processors now compile <code>.mdx</code> files themselves. You
still need to install <code>@astrojs/mdx</code> to add MDX support to
your project.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17262">#17262</a>
<a
href="https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40"><code>f8e9458</code></a>
Thanks <a
href="https://github.com/Princesseuh"><code>@​Princesseuh</code></a>! -
Adds a <code>recmaPlugins</code> option to <code>unified()</code> for
adding recma (estree/JSX) plugins to the MDX compiler.</p>
</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40"><code>f8e9458</code></a>]:
<ul>
<li><code>@​astrojs/internal-helpers</code><a
href="https://github.com/0"><code>@​0</code></a>.11.0</li>
</ul>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/withastro/astro/commit/2fdf731428aa738d5dcf3041b4e78eb9d036968c"><code>2fdf731</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/markdown/remark/issues/17849">#17849</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40"><code>f8e9458</code></a>
feat: move MDX into the processors themselves (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/markdown/remark/issues/17262">#17262</a>)</li>
<li>See full diff in <a
href="https://github.com/withastro/astro/commits/@astrojs/markdown-remark@7.3.0/packages/markdown/remark">compare
view</a></li>
</ul>
</details>
<br />

Updates `@astrojs/sitemap` from 3.7.3 to 3.7.4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/releases">@​astrojs/sitemap's
releases</a>.</em></p>
<blockquote>
<h2><code>@​astrojs/sitemap</code><a
href="https://github.com/3"><code>@​3</code></a>.7.4</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17851">#17851</a>
<a
href="https://github.com/withastro/astro/commit/52d3f56999ecdf92510b2c16ed2a3a4785b9c73a"><code>52d3f56</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes the sitemap outputting a URL with an empty path for the homepage
(e.g. <code>https://example.com</code> instead of
<code>https://example.com/</code>) when <code>trailingSlash</code> is
set to <code>&quot;never&quot;</code> or <code>build.format</code> is
set to <code>&quot;file&quot;</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/blob/main/packages/integrations/sitemap/CHANGELOG.md">@​astrojs/sitemap's
changelog</a>.</em></p>
<blockquote>
<h2>3.7.4</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17851">#17851</a>
<a
href="https://github.com/withastro/astro/commit/52d3f56999ecdf92510b2c16ed2a3a4785b9c73a"><code>52d3f56</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes the sitemap outputting a URL with an empty path for the homepage
(e.g. <code>https://example.com</code> instead of
<code>https://example.com/</code>) when <code>trailingSlash</code> is
set to <code>&quot;never&quot;</code> or <code>build.format</code> is
set to <code>&quot;file&quot;</code></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/withastro/astro/commit/2fdf731428aa738d5dcf3041b4e78eb9d036968c"><code>2fdf731</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/integrations/sitemap/issues/17849">#17849</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/52d3f56999ecdf92510b2c16ed2a3a4785b9c73a"><code>52d3f56</code></a>
fix(<code>@​astrojs/sitemap</code>): preserve root path <code>/</code>
in sitemap URLs when trailingSl...</li>
<li><a
href="https://github.com/withastro/astro/commit/2bfb179545249786e9f395325c88a9dfef574acb"><code>2bfb179</code></a>
chore: simpler package.json types (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/integrations/sitemap/issues/17229">#17229</a>)</li>
<li>See full diff in <a
href="https://github.com/withastro/astro/commits/@astrojs/sitemap@3.7.4/packages/integrations/sitemap">compare
view</a></li>
</ul>
</details>
<br />

Updates `@astrojs/starlight` from 0.41.9 to 0.42.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/starlight/releases">@​astrojs/starlight's
releases</a>.</em></p>
<blockquote>
<h2><code>@​astrojs/starlight</code><a
href="https://github.com/0"><code>@​0</code></a>.42.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/3572">#3572</a>
<a
href="https://github.com/withastro/starlight/commit/292fb179cbf6e2cd970ced46359c520aa14a80b4"><code>292fb17</code></a>
Thanks <a href="https://github.com/HiDeoo"><code>@​HiDeoo</code></a>! -
Distributes package as JavaScript files with dedicated type declaration
files instead of TypeScript source files.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4121">#4121</a>
<a
href="https://github.com/withastro/starlight/commit/2623ae60af0fdafc71938f96153b0427fcc2b5aa"><code>2623ae6</code></a>
Thanks <a href="https://github.com/delucis"><code>@​delucis</code></a>!
- Simplifies markup for Starlight’s mobile menu toggle</p>
<p><strong>⚠️ Potentially breaking change:</strong> If you use a theme
plugin, custom styles, or component overrides targeting the
<code>MobileMenuToggle</code> button or <code>PageFrame</code>
components, you may need to adjust these for the new markup. The button
is no longer wrapped in a <code>&lt;starlight-menu-button&gt;</code>
custom element and no longer uses the <code>aria-expanded</code>
attribute. Instead, you can use the <code>.sl-menu-button</code> class
name to target the button and the <code>:popover-open</code>
pseudo-class to style the menu open state specifically.</p>
<p>In the following example, custom styles for the menu button are
updated for the new approach:</p>
<pre lang="diff"><code>- starlight-menu-button button {
+ .sl-menu-button {
  color: var(--sl-color-text);
}
<ul>
<li>starlight-menu-button[aria-expanded='true'] button {</li>
</ul>
<ul>
<li>.sl-menu-button:has(~ :popover-open) {
color: var(--sl-color-text-accent-high);
}
</code></pre></li>
</ul>
<p>See <a
href="https://github.com/withastro/starlight/blob/main/packages/starlight/components/MobileMenuToggle.astro"><code>MobileMenuToggle.astro</code></a>
and <a
href="https://github.com/withastro/starlight/blob/main/packages/starlight/components/PageFrame.astro"><code>PageFrame.astro</code></a>
on GitHub for the full source code of the updated components.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/3572">#3572</a>
<a
href="https://github.com/withastro/starlight/commit/292fb179cbf6e2cd970ced46359c520aa14a80b4"><code>292fb17</code></a>
Thanks <a href="https://github.com/HiDeoo"><code>@​HiDeoo</code></a>! -
Removes the <code>tagline</code> configuration option, which was never
used.</p>
<p>If your configuration included a <code>tagline</code> option, you can
safely remove it without any replacement.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4134">#4134</a>
<a
href="https://github.com/withastro/starlight/commit/6135f015b527453d7083ad01d2dc6c61d72fbefc"><code>6135f01</code></a>
Thanks <a href="https://github.com/HiDeoo"><code>@​HiDeoo</code></a>! -
Updates internal <code>@astrojs/mdx</code>,
<code>@astrojs/markdown-satteri</code>, and <code>satteri</code>
dependencies.</p>
<p>⚠️ <strong>BREAKING CHANGE:</strong> The following minimum versions
are now required:</p>
<ul>
<li><code>astro</code> v7.2.10 or later</li>
<li><code>@astrojs/markdown-satteri</code> 0.4.0 or later (if you use
it)</li>
<li><code>@astrojs/markdown-remark</code> 7.3.0 or later (if you use
it)</li>
</ul>
<p>Please update Starlight and Astro together:</p>
<pre lang="sh"><code>npx @astrojs/upgrade
</code></pre>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4121">#4121</a>
<a
href="https://github.com/withastro/starlight/commit/2623ae60af0fdafc71938f96153b0427fcc2b5aa"><code>2623ae6</code></a>
Thanks <a href="https://github.com/delucis"><code>@​delucis</code></a>!
- Refactors Starlight’s mobile menu toggle to work when JavaScript fails
or is disabled</p>
<p>⚠️ <strong>BREAKING CHANGE:</strong> This release drops official
support for Chromium-based browsers prior to version 116 (released
August 2023), Safari-based browsers prior to version 17.0 (released
September 2023), and Firefox prior to version 125 (released April 2024).
You can find a list of currently supported browsers and their versions
using this <a
href="https://browsersl.ist/#q=%3E+0.5%25%2C+not+dead%2C+Chrome+%3E%3D+116%2C+Edge+%3E%3D+116%2C+Firefox+%3E%3D+125%2C+Safari+%3E%3D+17.0%2C+iOS+%3E%3D+17.0%2C+not+op_mini+all">browserslist
query</a>.</p>
<p>This change also removes the <code>data-mobile-menu-expanded</code>
attribute, which was previously added to <code>&lt;body&gt;</code> while
the mobile menu is open. If you have custom code that was depending on
this attribute, you will need to update it to use a new selector to
check if the mobile menu is open.</p>
<p>In the following example, a custom background colour for the site
header while the menu is open is updated for the new approach:</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/starlight/blob/main/packages/starlight/CHANGELOG.md">@​astrojs/starlight's
changelog</a>.</em></p>
<blockquote>
<h2>0.42.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/3572">#3572</a>
<a
href="https://github.com/withastro/starlight/commit/292fb179cbf6e2cd970ced46359c520aa14a80b4"><code>292fb17</code></a>
Thanks <a href="https://github.com/HiDeoo"><code>@​HiDeoo</code></a>! -
Distributes package as JavaScript files with dedicated type declaration
files instead of TypeScript source files.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4121">#4121</a>
<a
href="https://github.com/withastro/starlight/commit/2623ae60af0fdafc71938f96153b0427fcc2b5aa"><code>2623ae6</code></a>
Thanks <a href="https://github.com/delucis"><code>@​delucis</code></a>!
- Simplifies markup for Starlight’s mobile menu toggle</p>
<p><strong>⚠️ Potentially breaking change:</strong> If you use a theme
plugin, custom styles, or component overrides targeting the
<code>MobileMenuToggle</code> button or <code>PageFrame</code>
components, you may need to adjust these for the new markup. The button
is no longer wrapped in a <code>&lt;starlight-menu-button&gt;</code>
custom element and no longer uses the <code>aria-expanded</code>
attribute. Instead, you can use the <code>.sl-menu-button</code> class
name to target the button and the <code>:popover-open</code>
pseudo-class to style the menu open state specifically.</p>
<p>In the following example, custom styles for the menu button are
updated for the new approach:</p>
<pre lang="diff"><code>- starlight-menu-button button {
+ .sl-menu-button {
  color: var(--sl-color-text);
}
<ul>
<li>starlight-menu-button[aria-expanded='true'] button {</li>
</ul>
<ul>
<li>.sl-menu-button:has(~ :popover-open) {
color: var(--sl-color-text-accent-high);
}
</code></pre></li>
</ul>
<p>See <a
href="https://github.com/withastro/starlight/blob/main/packages/starlight/components/MobileMenuToggle.astro"><code>MobileMenuToggle.astro</code></a>
and <a
href="https://github.com/withastro/starlight/blob/main/packages/starlight/components/PageFrame.astro"><code>PageFrame.astro</code></a>
on GitHub for the full source code of the updated components.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/3572">#3572</a>
<a
href="https://github.com/withastro/starlight/commit/292fb179cbf6e2cd970ced46359c520aa14a80b4"><code>292fb17</code></a>
Thanks <a href="https://github.com/HiDeoo"><code>@​HiDeoo</code></a>! -
Removes the <code>tagline</code> configuration option, which was never
used.</p>
<p>If your configuration included a <code>tagline</code> option, you can
safely remove it without any replacement.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4134">#4134</a>
<a
href="https://github.com/withastro/starlight/commit/6135f015b527453d7083ad01d2dc6c61d72fbefc"><code>6135f01</code></a>
Thanks <a href="https://github.com/HiDeoo"><code>@​HiDeoo</code></a>! -
Updates internal <code>@astrojs/mdx</code>,
<code>@astrojs/markdown-satteri</code>, and <code>satteri</code>
dependencies.</p>
<p>⚠️ <strong>BREAKING CHANGE:</strong> The following minimum versions
are now required:</p>
<ul>
<li><code>astro</code> v7.2.10 or later</li>
<li><code>@astrojs/markdown-satteri</code> 0.4.0 or later (if you use
it)</li>
<li><code>@astrojs/markdown-remark</code> 7.3.0 or later (if you use
it)</li>
</ul>
<p>Please update Starlight and Astro together:</p>
<pre lang="sh"><code>npx @astrojs/upgrade
</code></pre>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4121">#4121</a>
<a
href="https://github.com/withastro/starlight/commit/2623ae60af0fdafc71938f96153b0427fcc2b5aa"><code>2623ae6</code></a>
Thanks <a href="https://github.com/delucis"><code>@​delucis</code></a>!
- Refactors Starlight’s mobile menu toggle to work when JavaScript fails
or is disabled</p>
<p>⚠️ <strong>BREAKING CHANGE:</strong> This release drops official
support for Chromium-based browsers prior to version 116 (released
August 2023), Safari-based browsers prior to version 17.0 (released
September 2023), and Firefox prior to version 125 (released April 2024).
You can find a list of currently supported browsers and their versions
using this <a
href="https://browsersl.ist/#q=%3E+0.5%25%2C+not+dead%2C+Chrome+%3E%3D+116%2C+Edge+%3E%3D+116%2C+Firefox+%3E%3D+125%2C+Safari+%3E%3D+17.0%2C+iOS+%3E%3D+17.0%2C+not+op_mini+all">browserslist
query</a>.</p>
<p>This change also removes the <code>data-mobile-menu-expanded</code>
attribute, which was previously added to <code>&lt;body&gt;</code> while
the mobile menu is open. If you have custom code that was depending on
this attribute, you will need to update it to use a new selector to
check if the mobile menu is open.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/withastro/starlight/commit/88ad3c2630487ba227a7b4ccbffc01a2bdf623a5"><code>88ad3c2</code></a>
[ci] release (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4172">#4172</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/74cb3f1a40985eed5f7974e2b1b994a2bdcfd8be"><code>74cb3f1</code></a>
Update dependencies (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4173">#4173</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/6135f015b527453d7083ad01d2dc6c61d72fbefc"><code>6135f01</code></a>
Use Sätteri 0.10 APIs (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4134">#4134</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/2623ae60af0fdafc71938f96153b0427fcc2b5aa"><code>2623ae6</code></a>
Refactor mobile menu to use Popover API (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4121">#4121</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/09af1c621e3bd48fa601d6ce47be181bb44d0e9b"><code>09af1c6</code></a>
chore(deps): update <code>magic-string</code> to v1 (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4082">#4082</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/292fb179cbf6e2cd970ced46359c520aa14a80b4"><code>292fb17</code></a>
[i18nIgnore] Transpile Starlight packages (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/3572">#3572</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/0138f22c04ab2a659b642526345ba53bef7e5033"><code>0138f22</code></a>
[ci] release (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4169">#4169</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/32a5e29127c13946ce746c36d7cb194f35d11952"><code>32a5e29</code></a>
Fix hidden heading anchor links (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4167">#4167</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/cdfafd8eedbbd6653cd6a1d3b1542b44ed1ba19e"><code>cdfafd8</code></a>
perf: optimize route and sidebar lookups (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4148">#4148</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/dd7fb9721e117337195fdfdb92a081d7dd23701e"><code>dd7fb97</code></a>
chore: add benchmarks (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4157">#4157</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/withastro/starlight/commits/@astrojs/starlight@0.42.0/packages/starlight">compare
view</a></li>
</ul>
</details>
<br />

Updates `@expressive-code/core` from 0.44.1 to 0.44.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/expressive-code/expressive-code/releases">@​expressive-code/core's
releases</a>.</em></p>
<blockquote>
<h2><code>@​expressive-code/core</code><a
href="https://github.com/0"><code>@​0</code></a>.44.2</h2>
<p>No release notes provided.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/expressive-code/expressive-code/blob/main/packages/@expressive-code/core/CHANGELOG.md">@​expressive-code/core's
changelog</a>.</em></p>
<blockquote>
<h2>0.44.2</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/expressive-code/expressive-code/commit/5c5b4522e0db357f453e2e6c9a46790e83ef6322"><code>5c5b452</code></a>
[CI] Release (<a
href="https://github.com/expressive-code/expressive-code/tree/HEAD/packages/@expressive-code/core/issues/469">#469</a>)</li>
<li>See full diff in <a
href="https://github.com/expressive-code/expressive-code/commits/@expressive-code/core@0.44.2/packages/@expressive-code/core">compare
view</a></li>
</ul>
</details>
<br />

Updates `astro` from 7.2.9 to 7.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/releases">astro's
releases</a>.</em></p>
<blockquote>
<h2>astro@7.3.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17899">#17899</a>
<a
href="https://github.com/withastro/astro/commit/03896405717471f7d6ff54986ed6beaec0cac94f"><code>0389640</code></a>
Thanks <a
href="https://github.com/ematipico"><code>@​ematipico</code></a>! -
Fixes an error that prevented projects using <code>astro:assets</code>
from starting or building</li>
</ul>
<h2>astro@7.3.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17767">#17767</a>
<a
href="https://github.com/withastro/astro/commit/ce7c91f77dbd7be03c04bc13f87af9d01fef6cef"><code>ce7c91f</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Adds <code>--ignore-lock</code> flag to <code>astro preview</code>,
allowing multiple preview servers to run simultaneously on different
ports. This is useful for E2E testing workflows (e.g., Playwright) that
need to run several preview servers at once.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17818">#17818</a>
<a
href="https://github.com/withastro/astro/commit/c0b65811dfa0dafa1aa04b7d6d67fd09250ff8c1"><code>c0b6581</code></a>
Thanks <a
href="https://github.com/florian-lefebvre"><code>@​florian-lefebvre</code></a>!
- Adds a <code>logger</code> parameter to image services hooks</p>
<p>Custom image services now receive Astro's runtime logger as an extra
argument. Messages logged with it are routed through the destination
configured in <code>logger</code> and respect your log level, instead of
being written straight to the console:</p>
<pre lang="ts"><code>import type { LocalImageService } from 'astro';
<p>const service: LocalImageService = {
// ...
async transform(inputBuffer, transform, imageConfig, logger) {
logger.warn(<code>Could not optimize
&amp;quot;${transform.src}&amp;quot;. Passing it through
unchanged.</code>);
return { data: inputBuffer, format: 'png' };
},
};
</code></pre></p>
<p>Astro's built-in Sharp service now uses this logger for the warnings
it emits when it encounters an unexpected or unsupported source
format.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17818">#17818</a>
<a
href="https://github.com/withastro/astro/commit/c0b65811dfa0dafa1aa04b7d6d67fd09250ff8c1"><code>c0b6581</code></a>
Thanks <a
href="https://github.com/florian-lefebvre"><code>@​florian-lefebvre</code></a>!
- Adds <code>logger</code> to the context object passed to cache
providers</p>
<p>Custom cache providers now receive Astro's runtime logger on the
context passed to <code>onRequest()</code>. Messages logged with it are
routed through the destination configured in <code>logger</code> and
respect your log level, instead of being written straight to the
console:</p>
<pre lang="ts"><code>import type { CacheProvider } from 'astro';
<p>const provider: CacheProvider = {
name: 'my-cache',
async onRequest({ request, url, logger }, next) {
logger.warn(<code>Skipping cache for ${url.pathname} because the
response sets a cookie.</code>);
return next();
},
// ...
};
</code></pre></p>
<p>Astro's built-in <code>memoryCache()</code> provider now uses this
logger for the warnings it emits when it skips caching a response that
sets cookies, and when a background revalidation fails.</p>
</li>
</ul>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17818">#17818</a>
<a
href="https://github.com/withastro/astro/commit/c0b65811dfa0dafa1aa04b7d6d67fd09250ff8c1"><code>c0b6581</code></a>
Thanks <a
href="https://github.com/florian-lefebvre"><code>@​florian-lefebvre</code></a>!
- Updates Astro's remaining internal warnings and errors to be written
through the configured logger instead of directly to the console, when
possible</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md">astro's
changelog</a>.</em></p>
<blockquote>
<h2>7.3.1</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17899">#17899</a>
<a
href="https://github.com/withastro/astro/commit/03896405717471f7d6ff54986ed6beaec0cac94f"><code>0389640</code></a>
Thanks <a
href="https://github.com/ematipico"><code>@​ematipico</code></a>! -
Fixes an error that prevented projects using <code>astro:assets</code>
from starting or building</li>
</ul>
<h2>7.3.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17767">#17767</a>
<a
href="https://github.com/withastro/astro/commit/ce7c91f77dbd7be03c04bc13f87af9d01fef6cef"><code>ce7c91f</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Adds <code>--ignore-lock</code> flag to <code>astro preview</code>,
allowing multiple preview servers to run simultaneously on different
ports. This is useful for E2E testing workflows (e.g., Playwright) that
need to run several preview servers at once.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17818">#17818</a>
<a
href="https://github.com/withastro/astro/commit/c0b65811dfa0dafa1aa04b7d6d67fd09250ff8c1"><code>c0b6581</code></a>
Thanks <a
href="https://github.com/florian-lefebvre"><code>@​florian-lefebvre</code></a>!
- Adds a <code>logger</code> parameter to image services hooks</p>
<p>Custom image services now receive Astro's runtime logger as an extra
argument. Messages logged with it are routed through the destination
configured in <code>logger</code> and respect your log level, instead of
being written straight to the console:</p>
<pre lang="ts"><code>import type { LocalImageService } from 'astro';
<p>const service: LocalImageService = {
// ...
async transform(inputBuffer, transform, imageConfig, logger) {
logger.warn(<code>Could not optimize
&amp;quot;${transform.src}&amp;quot;. Passing it through
unchanged.</code>);
return { data: inputBuffer, format: 'png' };
},
};
</code></pre></p>
<p>Astro's built-in Sharp service now uses this logger for the warnings
it emits when it encounters an unexpected or unsupported source
format.</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17818">#17818</a>
<a
href="https://github.com/withastro/astro/commit/c0b65811dfa0dafa1aa04b7d6d67fd09250ff8c1"><code>c0b6581</code></a>
Thanks <a
href="https://github.com/florian-lefebvre"><code>@​florian-lefebvre</code></a>!
- Adds <code>logger</code> to the context object passed to cache
providers</p>
<p>Custom cache providers now receive Astro's runtime logger on the
context passed to <code>onRequest()</code>. Messages logged with it are
routed through the destination configured in <code>logger</code> and
respect your log level, instead of being written straight to the
console:</p>
<pre lang="ts"><code>import type { CacheProvider } from 'astro';
<p>const provider: CacheProvider = {
name: 'my-cache',
async onRequest({ request, url, logger }, next) {
logger.warn(<code>Skipping cache for ${url.pathname} because the
response sets a cookie.</code>);
return next();
},
// ...
};
</code></pre></p>
<p>Astro's built-in <code>memoryCache()</code> provider now uses this
logger for the warnings it emits when it skips caching a response that
sets cookies, and when a background revalidation fails.</p>
</li>
</ul>
<h3>Patch Changes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/withastro/astro/commit/92f23cc121d3a1a03c6fb901a519309662b333de"><code>92f23cc</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17900">#17900</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/03896405717471f7d6ff54986ed6beaec0cac94f"><code>0389640</code></a>
fix: dont use internal paths (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17899">#17899</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/f800de13ffab9542f8d1bd13a8f4481c5f5c083a"><code>f800de1</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17881">#17881</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/1e0b7e6023128ad25a79ac125f6d33e5936a3def"><code>1e0b7e6</code></a>
[ci] format</li>
<li><a
href="https://github.com/withastro/astro/commit/4671a5c2d215545d8d7f763381ec1084dce9b7d1"><code>4671a5c</code></a>
chore(deps): update react to v19 in <code>0-css</code> fixture (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17888">#17888</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/31b886b75259e5a8d50dc750e587ee9bc97ee099"><code>31b886b</code></a>
Update bundled Zod to 4.5 (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17862">#17862</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/e747cba07fcd2b9e7fb03c02ed42abfe2079daa2"><code>e747cba</code></a>
Skip unsupported Vary responses in the memory cache (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17886">#17886</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/fb5b0d0a952b50c48908a262a702f43dcf42f9e7"><code>fb5b0d0</code></a>
[ci] format</li>
<li><a
href="https://github.com/withastro/astro/commit/ce7c91f77dbd7be03c04bc13f87af9d01fef6cef"><code>ce7c91f</code></a>
Add <code>--ignore-lock</code> flag support to <code>astro
preview</code> (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17767">#17767</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/c0b65811dfa0dafa1aa04b7d6d67fd09250ff8c1"><code>c0b6581</code></a>
feat: use logger instead of console where possible (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17818">#17818</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/withastro/astro/commits/astro@7.3.1/packages/astro">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-09-13 10:23:12 -07:00
Maximilian Roos cff085edf9 docs: fix misplaced copy buttons by dropping terminal frames (#4090)
On every shell code block the copy button sat 40px below the top of the
code: on the second line of multi-line blocks, and hanging below
one-line blocks such as `wt merge --no-ff` on /merge/. Shell blocks were
Expressive Code terminal frames, and the terminal plugin deleted their
title bar, but Expressive Code still offsets the copy button by that
bar's height.

## Changes

- `docs/astro.config.mjs` sets `defaultProps: { frame: 'code' }`, so no
block is a terminal frame and there is no title bar to remove. The
plugin's header-removal hook and its `props.frame = 'terminal'` go, and
so does the `wt-commands-only` class: mobile wrapping in `custom.css`
now keys on whether a block contains captured output
(`:has(.wt-output)`).
- Blocks with several commands had a whole-block copy button and
per-line buttons in the same corner, handed over on hover by opacity. A
hidden button still takes clicks, so clicking a line's button could copy
the whole block, and on touch screens one button covered another. Most
of these blocks are lists of alternatives, so they now get only per-line
buttons, and the hover rule is deleted.
- Those blocks wrap (Expressive Code's `wrap` prop) unless they carry
captured output, since a per-line button sits at the end of its line and
scrolled out of view on the long `jq` examples on /list/. A per-line
button is no taller than its line, so buttons on adjacent lines no
longer overlap.
- The two multi-command blocks that only work run in order, the FAQ's
stash recipe and the `Equivalent to:` block under `wt step diff`'s **How
it works** (in `src/cli/step.rs`), are now `bash` fences, the form
`docs/CLAUDE.md` prescribes for a copyable recipe, so they keep one copy
button for the whole recipe. Terminal `--help` renders `console` and
`bash` fences alike (it strips `$ `), and the help snapshots are
unchanged.

## Side effect

Expressive Code strips comment lines only from what terminal frames
copy. The eight `bash` and `powershell` blocks with comments on
/shell-integration/ now copy them, as console blocks already did. Pasted
into zsh without `interactivecomments`, each comment line prints
`command not found: #`; the commands still run.

## Tests

A browser test checks every copy button on every page, at 393px with
touch and at 1376px, for three things: it sits on the line it copies, it
is inside the visible code, and it doesn't overlap another button. These
checks fail against worktrunk.dev and against a build without the wrap
and the height cap. The built-site and plugin tests now expect per-line
payloads and no block payload on multi-command blocks, and a plugin test
pins that blocks carrying output never wrap.

> _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:00:25 -07:00
DDL 48626f2df9 fix(merge): honor git worktree lock when cleaning up (#4073)
## Summary
- `wt merge` built a `RemovalPlan` by hand and skipped the lock check
`wt remove` already had, so a successful merge could rename a `git
worktree lock`'d feature worktree into trash and report success.
- After merge, a locked worktree is now kept (`Worktree preserved
(locked)`) the same way a primary worktree is kept. The shared staging
path also refuses a lock, including under `--force`, so every removal
caller is covered.

## Test plan
- [x] `cargo test --lib -- git::remove::tests` (includes
`stage_refuses_locked_worktree` and
`stage_refuses_locked_worktree_even_with_force`)
- [x] `cargo test --test integration
test_merge_preserves_locked_worktree`
- [x] `cargo test --test integration test_merge_fast_forward`
- [x] `cargo test --test integration test_remove_locked` (existing lock
tests still pass)
- [ ] `git worktree lock .` in a feature worktree, then `wt merge` —
merge succeeds, worktree stays, message names the lock reason


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-09-12 19:25:46 -07:00
Maximilian Roos 10315349f4 Scope docs heading anchors under each subcommand section (#4079)
Command pages on the site append every subcommand's help, so headings
such as "Examples", "Options", and "Command reference" repeat down
`/step/` and `/config/`. The heading-id plugin numbered the repeats by
position (`/step/#examples-3`, `/step/#hooks-1`,
`/step/#command-reference-7`), so an anchor pointed somewhere else once
a same-named heading was added above it.

`docs/src/plugins/stable-heading-ids.mjs` now scopes ids below each
subcommand's H2 (a heading starting `wt `) by that section's id:
"Examples" under `wt step push` is `/step/#wt-step-push--examples`.
Page-level headings and the subcommand headings keep their ids, so the
table of contents and links like `/step/#wt-step-copy-ignored` are
unchanged. A slug never contains `--`, so a scoped id can't equal
another heading's slug — a "Cache" heading under `wt config state` stays
distinct from the `wt config state cache` section.

With the site handling it, the CLI help keeps unqualified headings.
#4000 renamed six `wt config` subcommand headings to "Approval
examples", "Alias examples", and so on to avoid the numbering; they're
back to "Examples", in `--help` as well.

Every anchor inside a subcommand section changes once:
`/step/#min-age-guard` is now `/step/#wt-step-prune--min-age-guard`. The
two internal links that pointed at such anchors — the FAQ's
copy-on-write link and `wt config approvals`' "Reading approval state" —
are updated, and `test:site` checks every internal fragment. External
links to the old anchors land at the top of the page.

The search-index plugin (`pagefind-command-references.mjs`) now imports
the subcommand-heading check from the heading-id plugin rather than
keeping its own copy.

> _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_01HUmx2Jd5mTK5TznqGDmLGp

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 16:57:33 -07:00
Worktrunk Bot ee52834701 docs(demos): record demos with the site's current palette (#4069)
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>
2026-09-12 15:42:32 -07:00
Maximilian Roos f194d5a13c Age branches by reflog entry in wt step prune; fix four doc claims (#4077)
`wt step prune` could delete a branch created a minute earlier. Its
min-age guard ages a branch that has no worktree by its oldest reflog
entry — the help text and the function's docstring both say so — but the
code read `git reflog show --format=%ct`, which is the committer date of
the commit the entry points at. `git branch foo main` on a default
branch whose last commit is more than a day old therefore looked days
old, and the next `wt step prune` removed it.

From `test_prune_orphan_branch_min_age`, a branch made 30 minutes before
the run, pointing at a month-old commit, with the default
`--min-age=1d`:

```console
$ wt step prune --yes          # before
✓ Removed branch orphan-integrated (same commit as main, _)
✓ Pruned 1 branch

$ wt step prune --yes          # after
○ Skipped orphan-integrated (younger than 1d)
```

The age now comes from the entry's own timestamp: `git reflog show
--date=unix --format=%gd` renders each selector as `<name>@{<epoch>}`.
`test_prune_orphan_branch_min_age` couldn't tell the two apart, because
the test harness gives the commit and the branch creation the same date;
its commit is now a month older than the branch, and the old code fails
it.

A branch with no reflog at all still counts as old enough, as before.
That includes a branch created only from a bare repository's own
directory, since a bare repository defaults `core.logAllRefUpdates` to
false; the docstring now says so. A branch created from inside a linked
worktree has a reflog.

Found while reviewing #4000, along with four doc claims corrected in a
separate commit:

- The hook page said `{{ vars.thing | upper }}` previews as `{{
VARS.THING }}`; `wt hook show --expanded` prints `'{{ VARS.THING }}'`,
shell-quoted like any other value.
- The agent-integration table checked `/wt-switch-create` for Codex and
Gemini while its footnote said it does nothing there; the mark moves to
the row label.
- The skill's agent-handoff instruction, followed literally for
OpenCode, dropped `run`; it now points at the tips section's note on
where a subcommand goes.
- `take_global_options`' docstring counted 11 Global Options blocks on
`wt config`; there were 12.

Reviewable files: `src/commands/step/prune.rs`,
`tests/integration_tests/step_prune.rs`, `src/cli/mod.rs` (one
sentence), `docs/src/content/docs/claude-code.md`,
`skills/worktrunk/SKILL.md`, `src/help.rs`, `CHANGELOG.md`. The skill
and plugin mirrors and the agent-skills digest are regenerated.

> _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_01HUmx2Jd5mTK5TznqGDmLGp

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 15:07:02 -07:00
Maximilian Roos dd389339f0 docs: sit the file-name tab flush with its code block (#4078)
Code blocks titled with a file name (for example `.config/wt.toml` above
a TOML excerpt) showed an awkward gap to the left of the name tab.
Expressive Code's `.header::before` draws a 1px outline around the whole
strip above the code, which our overrides left in place, and the tab was
inset 0.6rem from the frame's left edge, so an empty outlined box sat
beside it. The outline is removed and the tab now sits flush with the
frame's left edge, keeping its own background and rounded corners; the
copper rule still starts where the code does.

The browser test that already checks the label sits directly on its code
now also checks the label and the code share a left edge; with the old
inset restored, it fails.

> _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_016NGjseUKXkyL8qHLhaXsnt

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 15:06:02 -07:00
Maximilian Roos 4f26e9a1c1 docs: keep code frames in the content column and dev-server styling current (#4076)
## Code frames leave the content column on wide screens

#4000 stretched every terminal frame to the full content panel at 72rem
and up, to give `wt list --full` room. Once the panel is wider than the
62rem column, Starlight offsets the column inside it, so shell snippets
moved left of the prose they belong to and spanned past it on the right,
while TOML frames stayed in the column. On
https://worktrunk.dev/tips-patterns/ at 2560px, prose starts at x=760
and shell frames at x=248, 1504px wide.

#4039 had already made terminal output fit the column. With the stretch
disabled, no output-bearing frame on any of the 15 pages overflows;
three long one-line commands on `/config/`, `/list/` and `/step/`
scroll, as long TOML lines already do. So the rule is removed rather
than narrowed.

The desktop browser test ran only at 1152px and 1376px, where the panel
and the column are the same width and the rule changed nothing. It now
also runs at 1920px and asserts every code frame stays within the
column; with the old rule restored, it fails on the homepage.

## The dev server loses code styling after a merge

Astro caches rendered Markdown with the Expressive Code stylesheet URL
baked in. That URL's hash comes from the code-rendering config and
plugins, which Astro's content-cache key doesn't cover. A merge that
changes `astro.config.mjs` and the terminal plugin under a running dev
server makes Astro restart it in place: the server serves the new
stylesheet while content keeps rendering with the old renderer, so every
page links a stylesheet that returns 404. Restarting the process doesn't
recover, because the cache survives it.

- `docs/package.json`: `dev` passes `--force`, as `build` already does,
so a restart re-renders.
- `.config/wt.toml`: the docs hook sets `ASTRO_DEV_BACKGROUND=1`. When
`astro dev` detects an AI agent, it respawns itself detached, which
drops `--force` and escapes `wt step tether`; three worktrees currently
run their dev servers that way.
- `docs/CLAUDE.md`: explains the cache and says to restart with `wt hook
post-start docs` after `astro.config.mjs` changes, since the in-place
restart is Astro's to fix.

> _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_016NGjseUKXkyL8qHLhaXsnt

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 14:14:09 -07:00
Worktrunk Bot 240fd0166e docs(demos): point the theme-variant section at the real asset paths 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>
2026-09-12 13:07:52 -07:00
Maximilian Roos 89326f14af Publish shell integration, add a footer, dedupe global options (#4000)
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-reference` and `#command-reference-2`. `take_global_options`
cuts each reference at the heading as it is built, keeping only the
first; one `kept` flag threads through the subdoc expansion and the page
streams out rather than accumulating. Terminal `--help` renders 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 in `src/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-N` anchors, 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 switch` didn't cd" was to install the
Claude Code plugin. It is now `/shell-integration/`, offered first, with
the plugin as the second route. The `llms.txt` listing serves every page
as `/<slug>.md` from 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**

- A site footer carries the version (read from `Cargo.toml` at 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's `Footer` is wrapped rather than replaced.
- `wt list --full` renders 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.
- The `wt-command-reference` frames 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.
- The four command demos and the two hand-written figures get captions;
the 2.33 MB homepage GIF below the fold loads lazily.

**Sidebar order is pinned**

`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 exactly what the survey found:
`remove` listed before `merge`, Agent integration ahead of
lower-numbered pages. The test is written, so the sidebar and the
`llms.txt` ordering derived from the frontmatter can't drift apart
again.

<details>
<summary>Guide corrections</summary>

- Tips & patterns was 26 flat H2 recipes in no order, all 26 in the
sidebar. They group under five H2s — setup and layout, aliases and
hooks, per-worktree services, working with agents, status/commits/logs —
with each recipe demoted to H3. Anchors are level-independent, so
existing `/tips-patterns/#…` fragments still resolve.
- `-x 'opencode run'` has been broken since 0.75.0 made `-x` a literal
program: it is `-x opencode -- run '<task>'`.
- The branch-summary preview moved from tab 5 to 6 when the unified-diff
tab landed; the recipe names the `summary` tab instead of a number.
- The Caddy recipe claimed `feature-auth` hashes to port 16460 — that is
`fix-auth`'s port. It is 18283.
- `_` in `wt list` is same-commit *and clean*; the
same-commit-with-changes glyph is `–`, which is not safe to delete.
- `wt step prune` removes 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 -v` prints fifteen variables; the example showed two
under a lead calling them "the available template variables".
- A filter applied to `{{ vars.<key> }}` acts on the placeholder the
preview substitutes, so `{{ vars.port | default('8080') }}` previews as
`{{ vars.port }}`, filter gone.
- The `.git/wt/cache/` table was missing `picker-preview`, and `wt
config state clear` prompts unless `--yes`.
- `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 resolving nowhere. Nothing generated it, so
deleting the symlink is the whole fix.
- One home per topic: agent handoffs stay in tips-patterns, activity
markers in `claude-code.md`, alias-template deferral in `extending.md`,
and the `codename` filter's two `worktree-path` recipes 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.
- The FAQ linked `/worktrunk/#install`, the `noindex` compatibility
route; the plugin hook shim's Windows Terminal hint pointed there too.
Both use `/#install`, where the new sidebar Install entry goes.
- 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" per the project's own
terminology rule.

</details>

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 new `shell-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.

> _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_01XAUYWFN9d9oh6jyoQiouHb

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 12:44:47 -07:00
Worktrunk Bot f6b699a208 docs(hook): say post-merge runs in the destination worktree regardless of removal (#4071)
## Problem

`wt hook --help` lists the three cases where a hook's `cwd` differs from
`worktree_path`, and the `post-merge` entry qualifies itself with "with
removal":

> - `post-merge` with removal: the active worktree is gone, so the hook
runs in the target worktree

That condition does not exist. `post-merge` is anchored on the merge
destination unconditionally — `approve_merge_plan` adds it at
`destination_path` outside the `will_remove` branch, and
`finish_after_merge` registers it there whether or not the feature
worktree was removed. The stated reason is wrong too: the hook runs in
the destination because it is *about* the destination, not because the
source worktree happens to be gone.

The qualifier is also the only place on the page that says this. The
hook-type table ("Runs in the target branch worktree if it exists,
otherwise the primary worktree") and the merge-pipeline paragraph
("post-merge, post-switch and post-remove in the destination") both
state the unconditional behavior, so the page contradicts itself — and
the bullet is the version a reader lands on when they are specifically
asking which tree the hook sees.

The failure it invites is concrete: under `--no-remove` both worktrees
are on disk, and a hook written from this bullet — a deploy, a build, an
external freshness check — inspects the pre-merge feature worktree while
reporting on the integrated result. #4070 asks exactly this question.

## Solution

Replace the qualifier with the behavior the code implements, naming the
`--no-remove` case explicitly since that is the one the old wording got
wrong:

> - `post-merge`: the hook runs in the target branch's worktree (the
primary worktree if the target has none), including under `--no-remove`,
where the merged worktree `worktree_path` names is still on disk

Source is `after_long_help` in `src/cli/mod.rs`; the three generated
mirrors are regenerated by `test_docs_are_in_sync`. No `--help` snapshot
captures this text.

## Testing

`test_merge_post_merge_runs_in_destination_with_no_remove` merges a
feature worktree with `--no-remove` and a `post-merge` hook that writes
a marker relative to its cwd, then asserts the marker lands in the
destination worktree and *not* in the preserved feature worktree. It
passes against unchanged code — the behavior was already correct, and
the test pins it so the documented contract cannot drift back.

`integration_tests::merge::` (153 tests), `integration_tests::help::`
(59), `integration_tests::readme_sync::` (18), `cargo fmt --check`, and
`cargo clippy --all-targets` all pass locally.

Closes #4070 — automated triage

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-09-12 08:42:40 -07:00
dependabot[bot] b8510a5829 chore: bump svgo from 4.0.2 to 4.1.0 in /docs (#4045)
Bumps [svgo](https://github.com/svg/svgo) from 4.0.2 to 4.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/svg/svgo/releases">svgo's
releases</a>.</em></p>
<blockquote>
<h2>v4.1.0</h2>
<p>This minor release upgrades the SAX parser and introduces stricter
XML validation. It also includes important security hardening for
<code>removeScripts</code>, dependency updates, and improvements to the
test and regression infrastructure.</p>
<h3>Support SVGO</h3>
<p>If SVGO is valuable to you or your organization, please consider <a
href="https://opencollective.com/svgo">supporting the project on
OpenCollective</a>. Your sponsorship helps fund ongoing maintenance and
security work.</p>
<h3>Stricter XML validation</h3>
<p>SVGO now uses <a
href="https://www.npmjs.com/package/sax"><code>sax</code> 1.6.1</a>,
upgraded from 1.5.0 (<a
href="https://redirect.github.com/svg/svgo/pull/2257">#2257</a>).</p>
<p>The new parser version validates numeric character references against
the ranges permitted by XML. Invalid references are now rejected in both
text and attributes, including:</p>
<ul>
<li>disallowed control characters such as
<code>&amp;[#1](https://github.com/svg/svgo/issues/1);</code>,
<code>&amp;#xB;</code>, and <code>&amp;#x1F;</code>;</li>
<li>UTF-16 surrogate code points such as <code>&amp;#xD800;</code>;</li>
<li>invalid XML code points such as <code>&amp;#xFFFF;</code>.</li>
</ul>
<p>Valid boundary values—including <code>U+0020</code>,
<code>U+D7FF</code>, <code>U+E000</code>, <code>U+FFFD</code>, and
characters through <code>U+10FFFF</code>—remain supported.</p>
<p>Parser failures are consistently exposed as
<code>SvgoParserError</code> errors with an <code>Invalid character
entity</code> reason.</p>
<p>This is an intentional behavior change: malformed SVGs that were
previously accepted may now produce a parser error, while valid XML
documents are unaffected.</p>
<h3>Security</h3>
<p>The <a
href="https://svgo.dev/docs/plugins/removeScripts/"><code>removeScripts</code></a>
plugin has been hardened against several script-execution bypasses:</p>
<ul>
<li>Filters executable <code>data:</code> URLs containing HTML, XHTML,
or SVG documents while preserving inert data such as PNG images, and
filters legacy <code>vbscript:</code> URLs (<a
href="https://redirect.github.com/svg/svgo/pull/2263">#2263</a>).</li>
<li>Sanitizes content inside SVG <code>&lt;foreignObject&gt;</code>
elements by removing HTML event-handler attributes, <code>srcdoc</code>,
and executable URLs from <code>action</code>, <code>data</code>,
<code>formaction</code>, <code>href</code>, and <code>src</code>, while
preserving non-executable HTML and visual content (<a
href="https://redirect.github.com/svg/svgo/pull/2264">#2264</a>).</li>
<li>Recognizes namespace-prefixed SVG <code>&lt;a&gt;</code> elements
and removes ASCII tabs and newlines before checking URL schemes,
preventing values such as
<code>java&amp;[#9](https://github.com/svg/svgo/issues/9);script:</code>
from bypassing detection while preserving elements in unrelated custom
namespaces (<a
href="https://redirect.github.com/svg/svgo/pull/2268">#2268</a>).</li>
</ul>
<p>These changes address:</p>
<ul>
<li><a
href="https://github.com/svg/svgo/security/advisories/GHSA-4vpr-x523-8j87">GHSA-4vpr-x523-8j87</a></li>
<li><a
href="https://github.com/svg/svgo/security/advisories/GHSA-w27v-7q3p-w38r">GHSA-w27v-7q3p-w38r</a></li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>Upgraded <code>css-select</code> to v6 and <code>css-what</code> to
v7, and updated SVGO's custom selector adapter for
<code>css-select</code> v6 (<a
href="https://redirect.github.com/svg/svgo/pull/2244">#2244</a>).</li>
</ul>
<h3>Project maintenance</h3>
<p><a href="https://github.com/TrySound"><code>@​TrySound</code></a> is
back as an active SVGO maintainer.</p>
<p>Many thanks to <a
href="https://github.com/KTibow"><code>@​KTibow</code></a>, <a
href="https://github.com/SethFalco"><code>@​SethFalco</code></a>, and <a
href="https://github.com/XhmikosR"><code>@​XhmikosR</code></a> for
maintaining and improving SVGO over the past several years.</p>
<p><strong>Full Changelog:</strong> <a
href="https://github.com/svg/svgo/compare/v4.0.2...v4.1.0">https://github.com/svg/svgo/compare/v4.0.2...v4.1.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/svg/svgo/commit/5765cbe4e0a930dca648c6b81d335b1522cd7375"><code>5765cbe</code></a>
chore: prepare v4.1.0 release (<a
href="https://redirect.github.com/svg/svgo/issues/2275">#2275</a>)</li>
<li><a
href="https://github.com/svg/svgo/commit/3db3ef33e409a0bc0fdaf255e46c908b00e93bc2"><code>3db3ef3</code></a>
fix(removeScripts): handle anchor URL bypasses (<a
href="https://redirect.github.com/svg/svgo/issues/2268">#2268</a>)</li>
<li><a
href="https://github.com/svg/svgo/commit/4e9b9aed2a4607cf28025871151421d5bfe86484"><code>4e9b9ae</code></a>
chore: cache regression screenshots (<a
href="https://redirect.github.com/svg/svgo/issues/2267">#2267</a>)</li>
<li><a
href="https://github.com/svg/svgo/commit/d55270ce17b99ebfe16e4fad028bd162187205a1"><code>d55270c</code></a>
chore(regression): migrate comparison workers to Tinypool (<a
href="https://redirect.github.com/svg/svgo/issues/2266">#2266</a>)</li>
<li><a
href="https://github.com/svg/svgo/commit/fd51e474a300417d9361d9302d596b1763146327"><code>fd51e47</code></a>
fix(removeScripts): sanitize foreignObject content (<a
href="https://redirect.github.com/svg/svgo/issues/2264">#2264</a>)</li>
<li><a
href="https://github.com/svg/svgo/commit/dcaf957c6eb34832844de11ef2792e3e0e8db5dd"><code>dcaf957</code></a>
chore: optimize fixtures in a bounded worker pool (<a
href="https://redirect.github.com/svg/svgo/issues/2265">#2265</a>)</li>
<li><a
href="https://github.com/svg/svgo/commit/a3542937d9c85debab04b1ff75207768caf8a058"><code>a354293</code></a>
fix(removeScripts): filter executable data URLs (<a
href="https://redirect.github.com/svg/svgo/issues/2263">#2263</a>)</li>
<li><a
href="https://github.com/svg/svgo/commit/4e0d2ac5e7abd5f12d650d3e4b4d2500d4944cc5"><code>4e0d2ac</code></a>
ci(typecheck): return typechecking on CI</li>
<li><a
href="https://github.com/svg/svgo/commit/0b97fedd00a3001f1da616f61578c2c12e6cde1f"><code>0b97fed</code></a>
test(typescript): drop tsd for vitest type testing API</li>
<li><a
href="https://github.com/svg/svgo/commit/f6e8ae1afb012c2b47675622728d76b9bfd734f5"><code>f6e8ae1</code></a>
chore(pnpm): drop package.json#pnpm.onlyBuiltDependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/svg/svgo/compare/v4.0.2...v4.1.0">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for svgo since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=svgo&package-manager=npm_and_yarn&previous-version=4.0.2&new-version=4.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/max-sixty/worktrunk/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-08 23:37:50 -07:00
dependabot[bot] 6ebe109b58 chore: bump js-yaml from 4.3.1 to 4.3.2 in /docs (#4046)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.1 to 4.3.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nodeca/js-yaml/blob/4.3.2/CHANGELOG.md">js-yaml's
changelog</a>.</em></p>
<blockquote>
<h2>4.3.2 - 2026-08-26</h2>
<h3>Changed</h3>
<ul>
<li>[backport] Hard-limit merge sequence size to 100.</li>
</ul>
<h3>Security</h3>
<ul>
<li>[backport] Count empty mappings in merge sequences toward
<code>maxTotalMergeKeys</code>
to limit CPU usage, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/797">#797</a>.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/nodeca/js-yaml/commit/79ca68d90f333fbe6d9e42827527e62636200191"><code>79ca68d</code></a>
4.3.2 released</li>
<li><a
href="https://github.com/nodeca/js-yaml/commit/d90b6612a5a84385bdcb556c44578eac76dc0f6b"><code>d90b661</code></a>
Backport merge limits from v5.4.1</li>
<li>See full diff in <a
href="https://github.com/nodeca/js-yaml/compare/4.3.1...4.3.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=js-yaml&package-manager=npm_and_yarn&previous-version=4.3.1&new-version=4.3.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/max-sixty/worktrunk/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-08 23:37:47 -07:00
Maximilian Roos 28ebf24505 Drop post-commit when the merge removed the worktree it runs in (#4049)
## Problem

`wt merge` prints `◎ Running post-commit: …` and then spawns that
pipeline into a path the removal has already emptied. `post-commit` is
the only hook in the merge's background batch anchored on the feature
worktree, and `HookAnnouncer` flushes after `finish_after_merge` has
removed that worktree.

`run_pipeline` calls `Repository::at` on what's left, and git discovery
walks up from it:

- **Worktree outside the repository** (the default `../{{ repo }}.{{
branch }}`): nothing above it is a repository, so the runner logs
`failed to open repository for pipeline` to
`.git/wt/logs/<branch>/<source>/post-commit/runner.log`, which nothing
reads back. The hook doesn't run.
- **Worktree nested inside the repository** (`{{ repo_path
}}/.worktrees/{{ branch | sanitize }}`, one of the config page's own
examples): discovery resolves to the **primary** worktree, so the hook's
commands — arbitrary project code from `.config/wt.toml` — run against a
checkout the user never chose.

[#4026](https://github.com/max-sixty/worktrunk/pull/4026) documented
both outcomes rather than fixing them.

## Solution

The removal is the only thing that knows the anchor is gone, so it says
so: `spawn_hooks_after_remove` calls
`HookAnnouncer::mark_worktree_removed(ctx.worktree_path)`, and the flush
drops any pending pipeline anchored on a marked path. The survivors get
the usual `Running …` line; each dropped pipeline gets its own warning.

```
▲ Skipped post-commit: mark (user) — worktree removed @ ~/code/myproject/.worktrees/feature
↳ To run commands in a worktree before it is removed, use pre-remove
◎ Running post-remove: cleanup (user); post-switch: notify (user); post-merge: sync (user) @ ~/code/myproject
```

Reading the fact from the removal rather than probing the filesystem is
what makes the skip unconditional. The two removal paths leave the
anchor in different states: the fast path renames the worktree into
`.git/wt/trash/` before the flush, but where that rename fails —
cross-filesystem, permissions, Windows file locks —
`BackgroundFallbackMode::Detached` spawns `git worktree remove` and the
anchor is still on disk, intact, when the flush runs. A "does this path
still hold git data" probe answers `true` there and spawns the hook into
a worktree being deleted underneath it. The mark doesn't depend on which
path ran.

Deciding at the flush keeps every case where the removal doesn't happen:
`--no-remove`, merging on the target branch, merging from the primary
worktree, and a removal blocked by a dirty worktree all leave the anchor
in place, and post-commit runs there as before — as it does on `wt step
commit` and `wt step squash`. Only `wt merge` can reach the drop: every
other background hook anchors on a worktree its command keeps
(`post-merge`, `post-switch` and `post-remove` all render against the
destination).

There is no earlier moment to spawn it. Between the commit and the
removal the worktree is rebased and runs `pre-merge`, so a background
pipeline started there would race both.

## Testing

`test_merge_post_commit_runs_only_when_its_worktree_survives` in
`tests/integration_tests/user_hooks.rs` is parameterized over removal.
The worktree is nested inside the repository so that a regression
*executes* rather than merely fails: the hook writes `git rev-parse
--show-toplevel` to a marker in the primary worktree.

- `removed` — asserts the marker never appears, and reports the resolved
toplevel if it does. Dropping the partition writes the primary
worktree's path there.
- `kept` (`--no-remove`) — the control: same hook, same marker path,
worktree survives, and the marker names the feature worktree.

The mark is unconditional in `spawn_hooks_after_remove`, which every
removal path reaches after its removal, so there is no
fast-path/fallback branch left for a test to distinguish — the first
revision of this PR put a `holds_git_data` probe there instead, and
`test (windows)` caught the fallback taking the other answer.

The two merge announce snapshots now show the `Skipped` lines and a
`Running` line without `post-commit`; both tests are renamed to the
three hook types they still combine. `cargo run -- hook pre-merge --yes`
passes.

<details>
<summary>Manual repro</summary>

A scratch repo whose `post-commit` hook is `git rev-parse
--show-toplevel > {{ repo_path }}/toplevel.txt`, a feature worktree with
one commit and one uncommitted file, then `wt merge main --yes`.

Without the drop, the nested layout runs the hook and the marker names
the **primary** worktree, while the hook was anchored on
`.worktrees/feat`:

```
/private/var/folders/.../tmp.6MtN6Ufppv/repo
```

With it:

| Layout | Flags | Result |
|--------|-------|--------|
| nested (`{{ repo_path }}/.worktrees/{{ branch \| sanitize }}`) |
default (squash) | `▲ Skipped post-commit`, no marker |
| nested | `--no-squash` | `▲ Skipped post-commit`, no marker |
| external (`../{{ repo }}.{{ branch }}`) | default (squash) | `▲
Skipped post-commit`, no marker |
| external | `--no-remove` | `◎ Running post-commit`, marker names the
feature worktree |

</details>

> _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_01QG3SjDDtiVkvmk3eZDc6vu

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 18:22:27 -07:00
Maximilian Roos 9f28b1267f Make wt config show a real diagnostic (#3999)
`wt config show` could identify broken configuration while still exiting
0, which made it unsuitable as a scripted health check. This makes its
exit status reliable without truncating the human report or corrupting
JSON output.

## What changed

- `wt config show` renders every text section before returning non-zero
for unreadable or invalid config sources, invalid list-column settings,
or an invalid approvals file. Unknown and deprecated keys remain
warnings and exit 0.
- JSON output stays parseable on failure, preserves merged file layers
when runtime overrides are invalid, and reports an unreadable or invalid
source as `null`.
- Project config loaded from the Git object store is reported as that
source rather than as a missing file. Pending project commands appear in
a compact `APPROVALS` section.
- `wt config update --output` warns when it omits deprecated
`approved-commands`, and refuses to overwrite the source only in that
lossy case.

The scope is intentionally limited: there is no derived `EFFECTIVE`
section, no unconditional empty system-config section, and no broad
rewrite of the configuration guide. The command help adds only the
changed behavior and is synchronized to the generated docs and skill
reference.

Validation: the pre-merge hook and coverage suite passed all 4,826
tests, Clippy, formatting, snapshots, doctests, and documentation
checks; the Astro production build passes.

> _This was written by Codex on behalf of max-sixty_

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-09-07 19:19:47 -07:00
Maximilian Roos 134f9eb471 Default wt list JSON output to schema 2 (#4038)
`wt list --format=json` and `wt list statusline --format=json` now emit
the schema 2 envelope when `[list] json-schema` is unset. Explicit
`json-schema = 1` keeps the legacy bare array, while invalid values warn
and fall back to schema 2.

This removes the completed pending-default migration and its `wt config
update` prompt, and refreshes help, docs, config examples, and
snapshots.

Tests:

- `cargo run -- hook pre-merge --yes`
- `npm --prefix docs run build`

> _This was written by Codex on behalf of max-sixty_
2026-09-07 17:16:30 -07:00
Maximilian Roos a0ad432a04 docs: fit terminal examples on desktop (#4039)
Widen the documentation content rail and defer the right-hand table of
contents until the viewport can accommodate it, so terminal examples use
the available desktop space.

Capture every documentation-backed `wt list` example at the shared
98-column width. Full-mode examples now exercise Worktrunk's real column
hiding and truncation instead of embedding 134-column output, and the
activity example no longer inherits the test suite's 500-column default.

Add a browser regression that crawls every public route at both sides of
the responsive breakpoint and rejects horizontal overflow in
output-bearing terminal blocks.

> _This was written by Codex on behalf of max-sixty_
2026-09-07 16:51:23 -07:00
Maximilian Roos 981a207e26 Fix wt list column sizing, alignment, and headers (#3998)
Seven fixes to `wt list` — three to how the table lays out, one to what
detached and prunable worktrees show, and three to the page that
documents it.

**The table**

A single 58-character branch name sized the Branch column for every row:
at 60 columns the table degenerated into a branch list with nine columns
hidden, and at 200 it still lost Message. Branch now sizes to
`min(longest, 32)` and elides with `…`, the way Message already does.
`--format=json` still carries the whole name.

`Remote⇅` in a repo with no remote held its blank seven columns open
while Message, Commit, Age and Path — each with something to say on
every row — were dropped for want of them. The empty-column penalty now
exceeds every base priority, and the allocation loop stops admitting
empty columns once a populated one has failed to fit. `Remote⇅` also
learns it is empty before any task reports: a repo with no remote has no
branch that can track one, read O(1) off the bulk config map.

Alignment now follows the value type consistently: text and reference
columns align left; the scalar `Age` column and its header align right;
split diff fields use two right-aligned halves with centered headers and
centered whole-field states such as loading or in-sync markers. Rows no
longer carry trailing padding, and the hidden-column footer wraps at the
terminal width. The progressive renderer documents and asserts the
corresponding terminal invariant: ordinary rows and the loading footer
occupy one physical row, while only the final summary may wrap.

The familiar `main↕` and `main…±` labels remain fixed across
repositories. This avoids adding default-branch-name plumbing or a `^`
fallback without changing the meaning of either column.

**Detached and prunable rows**

A detached worktree rendered as a bare hash under Branch, wearing `⚑` on
loan from `branch_worktree_mismatch` — which it was only flagged with
because a worktree with no branch has no branch-implied path to sit at.
It gets `⊘`, and stops claiming to be off-template, so `⚑` again means
only what it says. A prunable worktree showed four `·` loading glyphs
that never resolved: its directory is gone, so no task is ever spawned
for it and no cell is coming. Those cells render blank, leaving `⊟` in
Status as the row's whole story.

**The page**

<details>
<summary>Documentation corrections</summary>

- The summary footer counted hidden columns ("3 columns hidden") without
saying which, and the page never explained that the table drops columns
to fit. The footer now names them ("hidden: Path, Commit, Message"), and
a paragraph under `## Columns` covers the drop order, `[list] columns`,
and that `--format=json` shows everything.
- The JSON section documented deprecated schema 1 at 159 lines and nine
sub-tables while schema 2 — what a future release makes the default —
got 88 lines and one table. Schema 2 now carries the full reference
(envelope, item fields, a sub-table per object, the three value
vocabularies) and the worked `jq` recipes. Schema 1 keeps a paragraph,
the deprecation pointer, and a schema-1 → schema-2 mapping table
covering every field it documented.
- The CI-cache line pointed at `wt config state`, a group with no
default action; it names `wt config state cache`.
- `wt list statusline --format=json` was documented as "a one-entry
array in the `wt list --format=json` schema", which holds only under
schema 1. It emits the current schema — an array under 1, the envelope
under 2 — and the line now records why that surface stays silent about
an unset `[list] json-schema` while plain `wt list` nags: a prompt
consumer can't act on a warning drawn over its own line.
- The Status-symbols section documented every subcolumn except the
branch marker. Both JSON schemas gain an additive `marker` field, so the
value is readable without parsing it back out of `symbols`.

</details>

UX survey items: `#24`, `#34`, `#58`, `#89`, `#90`, `#92`, `#93`.

Reviewable files: `src/commands/list/**` (`layout.rs`, `columns.rs`,
`render.rs`, `progressive_table.rs`, `model/item.rs`, `mod.rs`,
`json_v2.rs`), the list section of `src/cli/mod.rs`,
`src/styling/line.rs`, and
`.claude/skills/writing-user-outputs/SKILL.md`. Generated mirrors and
snapshots are regenerated.

Original analysis:
https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb

> _This was written by Codex on behalf of max-sixty_

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 16:09:00 -07:00
Worktrunk Bot 5a552b29e7 docs(faq): list the agent-integration files worktrunk writes outside its own config (#4036)
`CLAUDE.md` names the FAQ's [What files does Worktrunk
create?](https://worktrunk.dev/faq/#what-files-does-worktrunk-create)
and [What can Worktrunk
delete?](https://worktrunk.dev/faq/#what-can-worktrunk-delete) as the
full inventory of worktrunk's file surface, and tells reviewers to check
new code against them. Neither section has ever covered `wt config
plugins <agent> install`, so the inventory has been incomplete since the
OpenCode plugin landed, and adding the Pi hook this week widened the
gap.

The sections now carry an "Agent integrations" entry listing the three
files worktrunk writes into another tool's config tree, and the "does
NOT create" bullet — which read "No files outside `.git/`, config
directories, worktree directories, or the system temporary directory" —
now says which agent paths are the exception and that they are only
touched when you run the install command. `wt config plugins
{opencode,pi} uninstall` is added to the deletion list.

Nightly sweep finding; no linked issue.

<details><summary>What each claim is grounded in</summary>

Files worktrunk writes directly:

- `<opencode config dir>/plugins/worktrunk.ts` — `opencode_plugins_dir`
in
[`src/commands/config/opencode.rs`](https://github.com/max-sixty/worktrunk/blob/74ad58ac6/src/commands/config/opencode.rs#L24-L44),
precedence `$OPENCODE_CONFIG_DIR` > `$XDG_CONFIG_HOME/opencode` >
`~/.config/opencode`.
- `<pi agent dir>/hooks/pre/worktrunk.ts` — `pi_agent_dir` /
`plugin_path` in
[`src/commands/config/pi.rs`](https://github.com/max-sixty/worktrunk/blob/74ad58ac6/src/commands/config/pi.rs#L20-L49),
honoring `$PI_CONFIG_DIR`, `$OMP_PROFILE`/`$PI_PROFILE`, and
`$PI_CODING_AGENT_DIR`.
- `<claude config dir>/settings.json` —
`handle_claude_install_statusline` in
[`src/commands/config/plugins.rs`](https://github.com/max-sixty/worktrunk/blob/74ad58ac6/src/commands/config/plugins.rs#L91-L152),
which merges the `statusLine` key (command `wt list statusline
--format=claude-code`) into the existing object rather than replacing
the file, and writes through `write_atomically` — the same
`settings.json` that `CLAUDE.md`'s Data Safety section already names as
a `write_atomically` target.

Files worktrunk does not write itself: `wt config plugins claude
install` and `codex install` shell out to `claude` / `codex`, which
record the marketplace and plugin in their own config
(`~/.claude/plugins/known_marketplaces.json` and
`installed_plugins.json`, read back by `is_marketplace_configured` /
`is_plugin_installed` in `show.rs`; `~/.codex/config.toml`'s
`[marketplaces.*]` tables, read by `is_marketplace_configured` in
`codex.rs`). Gemini is left out — worktrunk has no install path for it,
only a `wt config show` status line pointing at `gemini extensions
install`.

The three `faq.md` copies stay in sync via `test_docs_are_in_sync`; the
site page is primary and the two skill mirrors are generated.

</details>

No test: documentation only. `cargo test --test integration
test_docs_are_in_sync` and `test_plugin_layout_is_consolidated` pass
locally.

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-09-07 09:07:13 -07:00
Worktrunk Bot fb6576946a feat(config): install the Codex plugin, not just its marketplace (#4019)
`wt config plugins codex install` registered the Worktrunk marketplace
in Codex and then handed the rest back to the user (`↳ Next, run
/plugins in Codex and install Worktrunk from the marketplace`). The
Codex plugin CLI has had `codex plugin add PLUGIN@MARKETPLACE` since
[`rust-v0.131.0`](https://github.com/openai/codex/releases/tag/rust-v0.131.0),
so the install now runs it and converges on the Claude flow. Uninstall
moves with it — it removes the plugin, then the marketplace — so the two
commands are inverses again. Verified by the mock-driven
`test_plugins_codex_*` tests; `cargo test --lib --bins`, `cargo test
--test integration`, and `pre-commit run --all-files` pass locally.

Per [#4017
(comment)](https://github.com/max-sixty/worktrunk/issues/4017#issuecomment-5561118514)
there is no version floor and no fallback to the old hint: a failing
`codex plugin add` or `codex plugin remove` surfaces as an error with
codex's stderr in the gutter, the same way Claude's `plugin install`
does.

<details><summary>Settled point, and what wasn't verified</summary>

**The uninstalls now agree.** Both harnesses remove the plugin and then
its marketplace, so uninstall is the inverse of install for each. The
Claude half landed in `f681fa1d1`; the policy it follows is settled in
[a comment on this
PR](https://github.com/max-sixty/worktrunk/pull/4019#issuecomment-5564279241),
which also records what that costs someone who installed the plugin
without `wt`.

**Not verified from CI:** the sandbox has no `codex` binary, so the argv
is pinned by the mock harness but the claim that `codex plugin add
worktrunk@worktrunk` installs the plugin from our configured marketplace
is read from upstream source, not executed. The non-remote branch of
`run_plugin_add` at `rust-v0.153.0` is `find_marketplace_for_plugin` →
`manager.install_plugin(...)`, which filters on marketplace name plus
plugin name and reads no `PluginInstallPolicy` — so `"installation":
"AVAILABLE"` in `.agents/plugins/marketplace.json` and the curated
Git-source allowlist don't gate it. `RemovePluginArgs` takes the same
`PLUGIN[@MARKETPLACE]` selector as `AddPluginArgs`.

**Superseded during review:** an earlier revision made `codex plugin
remove` best-effort, on the premise that it fails where no plugin is
installed. It doesn't — the non-remote `run_plugin_remove` bottoms out
in `PluginStore::uninstall`, which returns `Ok(())` for a path that
isn't there — so that branch only ever swallowed genuine failures. It's
gone; `d69dcf2` carries the removal.

**Changed surface**

- `src/commands/config/codex.rs` — the two handlers
- `src/cli/config.rs` — `after_long_help` for both subcommands, plus
their one-line `about`s (`Configure the Worktrunk marketplace in Codex`
→ `Install the Worktrunk plugin`)
- `docs/src/content/docs/claude-code.md` and its generated mirrors
- `src/testing/mod.rs` — `setup_mock_codex_with_plugins` gains `plugin
add` / `plugin remove`; `setup_mock_codex_with_plugins_failing` fails
all four; new `setup_mock_codex_with_plugin_ops_failing` isolates a
plugin-op failure from the marketplace step

</details>

Closes #4017

---------

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>
2026-09-06 20:42:24 -07:00
Maximilian Roos acc0fd06d8 Report whether copy-ignored reflinked or copied in full (#4025)
`wt step copy-ignored` told every user the same thing — `Copied 65,679
files · 29.5 GB` — whether the filesystem shared the source's blocks or
wrote all 29.5 GB out. On ext4 and NTFS, which have no reflink, that is
the difference between a free copy and a full one, and nothing in the
output said which had happened. #4022 documented the caveat per
filesystem; this reports it per machine.

```
✓ Copied 4,812 files · 14.0 GB (reflinked, no extra disk)
✓ Copied 4,812 files · 14.0 GB (full copy)
✓ Copied 4,812 files · 14.0 GB (3,200 of 4,812 reflinked)
```

The signal was already in hand and thrown away. `reflink_or_copy`
returns `Ok(None)` when the platform's clone syscall succeeded and
`Ok(Some(bytes))` when it fell through to `fs::copy`, and `copy_leaf`
matched `Ok(_)`. It now returns that alongside the byte count,
`Progress::record` takes a `DataCopy` and counts each side,
`Progress::copy_split` reads the pair back, and `--format=json` gains
`reflinked` and `written` — with one rule and no exceptions: a payload
reporting a **result** carries all four, zeroed where nothing was
copied, and a **plan** (`dry_run: true`) carries none of them, since it
says what would be copied rather than what was. That last half is a
small behaviour change beyond the new keys — the `--require-include` and
empty-entries returns fire before the dry-run branch, so under
`--dry-run` they used to emit `files: 0, bytes: 0` while a plan with
entries in it emitted neither. They now emit none, so `jq '.reflinked +
.written'` no longer returns a number for one plan and null for another.
`same_worktree` is unchanged: it reports a result rather than a plan and
carries no `dry_run` key.

Only the reflinked wording carries a gloss, since that is the term a
reader won't know; the contrast then says what a full copy cost without
repeating the byte count sitting two words to its left.

<details>
<summary>Three decisions worth a look</summary>

**Symlinks record `DataCopy::Neither` rather than counting as written.**
A symlink's content is a path, so it has no extents to share or to
write. Counting it on either side would make a `node_modules/` full of
bin shims report as a partial reflink failure on a machine where
everything with data in it cloned fine.

**`WORKTRUNK_TEST_REFLINK=1|0` pins the reported label**, while the copy
still attempts a reflink either way. Whether a clone succeeds is a
property of the filesystem under the test's temp directory, and CI spans
APFS, ext4, and NTFS, so no one snapshot could hold on all three and the
branch a given run took would be invisible. It is set per command in the
copy-ignored tests rather than in `STATIC_TEST_ENV_VARS`, which reaches
every child and would add an `env:` line to every snapshot in the suite.
Eleven snapshots pin the reflinked branch and one new test pins `(full
copy)`; the mixed case needs two filesystems under one tree, so unit
tests cover its counting and its rendering.

**`classify_copy` is a named function for the same reason.** The
coverage job runs on `ubuntu-24.04`, so ext4 never reaches the reflinked
arm and it would have posted as a patch miss. A unit test pins the
mapping instead of whichever runner happens to execute it.

</details>

The signal is exact per file, but it says only that a clone did not
happen, never why — an unsupported filesystem and a cross-device copy
are indistinguishable here. That is enough for the claim the output
makes (those bytes really were written) and not enough to assert "this
filesystem has no reflink", which the wording avoids.

`wt step promote` also calls `copy_leaf` and reports no split: its copy
path only runs as the cross-device fallback when `rename` fails with
EXDEV, where a reflink is impossible by definition.

> _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_01NrPJmECkKALUaqxb9GKCC5

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 20:24:22 -07:00
Maximilian Roos 38c8100527 Build the commit prompt from the worktree being committed (#3996)
`wt step commit --branch <b>` commits in another worktree, but built the
LLM prompt from the invoking one. `build_commit_prompt` called
`Repository::current()` and diffed from its discovery path, so with
nothing staged in the cwd the generator got an empty diff, an empty
diffstat, the invoking branch's name, and the invoking branch's recent
commits — and produced a commit message describing none of the changes
it was committing. `wt step relocate --commit`, which commits into each
relocated worktree in turn, had the same bug for the same reason.

`build_commit_prompt` and `generate_commit_message` now take the target
`WorkingTree`, and every input — diff, diffstat, branch, repo root,
recent commits — is read from it. `--show-prompt` and `--dry-run`
resolve `--branch` the same way the real run does, through a shared
`resolve_env`, so a preview describes the commit the same flags would
make. `--dry-run`'s temp index is created in the target worktree too.

Two accessors move from `Repository` to `WorkingTree`, since both answer
per-worktree questions:

- `recent_commit_subjects` walks back from HEAD, and HEAD is
per-worktree. On the repository it answered for whichever worktree the
repo was discovered from.
- `diff_stats_summary` — every caller diffs the index or HEAD.

Existing call sites that meant "the invoking worktree" say so explicitly
(`repo.current_worktree().diff_stats_summary(…)`).

New coverage: `--branch` against a worktree with staged changes in
`tests/integration_tests/merge.rs`, and the relocate path in
`tests/integration_tests/step_relocate.rs`.

The `wt step commit` help said `--branch` "has no effect on `--dry-run`,
which always previews the current worktree" — accurate before this
change, and not after. It now says `--branch` selects the previewed
worktree the same way it selects the committed one.

UX survey items: `#103`.

Reviewable files: `src/llm.rs`, `src/commands/step/commit.rs`,
`src/git/repository/diff.rs`, `src/commands/commit.rs`,
`src/cli/step.rs`, `tests/integration_tests/merge.rs`,
`tests/integration_tests/step_relocate.rs`. Generated mirrors and
snapshots are regenerated.

> _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_01XAUYWFN9d9oh6jyoQiouHb

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 19:16:37 -07:00
Maximilian Roos 5327a068d5 docs(merge): record that post-commit can't run when the merge removes its worktree (#4026)
`wt merge` prints `◎ Running post-commit: …` and then spawns that
pipeline into a path the removal has already emptied, so a `post-commit`
hook can't be relied on to do what it says. Nothing reports this, and
two doc lines promised the behavior that doesn't happen.

`post-commit` is the only hook in the merge's background batch anchored
on the feature worktree — `post-merge`, `post-switch` and `post-remove`
all anchor on the destination. `HookAnnouncer` flushes every pending
pipeline once at the end of the command, which is after
`finish_after_merge` has removed that worktree.

What the runner then finds depends on where the worktree lived, because
the fast removal path renames it into `.git/wt/trash/` and leaves an
empty placeholder at the original path (`changed_directory: true`, so
the shell's `$PWD` stays valid), torn down a second later by the
detached `sleep 1 && rmdir`. `run_pipeline` calls `Repository::at` on
that placeholder, and git discovery walks up from it:

- **Worktree outside the repo** (the default `../{{ repo }}.{{ branch
}}`): no git ancestor, so the runner logs `failed to open repository for
pipeline` to `.git/wt/logs/<branch>/<source>/post-commit/runner.log`,
which nothing reads back. The hook doesn't run.
- **Worktree nested inside the repo** (`worktree-path = "{{ repo_path
}}/.worktrees/{{ branch | sanitize }}"`, one of the config page's own
examples): discovery resolves to the **primary** worktree,
`Repository::at` succeeds, and the steps do run — with their cwd set to
the placeholder `rmdir` unlinks a moment later, and any `git` inside
them resolving against the primary worktree.

It's a regression, not a hook that never worked: #1679 added
`post-commit` and it fired for five weeks, until #2457 collapsed the
merge's two or three `◎ Running …` lines into one. That was a cosmetic
change, and deferring the spawn to a single end-of-command flush moved
it past the removal.

Documented rather than fixed, deliberately. The commit `post-commit`
would fire on is squashed and rebased before the merge lands;
`pre-remove` already covers work that must finish in the feature
worktree, blocking removal until it does; and spawning at commit time
only narrows the window, since `git worktree remove` succeeds against a
live cwd. `post-commit` still runs properly wherever the worktree
survives — `--no-remove`, merging on the target branch, merging from the
primary worktree — and on `wt step commit` / `wt step squash`.

So this adds the explanation at the `HookAnnouncer` construction in
`handle_merge`, and corrects what the docs claimed:

- The merge pipeline's step 1 said "Post-commit hooks run in background"
flatly.
- The hook page said "During `wt merge`, hooks run in this order:
pre-commit → post-commit → pre-merge → pre-remove → post-remove +
post-merge" — wrong twice, since the `post-*` hooks don't run in
sequence either (#4020 corrected that one row above). It now gives the
blocking order, says the `post-*` hooks start together in the worktree
each is anchored on, and points at `pre-remove` or `--no-remove`.
- `test_merge_squash_combines_post_commit_…` said the merge "fires
post-commit"; it announces it. Both it and its auto-commit sibling now
say the announce line is all they pin.

No behavior change.

<details>
<summary>Reproduction</summary>

A scratch repo with a `post-commit` hook, a feature worktree with one
commit and one uncommitted file, then `wt merge main --yes`. Every case
prints `◎ Running post-commit: mark (user)`:

| Layout | Flags | Outcome |
|--------|-------|---------|
| external (`../{{ repo }}.{{ branch }}`) | default (squash) | hook
doesn't run |
| external | `--no-squash` | hook doesn't run |
| external | `--no-remove` | runs correctly |
| nested (`{{ repo_path }}/.worktrees/{{ branch }}`) | default (squash)
| runs in the doomed placeholder |

The external removing cases leave this in
`.git/wt/logs/feat/user/post-commit/runner.log`:

```
✗ failed to open repository for pipeline
  fatal: not a git repository (or any of the parent directories): .git
```

The nested case leaves an empty `runner.log` and the hook's own output
shows where it landed — `pwd` is the removed worktree's path, while `git
rev-parse --show-toplevel` from inside it answers with the primary
worktree:

```
cwd=/…/repo/.worktrees/feat
toplevel=/private/…/repo
```

The commit itself is made in every case — `Changes to dirty.txt` reaches
`main`.

</details>

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 18:41:43 -07:00
Maximilian Roos fa57cb63a4 Document copy-ignored's disk-space benefit (#4022)
`wt step copy-ignored` reflinks the files it copies, so a new worktree's
`target/` shares disk blocks with the primary worktree's until something
writes to them. That was documented only under a "Performance" heading,
next to a table of times, where it read as an explanation of why the
copy is fast rather than a benefit in its own right. Nothing quantified
the disk saving, and the homepage bullet ("Copy build caches — skip cold
starts…") sold the time and not the space.

The saving is large and it survives real use: cargo writes new files for
changed crates rather than rewriting dependency rlibs, so the bulk of
`target/` stays shared as a worktree is built in. Measured across 56
worktrees of this repo on one machine, `du` reports 2.59 TB where the
disk holds 0.71 TB.

Changes:

- Rename the `copy-ignored` "Performance" section to "Copy-on-write",
add a Disk column to the existing table, and name the filesystems that
support reflink along with what happens on ext4 and NTFS. Nothing linked
to the old `#performance` anchor.
- Add an FAQ question, "How much disk do worktrees use?", for the
fleet-level number — what someone weighing a many-worktree workflow
actually wants to know, and a question the FAQ didn't answer.
- Rewrite the homepage bullet as "Share build caches" — "Copy" named the
thing the feature avoids doing — and state both benefits: `target/`,
`node_modules/`, etc reach ten worktrees without being built or copied.
- Drop the copy-on-write bullet from the Claude Code comparison. Claude
Code ships compiled with Bun, whose `fs.copyFile` uses `clonefile()` on
macOS, so it isn't a difference there: on a 2 GB file, Bun's
`copyFileSync` consumed 6 MiB against Node's 2111 MiB. The remaining
bullets carry the real differences.

The filesystem scope is stated wherever a claim is made, since reflink
needs APFS, btrfs, XFS, or ReFS; ext4 and NTFS fall back to a full copy.

Follow-up, not in this PR: `reflink_or_copy` returns `None` on reflink
and `Some(bytes)` on fallback, and `src/copy.rs:125` matches `Ok(_)` and
discards it. So a user on ext4 gets a full copy, is told `Copied N files
· 29.5 GB`, and has no way to learn which they got. The docs now state
the caveat per filesystem; the command could state it per machine.

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 16:15:50 -07:00
Maximilian Roos 24d1a8d15e docs(hook): say that post-* hooks from the two sources run concurrently (#4020)
`wt hook --help` said project hooks run "after user hooks". That holds
for `pre-*`, which run as one blocking pipeline, and not for `post-*`,
where each source gets its own detached pipeline and both start at once.
Nothing else in the docs said so, so a hook written against the stated
order looked supported when it wasn't — two `post-merge` hooks that each
run `git pull` in the merge destination append to one another's
`FETCH_HEAD`, both die with `fatal: Cannot rebase onto multiple
branches`, and `wt merge` reports success with the target unpushed.

This corrects the table and adds a paragraph saying what each kind
actually does, including that commands which depend on each other belong
in one source.

<details>
<summary>Why the docs moved rather than the code</summary>

Sequencing the pipelines was the other way to close the gap, and this
branch tried it first. Two things break:

`post-start` is documented for dev servers and `wt step tether`. With a
user `post-start` of `sleep 300` standing in for one, the project's
`post-start` marker lands within 6s on `main` and never lands when the
pipelines are chained.

A batch also spans two worktrees: `post-commit` anchors on the invoking
worktree, `post-remove`/`post-switch`/`post-merge` on the destination.
The invoking worktree is removed before the hooks run, so chaining drops
everything behind that head. On `wt merge main --no-squash` with a dirty
worktree and all four hooks configured, `main` runs post-remove,
post-switch and post-merge; the chained version ran only post-switch.

The race the sequencing was aimed at is real but narrower than the cure:
it needs two hooks touching one worktree's git state, which the docs now
tell users to avoid by keeping dependent commands in one source.
`CLAUDE.md` records the constraint so the fix isn't re-attempted.

</details>

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 15:22:08 -07:00
Maximilian Roos 474f93a007 Replace config update --print with --output (#4021)
`wt config update` now uses a destination-based output interface:
`--output <path>` atomically writes the migration artifact, while
`--output=-` emits the same bytes to stdout. This replaces `--print`,
keeps output mode read-only, and includes project-config migrations when
invoked from a linked worktree.

Relative destinations honor the global `-C` directory. When there is
nothing to migrate, stdout stays silent and an existing file destination
remains untouched. If both user and project configs need migration,
stdout emits a labeled inspection artifact while file output fails
before writing. Help text also notes that output artifacts omit legacy
`approved-commands`; the in-place mode remains responsible for moving
those entries to `approvals.toml`.

Integration coverage exercises file replacement, preservation and write
failures, multi-config rejection, stdout piping and broken consumers,
clean configs, linked worktrees, source-file preservation, help text,
and rejection of the removed flag. The full pre-merge gate passed all
4,770 tests, targeted instrumentation covers both file-error branches,
and the production docs build passed all 16 built-site and browser
tests.

> _This was written by Codex on behalf of max-sixty_
2026-09-06 15:07:29 -07:00
ADITYA 7d08f6775f Add Pi activity tracking plugin (#3594)
## Summary

- add `wt config plugins pi install|uninstall`
- install a profile-aware Pi / oh-my-pi hook with atomic writes
- map Pi lifecycle events to Worktrunk working, waiting, and cleared
activity markers
- honor Pi's agent-directory, config-directory, and profile environment
variables
- cover profile paths, explicit directory overrides, uninstall behavior,
and CLI output snapshots

## Validation

- `cargo fmt --check` — passed
- `cargo test --test integration test_pi_ -- --nocapture` — 3 passed
- the same snapshot tests passed again without `INSTA_UPDATE`
- `cargo clippy --bin wt -- -D warnings` — passed

Closes #3571

---------

Signed-off-by: Aditya Datta <crazyme07071996@gmail.com>
2026-09-06 10:52:18 -07:00
Worktrunk Bot 8e420cb8c4 Load the OpenCode plugin under OpenCode 2 (#4018)
## Problem

`wt config plugins opencode install` writes a plugin OpenCode 2 silently
ignores, reported in #4014. OpenCode 2's loader decodes a plugin
module's default export against a schema that accepts `{ id, effect }`
or `{ id, setup }` — a bare default-exported function, which is what
this plugin has always been, fails the decode and the load is dropped
without a message
([`packages/core/src/plugin/module.ts#L60`](https://github.com/sst/opencode/blob/b2cecc6350d377c382e1ec32ee66ec63ad68f715/packages/core/src/plugin/module.ts#L60)
on the `beta` branch, which is where the OpenCode 2 prereleases come
from — `dev` is the 1.18.x line). The plugin also ran `wt` through Bun's
`$`, which the host only supplies when it is itself running under Bun.

## Solution

One file, both runtimes: the default export is now `{ id, setup, server
}`, and `wt` is spawned with `node:child_process` instead of Bun's
shell. OpenCode 2 reads `setup` and subscribes to session events;
OpenCode 1.16+ reads `server` and gets exactly the hooks it has today.
Each version ignores the other's key, so the install path, filename, and
marker behavior are all unchanged.

Two details in the `setup` arm that differ from the port in the issue's
thread — both from reading the shipped `@opencode-ai/plugin@beta` types:
`session.status` carries `status.type ∈ {idle, busy, retry}` and
`session.idle` is annotated deprecated, so the status is read rather
than the event name; and `session.status` with an `idle` status sets 💬
rather than 🤖.

**The part worth a maintainer's call**: this keeps the OpenCode 1 shape
rather than switching cleanly to v2 alone. OpenCode 2 is still
prerelease (`npm dist-tags` for `@opencode-ai/plugin`: `latest` 1.18.29,
`beta` 0.0.0-beta-19192), so a v2-only file would make every current
stable install fail with `Plugin … must default export an object with
server()`. The cost is the `server` arm plus its two local types — say
the word and I will cut it to `{ id, setup }`. Either way OpenCode below
1.14.19 loses the plugin: that is where `readV1Plugin` — the function
that admits an object-shaped default export — first appears, and older
releases accept only the bare function. 1.16 is the floor worth
documenting, though, because it is where the host began filtering events
to each plugin instance's directory
([v1.16.0](https://github.com/sst/opencode/blob/6cb74317a6efacd656483cb0489d8e7e3701c12e/packages/opencode/src/plugin/index.ts#L258-L259));
[v1.15.0](https://github.com/sst/opencode/blob/2662a4f955e563fd22cd5c4873ca350d21745275/packages/opencode/src/plugin/index.ts#L246)
fans every bus event to every instance, so on 1.14.19–1.15.x the marker
follows the worktree the instance was created for rather than the
session that is active — the same behavior today's plugin has, not a
regression.

## Verification

`cargo test --test integration test_docs_are_in_sync`, the twelve
`opencode` integration tests, and `packaged_assets` pass. Beyond that I
ran the new file through both loaders' own logic under `node
--experimental-strip-types`, with a stub `wt` on `PATH` recording its
arguments.

<details><summary>Loader checks and recorded marker calls</summary>

Decoding the real module with the exact schema OpenCode 2 uses
(`effect@4.0.0-rc.112`, the version its plugin package depends on), and
with OpenCode 1's
[`readV1Plugin`](https://github.com/sst/opencode/blob/337fd144d2ba144743368f78d9579a99cce175bd/packages/opencode/src/plugin/shared.ts#L272)
detect-mode logic:

```
OpenCode 2 module decode : ok, id=worktrunk
OpenCode 1 readV1Plugin  : ok, id=worktrunk
v2 setup returned cleanup: true
v2 setup on a 1.18 context: bails, no throw
```

The last line covers a case worth calling out: OpenCode 1.18 also
carries a `{ id, setup }` loader, but the context it passes has neither
`location` nor `event`. `setup` returns early there instead of throwing,
and `server` drives the marker.

Feeding a fake event stream, the commands the plugin actually issued:

```
v2:  set 🤖   (status busy)
     set 🤖   (status retry)
     set 💬   (status idle)
     — skipped, event location is another worktree —
     set 💬   (session.idle)
     clear    (session.deleted)
     clear    (cleanup)

v1:  set 🤖 / set 💬 / clear   (unchanged from today)
```

What I could not check from CI is the plugin running inside a real
OpenCode 2 session — @pragmaticivan, if you are still up for testing,
`wt config plugins opencode install` off this branch should behave the
same as your local port.

Install location is unchanged and confirmed on `beta`: plugins are still
discovered as `.ts`/`.js` files under `plugin/`, `plugins/` in each
config directory
([`source-directory.ts#L7`](https://github.com/sst/opencode/blob/b2cecc6350d377c382e1ec32ee66ec63ad68f715/packages/core/src/plugin/source-directory.ts#L7)).

</details>

Closes #4014

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-09-06 09:19:38 -07:00
Mahiro Hirakawa 29a611b7c4 fix(config): marker set/clear no-op outside a git repository (#3981) 2026-09-05 23:27:55 -07:00
Worktrunk Bot 3f87fec5a7 Leave the branch template variable unset in a detached worktree (#4010)
## Problem

In a detached worktree, `CommandContext::branch_or_head` substituted the
literal `HEAD` for `{{ branch }}`. `HEAD` is a non-empty string git
happily resolves as a ref, so every guard written around `branch` passed
and the command ran against the wrong thing — the reported case ended in
`git push origin --delete HEAD`. It also disagreed with `wt list
--format=json`, which reports `branch: null` for the same worktree, and
with the hook docs, which say undefined variables error so a template
can guard them.

## Solution

`branch` is now absent in a detached worktree rather than falling back.
`{% if branch %}` guards it the way the docs already prescribe for
`upstream`; an unguarded `{{ branch }}` is an undefined-variable error
naming the template and listing the variables that *are* in scope. This
is the first of the two options in #4009 — the one the reporter picked.

The removal path needed the same treatment on its own:
`spawn_hooks_after_remove` took the removed branch as `&str` and both
detached call sites passed the literal `HEAD`, which
`PostRemoveContext::extra_vars` then applied *after* the base context —
so `post-remove` still saw `branch = HEAD` even with the base fix in
place. It takes `Option<&str>` now. That is the hook where the issue's
own command belongs, so it's covered by its own regression test
(`test_user_post_remove_branch_unset_in_detached_worktree`, which
reproduces `branch=[HEAD]` without the change).

Riding along:

- The `base` and `target` branch names derived from the current branch
follow `branch` and stay unset too — both for a **manual** `wt hook
<type>` and for the `pre-switch` hooks `wt switch` fires, which
previously rendered `base` as an empty string. The directional *path*
vars (`base_worktree_path`, `target_worktree_path`) still apply — the
worktree exists whether or not it is on a branch.
- `vars` is now inserted as the **empty map** when `branch` is absent,
not skipped. Per-branch vars are keyed by branch, so a detached worktree
has none either way (`wt config state vars set` can't even write there —
it goes through `require_current_branch`), but keeping the object
defined means `{{ vars.key | default('x') }}` still renders under
SemiStrict instead of erroring on an undefined `vars`. Same reason
`list::custom_columns` injects an empty map for a branchless row.
- The verbose variables table rendered a detached `branch` as `(unused)`
under `VarScope::Referenced` — the label that means "the scope gate
saved us the work", which is wrong for a cheap var computed regardless.
`ALWAYS_COMPUTED_VARS` in `expansion.rs` marks it `(unset)`, the label
for a var the operation genuinely couldn't supply.
- The `hook` long-help sentence about unset `base` / `target` scoped
them to a manual `wt hook`; both are now operation-driven cases too
(`base` in a `pre-switch` from a detached worktree, `target` in a
removal that lands in one), so it's worded generically and the three doc
mirrors are regenerated.
- The `hook` long-help JSON-context example read `ctx['branch']`
unguarded — the same unguarded case in Python, which now raises
`KeyError`. It uses `ctx.get('branch', '')` and the section says why.
- `target` — the branch the user lands on after a removal — followed the
same rule on review: `PostRemoveContext::new` built it with
`unwrap_or_default()`, so a **detached primary worktree** handed
`post-remove` an empty string and made `wt -v` print `target = ` instead
of `target = (unset)`. It's `Option<String>` now, pushed only when there
is one. `commit` / `short_commit` keep their `""` shape — they predate
this and `PostRemoveContext::new` documents the choice. The `pre-remove`
half of the same removal built its extra vars by hand and had the same
`unwrap_or_default()`; it goes through `TemplateVars::with_target_opt` +
`as_extra_vars` now — the builder `wt hook pre-remove` already used — so
both hooks of one removal agree.
- The `HEAD` literal survives in exactly one place: the background
pipeline's **log file name** (`spawn_hook_pipeline_quiet`), which needs
some string and never reaches a template.

`branch_or_head` had no callers left, so it's deleted.
`TemplateVars::with_base` takes `Option<&str>` and a new
`with_target_opt` sits beside `with_target`, so the optional-branch call
sites keep the POSIX path conversion in the builder rather than
re-inlining it.

## Behavior change worth a look

This is deliberate — it's what the issue asks for — but it turns
previously-working shapes into errors, each pinned by a test that this
PR updates rather than deletes:

- `wt step for-each -- echo '{{ branch }}'` now fails at the detached
worktree instead of printing `HEAD`, and (as with any undefined
variable) the loop stops there. Not a new failure mode: `{{ upstream }}`
already behaved this way at the first non-tracking worktree.
`test_for_each_detached_branch_variable_unguarded` snapshots it.
- A project `pre-remove` or `post-remove` hook that references `{{
branch }}` unguarded now blocks `wt remove` on a detached worktree until
it's guarded (or `--no-hooks`).
`test_pre_remove_hook_branch_expansion_detached_head` previously
asserted `branch=HEAD`; it now uses the guarded form and asserts
`branch=`.
- A `pre-switch` hook run from a detached worktree gets no `base` rather
than an empty one. An unguarded `{{ base }}` errors where it used to
render nothing.

If any of these should instead render an empty string rather than error,
that's a different fix — say the word and I'll redo it that way.

## Testing

`test_alias_branch_unset_in_detached_worktree`
(`tests/integration_tests/step_alias.rs`) and
`test_user_post_remove_branch_unset_in_detached_worktree`
(`tests/integration_tests/user_hooks.rs`) are the reproductions: both
fail with `HEAD` before their respective fixes and pass now.
`test_user_remove_hooks_target_unset_with_detached_primary_worktree`
(both `pre-remove` and `post-remove` of one removal) and
`test_user_pre_switch_base_unset_in_detached_worktree` pin the `target`
and `base` cases; both use `{% if x is defined %}` rather than `{% if x
%}`, since an empty string and an absent var are indistinguishable under
the plain guard, and each half fails on its pre-fix shape.

`cargo run -- hook pre-merge --yes` is green apart from three failures
that reproduce with these changes stashed, on this branch, in the same
sandbox — `test_copy_ignored_preserves_file_executable_permissions`
(expects 0644, gets 0664 under the runner's umask 002) plus
`test_powershell_skipped_when_installed_no_profile` and
`test_nushell_install_target_is_a_vendor_autoload_dir` (the sandbox
home's shell state). Clippy `--all-targets --all-features` and `cargo
fmt --check` are clean.

<details><summary>Manual check against the issue's repro</summary>

```console
$ git worktree add --detach ../repo.scratch HEAD && cd ../repo.scratch

$ wt -v guarded          # probe = 'echo "[{% if branch %}{{ branch }}{% endif %}]"'
○ guarded template variables:
  branch                = (unset)
  worktree_path         = /tmp/…/repo.scratch
…
[]

$ wt probe               # probe = 'echo "[{{ branch }}]"'
✗ Failed to expand probe: undefined value @ line 1
  echo "[{{ branch }}]"
↳ Available variables: args, cwd, main_worktree, repo, repo_path, repo_root, worktree, worktree_name, worktree_path

$ cd ../repo && wt probe # unchanged on a branch
[main]
```

</details>

---

Closes #4009 — automated triage

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-09-05 13:22:49 -07:00
Worktrunk Bot 51fd3b2b7e docs(config): name the bare repo's own location in the worktree-path 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>
2026-09-04 12:35:53 -07:00
Maximilian Roos ce57d2d997 Name the unconfigured source when a hook filter matches nothing (#3997)
A source filter with no command name (`wt hook pre-merge user:`) reported `No command named user:` when that source configured no hooks — a message about a name the user never gave. `HookSourceNotConfigured` says what is true and points at the source that does have hooks, when one does.

The rest corrects claims the hook and step reference pages had drifted from: `--foreground` for a post-hook, `pre-commit` running before every Worktrunk commit rather than only the merge's, which commands accept `--no-hooks`, that a preview leaves a whole `vars` expression alone including its filters, the `wt hook show` approval glyph, `wt step commit --branch`, that hooks fire on `wt step commit` and `wt step squash`, `wt step copy-ignored`'s `--from` / `--to` and its primary-worktree default, and that `wt step prune` removes branches as well as worktrees with `--min-age` guarding both. Two `worktree-path` recipes give way to a link to the config page that owns them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb
2026-09-02 18:44:14 -07:00
dependabot[bot] fd10235f75 chore: bump fast-uri from 3.1.5 to 3.1.7 in /docs (#3991)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.5 to
3.1.7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/fastify/fast-uri/releases">fast-uri's
releases</a>.</em></p>
<blockquote>
<h2>v3.1.7</h2>
<h2>⚠️ Security Warning</h2>
<p>This is a security release that fixes the following high-severity
security advisories:</p>
<ul>
<li><a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-qw65-cvwx-89v3">GHSA-qw65-cvwx-89v3</a>
— authority injection via an unvalidated port in
<code>serialize()</code></li>
<li><a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-58mr-gqgx-xq4g">GHSA-58mr-gqgx-xq4g</a>
— host confusion via unbalanced or misplaced IP-literal brackets</li>
</ul>
<p>Users of the v3.x release line should upgrade to v3.1.7.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/fastify/fast-uri/compare/v3.1.6...v3.1.7">https://github.com/fastify/fast-uri/compare/v3.1.6...v3.1.7</a></p>
<h2>v3.1.6</h2>
<h2>⚠️ Security Warning</h2>
<p>This release addresses the following high-severity security
advisories:</p>
<ul>
<li><a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-5jgf-p345-68v8">GHSA-5jgf-p345-68v8</a>
— host confusion via skipped IDN canonicalization on scheme-relative
references</li>
<li><a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-fph4-wmhf-6fwf">GHSA-fph4-wmhf-6fwf</a>
— server-side request forgery via repeated hostname
percent-decoding</li>
<li><a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-f65p-4m7j-42xc">GHSA-f65p-4m7j-42xc</a>
— server-side request forgery via malformed IPv6 normalization</li>
<li><a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-jqff-g426-hqxp">GHSA-jqff-g426-hqxp</a>
— host confusion via percent-encoded scheme normalization</li>
</ul>
<p>Users of the v3.x release line should upgrade to v3.1.6.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/fastify/fast-uri/compare/v3.1.5...v3.1.6">https://github.com/fastify/fast-uri/compare/v3.1.5...v3.1.6</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/fastify/fast-uri/commit/412e40abd4eb8beabfb952d80abf949a2baf27a3"><code>412e40a</code></a>
Bumped v3.1.7</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/9f4c943e4d2133e8d78e0941203879216255bb01"><code>9f4c943</code></a>
fix: backport port and IP-literal validation to v3.x (<a
href="https://redirect.github.com/fastify/fast-uri/issues/216">#216</a>)</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/1eb3ce436fe050807caba79f886ab894f485a588"><code>1eb3ce4</code></a>
fix: treat unterminated bracket hosts as reg-names again (<a
href="https://redirect.github.com/fastify/fast-uri/issues/214">#214</a>)</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/6f970b2951fd896aa0f3a7ff28eeb6640c137d33"><code>6f970b2</code></a>
Bumped v3.1.6</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/d941579a84273ec7e96bde596b1f7a8be447df2a"><code>d941579</code></a>
fix: never run IDN canonicalization on bracketed IP literals</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/c0f0279cf370cb89ee56b04bbcde2a7afbe81aba"><code>c0f0279</code></a>
test: adapt decoded-scheme handler assertion to 3.x (no mailto
scheme)</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/37f3417c82994279656854f83ce938acd81c3862"><code>37f3417</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/607bfbe953f28a14c2e06ae64aff38c81ca2937f"><code>607bfbe</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/ae92a4c5d8c4b6c9e447f048d5fcbde7eebd5514"><code>ae92a4c</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/444ecdad447db2cc23c4d422acc6f0daa6fa8eef"><code>444ecda</code></a>
Merge commit from fork</li>
<li>Additional commits viewable in <a
href="https://github.com/fastify/fast-uri/compare/v3.1.5...v3.1.7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=fast-uri&package-manager=npm_and_yarn&previous-version=3.1.5&new-version=3.1.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/max-sixty/worktrunk/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-02 15:33:50 -07:00
Maximilian Roos de0052d8db Release v0.76.0 (#3983)
Cuts 0.76.0. `cargo semver-checks` fails ten lints, so the bump is
minor.

## Changelog filing fix

Five entries had been appended to the already-tagged `## 0.75.0` section
after v0.75.0 shipped (#3977/#2860, #3949 ×2, #3959, #3945). They
describe changes that ship *here*, and GitHub's 0.75.0 release notes —
built from the tag — never showed them. They are moved into `## 0.76.0`
and rewritten to the length ceiling; `## 0.75.0` is restored
byte-identical to `git show v0.75.0:CHANGELOG.md`.

The mechanism is systemic: a PR appends under whatever heading is
currently top, so every post-release PR lands in the shipped section
until the next release opens a new one.

## `--execute` template guidance

`#3977` switched `--execute` expansion to `ShellEscapeMode::Literal`,
which is correct for an argv model. But the migration that
v0.53.0–v0.75.0's deprecation warning *printed* — `-x sh -- -c '<old
body>'` — splices a template variable into text `sh` re-parses, so a
value with spaces word-splits where the old POSIX-escaped model kept it
intact. Worst case is `rm -rf` against unintended paths.

`--execute`'s help now documents passing the variable as a separate
argument and referencing it positionally. `extending.md` already
modelled that form, so no recipe changed.

## Validation

- `nightly` on the cut-from tip (`baf161bf6`): all 14 jobs green — [run
33538866753](https://github.com/max-sixty/worktrunk/actions/runs/33538866753)
- `wt hook pre-merge --yes`: 4713 passed
- Data-loss surface reviewed across the 45-commit diff by four
independent finders; adjudication in the release thread. No new
destructive path — `#3977` removes one, deleting the EXEC directive file
from all five shell wrappers.

> _This was written by Claude Code on behalf of max-sixty_
2026-09-01 14:46:52 -07:00
Maximilian Roos baf161bf60 Run switch --execute as literal argv (#3977)
## Summary

- Treat `wt switch -x` as one program plus arguments that bypass
Worktrunk shell parsing; program lookup and argument decoding use native
operating-system behavior.
- Launch the program from `wt` in the selected worktree, preserving
terminal access, signals, and exit status.
- Remove the shell exec directive, shell-specific escaping, and implicit
`sh` dependency. Shell wrappers now carry only the directory change.
- Make the Nushell wrapper recognize clustered execute flags such as
`-cx`, stop scanning at `--`, and warn when a retired exec-file wrapper
buffers stdout away from the terminal.

Shell syntax remains explicit: `-x sh -- -c 'code . && test -f
Cargo.toml'`. On Windows, shell shims need their extension (`-x
code.cmd`) or an explicit shell such as `-x cmd.exe -- /C code`.

This completes the argv cutover proposed in #2860 and removes the
remaining Nushell-on-Windows problem in #3944. It adds no PATHEXT
lookup, custom Windows quoting, or PowerShell launcher path, and still
removes 597 lines from `src` and `templates` on net.

## Validation

- `cargo run -- hook pre-merge --yes`
- 4,712 tests passed; one skipped
- Formatting, clippy, docs, doctests, lockfile, and snapshot checks
passed

Thanks @omgreenfield for testing the migration path in #2860.

Closes #2860
Closes #3944

> _This was written by Codex on behalf of @max-sixty_
2026-09-01 10:36:30 -07:00
dependabot[bot] 40fab76815 chore: bump the docs-site group in /docs with 2 updates (#3968)
Bumps the docs-site group in /docs with 2 updates:
[@astrojs/starlight](https://github.com/withastro/starlight/tree/HEAD/packages/starlight)
and
[astro](https://github.com/withastro/astro/tree/HEAD/packages/astro).

Updates `@astrojs/starlight` from 0.41.7 to 0.41.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/starlight/releases">@​astrojs/starlight's
releases</a>.</em></p>
<blockquote>
<h2><code>@​astrojs/starlight</code><a
href="https://github.com/0"><code>@​0</code></a>.41.9</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/starlight/pull/4145">#4145</a>
<a
href="https://github.com/withastro/starlight/commit/1c90d69b424aedef096e12577e9e7ec96bed4efc"><code>1c90d69</code></a>
Thanks <a href="https://github.com/pyxelr"><code>@​pyxelr</code></a>! -
Adds 1 new icon: <code>goodreads</code></li>
</ul>
<h2><code>@​astrojs/starlight</code><a
href="https://github.com/0"><code>@​0</code></a>.41.8</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4142">#4142</a>
<a
href="https://github.com/withastro/starlight/commit/cacbc9ff45c551c2f20c6b307800f7ff7ee07db2"><code>cacbc9f</code></a>
Thanks <a
href="https://github.com/alebelcor"><code>@​alebelcor</code></a>! - Adds
1 new icon: <code>whatsApp</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4133">#4133</a>
<a
href="https://github.com/withastro/starlight/commit/3944311bed46f956e12e79a8eb0c32084eeeaea6"><code>3944311</code></a>
Thanks <a href="https://github.com/delucis"><code>@​delucis</code></a>!
- Internal refactor: numbered <code>id</code> attributes in the
<code>&lt;Tabs&gt;</code> component are now page-specific instead of
using a global counter</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4138">#4138</a>
<a
href="https://github.com/withastro/starlight/commit/cd4b6655f3235220cded12f0567e0dad1bf5adac"><code>cd4b665</code></a>
Thanks <a href="https://github.com/delucis"><code>@​delucis</code></a>!
- Fixes localisation of code block UI elements when using the Sätteri
Markdown processor</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/starlight/blob/main/packages/starlight/CHANGELOG.md">@​astrojs/starlight's
changelog</a>.</em></p>
<blockquote>
<h2>0.41.9</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/starlight/pull/4145">#4145</a>
<a
href="https://github.com/withastro/starlight/commit/1c90d69b424aedef096e12577e9e7ec96bed4efc"><code>1c90d69</code></a>
Thanks <a href="https://github.com/pyxelr"><code>@​pyxelr</code></a>! -
Adds 1 new icon: <code>goodreads</code></li>
</ul>
<h2>0.41.8</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4142">#4142</a>
<a
href="https://github.com/withastro/starlight/commit/cacbc9ff45c551c2f20c6b307800f7ff7ee07db2"><code>cacbc9f</code></a>
Thanks <a
href="https://github.com/alebelcor"><code>@​alebelcor</code></a>! - Adds
1 new icon: <code>whatsApp</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4133">#4133</a>
<a
href="https://github.com/withastro/starlight/commit/3944311bed46f956e12e79a8eb0c32084eeeaea6"><code>3944311</code></a>
Thanks <a href="https://github.com/delucis"><code>@​delucis</code></a>!
- Internal refactor: numbered <code>id</code> attributes in the
<code>&lt;Tabs&gt;</code> component are now page-specific instead of
using a global counter</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/starlight/pull/4138">#4138</a>
<a
href="https://github.com/withastro/starlight/commit/cd4b6655f3235220cded12f0567e0dad1bf5adac"><code>cd4b665</code></a>
Thanks <a href="https://github.com/delucis"><code>@​delucis</code></a>!
- Fixes localisation of code block UI elements when using the Sätteri
Markdown processor</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/withastro/starlight/commit/bc6b188463c82a58466ad0c9339d62e30977d807"><code>bc6b188</code></a>
[ci] release (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4153">#4153</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/1c90d69b424aedef096e12577e9e7ec96bed4efc"><code>1c90d69</code></a>
Add Goodreads icon (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4145">#4145</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/e81fedf2ed47fc6ca5b7c1bcb4094e514dc1dade"><code>e81fedf</code></a>
[ci] release (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4137">#4137</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/cd4b6655f3235220cded12f0567e0dad1bf5adac"><code>cd4b665</code></a>
Fix localisation of Expressive Code UI (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4138">#4138</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/cacbc9ff45c551c2f20c6b307800f7ff7ee07db2"><code>cacbc9f</code></a>
Add WhatsApp icon (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4142">#4142</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/3944311bed46f956e12e79a8eb0c32084eeeaea6"><code>3944311</code></a>
Use a page-specific counter for <code>\&lt;Tabs&gt;</code> IDs (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4133">#4133</a>)</li>
<li><a
href="https://github.com/withastro/starlight/commit/9b8221c0d85903437a26c81d1de5bd9a95a9baa2"><code>9b8221c</code></a>
Remove Vitest native config loader warnings (<a
href="https://github.com/withastro/starlight/tree/HEAD/packages/starlight/issues/4129">#4129</a>)</li>
<li>See full diff in <a
href="https://github.com/withastro/starlight/commits/@astrojs/starlight@0.41.9/packages/starlight">compare
view</a></li>
</ul>
</details>
<br />

Updates `astro` from 7.2.4 to 7.2.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/releases">astro's
releases</a>.</em></p>
<blockquote>
<h2>astro@7.2.9</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17846">#17846</a>
<a
href="https://github.com/withastro/astro/commit/b44118001c8f9b4f05ccd004cc3b4d42a8da8bfc"><code>b441180</code></a>
Thanks <a
href="https://github.com/Princesseuh"><code>@​Princesseuh</code></a>! -
Fixes importing <code>.html</code> files not being typed outside of
<code>.astro</code> files</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17831">#17831</a>
<a
href="https://github.com/withastro/astro/commit/32e8b44c25a80759f0ed0b47551351331584f779"><code>32e8b44</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes a crash when requesting <code>/index.html</code> against a
dynamic page route like <code>[slug].astro</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17841">#17841</a>
<a
href="https://github.com/withastro/astro/commit/c35448ec8e53fc0f93798840953185c9facaa39b"><code>c35448e</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes SSR manifest containing stale <code>entryModules</code>
references to prerender-only chunks that no longer exist in the final
build output</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17847">#17847</a>
<a
href="https://github.com/withastro/astro/commit/eb87a2391679ffa6fa25c61701569a474aa2e548"><code>eb87a23</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
<code>set:text</code> escaping in MDX script and style elements</p>
</li>
</ul>
<h2>astro@7.2.8</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17837">#17837</a>
<a
href="https://github.com/withastro/astro/commit/ecb4082131490b4fe9a56aa44fda84b54ef8967b"><code>ecb4082</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! -
Updates the minimum supported version of Sharp to 0.35.4</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17786">#17786</a>
<a
href="https://github.com/withastro/astro/commit/db7c53b1707856866e06cdeeef1aa4ae3598b1f2"><code>db7c53b</code></a>
Thanks <a
href="https://github.com/gameroman"><code>@​gameroman</code></a>! -
Replaces the internal <code>find-process</code> dependency with a
smaller, lighter alternative</p>
</li>
</ul>
<h2>astro@7.2.7</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17415">#17415</a>
<a
href="https://github.com/withastro/astro/commit/55d38c868b7cbf2266649929c60ddf442abe674f"><code>55d38c8</code></a>
Thanks <a
href="https://github.com/iseraph-dev"><code>@​iseraph-dev</code></a>! -
Deserializes each route once when loading the SSR manifest</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17772">#17772</a>
<a
href="https://github.com/withastro/astro/commit/023b48b139a2c40420b340f61b53a62b47a557e5"><code>023b48b</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
route selection for normalized request paths in adapter and development
request handling</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17819">#17819</a>
<a
href="https://github.com/withastro/astro/commit/633855b0cabd55cc7b913eb556a739a6a2d93dd2"><code>633855b</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! -
Updates generated and default Cloudflare <code>compatibility_date</code>
values to match the installed runtime and requires Wrangler
<code>^4.125.0</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17813">#17813</a>
<a
href="https://github.com/withastro/astro/commit/ae26d18c71515c47ecbd7e1ffe5c5dfc29fbd613"><code>ae26d18</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
<code>rewrite()</code> and <code>next(payload)</code> for GET and HEAD
requests with host-provided bodies</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17816">#17816</a>
<a
href="https://github.com/withastro/astro/commit/a0d2fe3af25a25bc9b808070f25886c37d5be6fc"><code>a0d2fe3</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes the experimental <code>svgOptimizer</code> not generating unique
per-file ID prefixes when using SVGO's <code>prefixIds</code> plugin</p>
</li>
</ul>
<h2>astro@7.2.6</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17812">#17812</a>
<a
href="https://github.com/withastro/astro/commit/29af6da5c11aff673133f96df029f40345674f0e"><code>29af6da</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
a bug where <code>new FetchState(request)</code> could fail in
development when server dependencies were optimized</li>
</ul>
<h2>astro@7.2.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17758">#17758</a>
<a
href="https://github.com/withastro/astro/commit/5f419e25c570002a2ce0e10a973aa13336016b0c"><code>5f419e2</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes a bug where <code>experimental_getFontFileURL()</code> rejected
valid font URLs when using the Cloudflare adapter</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17416">#17416</a>
<a
href="https://github.com/withastro/astro/commit/493796b4c318b19985eccaac7a11aa7b787e1efe"><code>493796b</code></a>
Thanks <a
href="https://github.com/iseraph-dev"><code>@​iseraph-dev</code></a>! -
Skips no-op pathname writes when normalizing SSR request URLs</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17712">#17712</a>
<a
href="https://github.com/withastro/astro/commit/bd374b7507de8d706c845946fd847e76de6fc06b"><code>bd374b7</code></a>
Thanks <a
href="https://github.com/fkatsuhiro"><code>@​fkatsuhiro</code></a>! -
Updates deprecation messages target from Astro 7 to 8</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17719">#17719</a>
<a
href="https://github.com/withastro/astro/commit/dac17688f691c6cecdff969aa48523bf17fc0657"><code>dac1768</code></a>
Thanks <a
href="https://github.com/astrobot-houston"><code>@​astrobot-houston</code></a>!
- Fixes session ID validation to reject non-UUID cookie values before
using them as storage keys</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17770">#17770</a>
<a
href="https://github.com/withastro/astro/commit/84eb7e7db99573b80c339efe0392d959e7a9b6cf"><code>84eb7e7</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes <code>--mode</code>, <code>--site</code>, <code>--base</code>,
<code>--out-dir</code>, <code>--verbose</code>, <code>--silent</code>,
and <code>--open</code> flags being silently dropped when using
<code>astro dev --background</code> or <code>astro preview
--background</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17713">#17713</a>
<a
href="https://github.com/withastro/astro/commit/d035290a14afac8834885b727327a7f44d3a3a48"><code>d035290</code></a>
Thanks <a
href="https://github.com/wakqasahmed"><code>@​wakqasahmed</code></a>! -
Fixes <code>content-modules.mjs</code> not removing entries for deleted
or renamed content files, which could cause Vite to attempt to resolve
non-existent modules</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md">astro's
changelog</a>.</em></p>
<blockquote>
<h2>7.2.9</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17846">#17846</a>
<a
href="https://github.com/withastro/astro/commit/b44118001c8f9b4f05ccd004cc3b4d42a8da8bfc"><code>b441180</code></a>
Thanks <a
href="https://github.com/Princesseuh"><code>@​Princesseuh</code></a>! -
Fixes importing <code>.html</code> files not being typed outside of
<code>.astro</code> files</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17831">#17831</a>
<a
href="https://github.com/withastro/astro/commit/32e8b44c25a80759f0ed0b47551351331584f779"><code>32e8b44</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes a crash when requesting <code>/index.html</code> against a
dynamic page route like <code>[slug].astro</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17841">#17841</a>
<a
href="https://github.com/withastro/astro/commit/c35448ec8e53fc0f93798840953185c9facaa39b"><code>c35448e</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes SSR manifest containing stale <code>entryModules</code>
references to prerender-only chunks that no longer exist in the final
build output</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17847">#17847</a>
<a
href="https://github.com/withastro/astro/commit/eb87a2391679ffa6fa25c61701569a474aa2e548"><code>eb87a23</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
<code>set:text</code> escaping in MDX script and style elements</p>
</li>
</ul>
<h2>7.2.8</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17837">#17837</a>
<a
href="https://github.com/withastro/astro/commit/ecb4082131490b4fe9a56aa44fda84b54ef8967b"><code>ecb4082</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! -
Updates the minimum supported version of Sharp to 0.35.4</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17786">#17786</a>
<a
href="https://github.com/withastro/astro/commit/db7c53b1707856866e06cdeeef1aa4ae3598b1f2"><code>db7c53b</code></a>
Thanks <a
href="https://github.com/gameroman"><code>@​gameroman</code></a>! -
Replaces the internal <code>find-process</code> dependency with a
smaller, lighter alternative</p>
</li>
</ul>
<h2>7.2.7</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17415">#17415</a>
<a
href="https://github.com/withastro/astro/commit/55d38c868b7cbf2266649929c60ddf442abe674f"><code>55d38c8</code></a>
Thanks <a
href="https://github.com/iseraph-dev"><code>@​iseraph-dev</code></a>! -
Deserializes each route once when loading the SSR manifest</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17772">#17772</a>
<a
href="https://github.com/withastro/astro/commit/023b48b139a2c40420b340f61b53a62b47a557e5"><code>023b48b</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
route selection for normalized request paths in adapter and development
request handling</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17819">#17819</a>
<a
href="https://github.com/withastro/astro/commit/633855b0cabd55cc7b913eb556a739a6a2d93dd2"><code>633855b</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! -
Updates generated and default Cloudflare <code>compatibility_date</code>
values to match the installed runtime and requires Wrangler
<code>^4.125.0</code></p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17813">#17813</a>
<a
href="https://github.com/withastro/astro/commit/ae26d18c71515c47ecbd7e1ffe5c5dfc29fbd613"><code>ae26d18</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
<code>rewrite()</code> and <code>next(payload)</code> for GET and HEAD
requests with host-provided bodies</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17816">#17816</a>
<a
href="https://github.com/withastro/astro/commit/a0d2fe3af25a25bc9b808070f25886c37d5be6fc"><code>a0d2fe3</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes the experimental <code>svgOptimizer</code> not generating unique
per-file ID prefixes when using SVGO's <code>prefixIds</code> plugin</p>
</li>
</ul>
<h2>7.2.6</h2>
<h3>Patch Changes</h3>
<ul>
<li><a
href="https://redirect.github.com/withastro/astro/pull/17812">#17812</a>
<a
href="https://github.com/withastro/astro/commit/29af6da5c11aff673133f96df029f40345674f0e"><code>29af6da</code></a>
Thanks <a
href="https://github.com/matthewp"><code>@​matthewp</code></a>! - Fixes
a bug where <code>new FetchState(request)</code> could fail in
development when server dependencies were optimized</li>
</ul>
<h2>7.2.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17758">#17758</a>
<a
href="https://github.com/withastro/astro/commit/5f419e25c570002a2ce0e10a973aa13336016b0c"><code>5f419e2</code></a>
Thanks <a
href="https://github.com/apps/astro-factory"><code>@​astro-factory</code></a>!
- Fixes a bug where <code>experimental_getFontFileURL()</code> rejected
valid font URLs when using the Cloudflare adapter</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17416">#17416</a>
<a
href="https://github.com/withastro/astro/commit/493796b4c318b19985eccaac7a11aa7b787e1efe"><code>493796b</code></a>
Thanks <a
href="https://github.com/iseraph-dev"><code>@​iseraph-dev</code></a>! -
Skips no-op pathname writes when normalizing SSR request URLs</p>
</li>
<li>
<p><a
href="https://redirect.github.com/withastro/astro/pull/17712">#17712</a>
<a
href="https://github.com/withastro/astro/commit/bd374b7507de8d706c845946fd847e76de6fc06b"><code>bd374b7</code></a>
Thanks <a
href="https://github.com/fkatsuhiro"><code>@​fkatsuhiro</code></a>! -
Updates deprecation messages target from Astro 7 to 8</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/withastro/astro/commit/ad7a33229efec04c8ff6c7b450e480ec8e9d2c57"><code>ad7a332</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17845">#17845</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/eb87a2391679ffa6fa25c61701569a474aa2e548"><code>eb87a23</code></a>
Escape set:text values in Astro JSX (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17847">#17847</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/b44118001c8f9b4f05ccd004cc3b4d42a8da8bfc"><code>b441180</code></a>
fix(astro): type .html imports outside of .astro files (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17846">#17846</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/32e8b44c25a80759f0ed0b47551351331584f779"><code>32e8b44</code></a>
Guard against stripping /index.html when stripped pathname no longer
matches ...</li>
<li><a
href="https://github.com/withastro/astro/commit/c35448ec8e53fc0f93798840953185c9facaa39b"><code>c35448e</code></a>
fix(build): strip prerender-only entry specifiers from SSR manifest (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17841">#17841</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/7cadf1055a61c85d0b05f3c7d8c709f7faa5cf0d"><code>7cadf10</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17826">#17826</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/ecb4082131490b4fe9a56aa44fda84b54ef8967b"><code>ecb4082</code></a>
Update Sharp to 0.35.4 (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17837">#17837</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/8bf6f1af679947ffbc702db26425f3f13a7f3040"><code>8bf6f1a</code></a>
chore: split v5/v6 changelogs (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17825">#17825</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/db7c53b1707856866e06cdeeef1aa4ae3598b1f2"><code>db7c53b</code></a>
chore(deps): replace &quot;find-process&quot; with a smaller, lighter
alternative (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17786">#17786</a>)</li>
<li><a
href="https://github.com/withastro/astro/commit/eface15c86b38d44e94b841c9d1e27394c470600"><code>eface15</code></a>
[ci] release (<a
href="https://github.com/withastro/astro/tree/HEAD/packages/astro/issues/17815">#17815</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/withastro/astro/commits/astro@7.2.9/packages/astro">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 21:26:53 -07:00
Maximilian Roos 59211c6e64 docs(hook): fix the --base example in the pre-start upstream snippet (#3955)
`wt hook --help`'s `pre-start` example illustrated the `upstream`
template variable with `wt switch --create feature origin/feature`,
which the CLI rejects — `wt switch` takes no positional base:

```console
$ wt switch --create feature origin/feature
error: unexpected argument 'origin/feature' found
```

Now `--base origin/feature`. The template snippet it annotates is
unaffected.

> _This was written by Claude Code on behalf of max-sixty_

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 20:41:45 -07:00
Maximilian Roos 541f6d204d Revert "feat(list): add [list] sort for configurable row order" (#3952)
(not determinate, but want to rethink whether this is useful)
2026-08-29 00:29:18 +00:00
Maximilian Roos 1b4c492ee8 fix(switch): decide branch tracking for --create, whatever the git config says (#3950)
## Problem

`wt switch --create <name> --base origin/<branch>` must not leave the
new branch tracking a differently-named base: under `push.default =
upstream` a bare `git push` then pushes the new work onto the base
branch ([#713](https://github.com/max-sixty/worktrunk/issues/713)).

Since #3913 that came from `-c branch.autoSetupMerge=simple`, injected
only when the user had not set the key. So the outcome still depended on
the user's git config, and two of git's five values reach a wrong
answer: `true` and `always` track a differently-named base (the #713
footgun is still live), while `false` and `inherit` deny a *same*-named
branch the tracking that is the point of it — including the DWIM `wt
switch feature` from `origin/feature`, which the docs promise is a
tracking branch.

## Solution

Force the `-c` instead of defaulting it, on every `git worktree add` `wt
switch` runs. `-c` outranks every config file, so one rule now decides
the upstream whatever the user has configured: a new branch tracks the
remote branch it starts from only when the two share a name. DWIM always
shares it, so it always tracks; `--create` from a differently-named base
gets no upstream.

Tests: `test_switch_create_from_remote_base_upstream` becomes a matrix
over all six `branch.autoSetupMerge` values (unset, simple, false,
inherit, true, always) × three base spellings (`origin/release`, the
bare `release` that resolves to it, and `refs/remotes/origin/staging`);
`test_switch_dwim_from_remote_tracks` pins the DWIM half over the values
that used to decline.

## Why not `--track` / `--no-track`

The obvious alternative is for `wt` to pick git's explicit flags from a
name comparison of its own. I built that first; it passed the full
suite, and it is wrong twice over.

<details>
<summary>Three reproduced defects in the explicit-flags
version</summary>

**The predicate is wrong.** `strip_remote_prefix` splits
`<remote>/<branch>` at the first slash, but git maps a remote-tracking
ref back to its branch through the *fetch refspec*. With a remote named
`team/fork`, the two disagree and the verdict inverts both ways:

```console
$ git remote add team/fork <url> && git fetch team/fork
$ wt switch --create fork/release --base team/fork/release
$ git config branch.fork/release.merge
refs/heads/release
```

A bare `git push` under `push.default = upstream` then lands on
`release` — #713, reintroduced. The same base with `--create release`
got *no* upstream, the opposite error. A refspec renaming into a
sub-namespace (`+refs/heads/*:refs/remotes/origin/mirror/*`) does the
same with no unusual remote name.

**`--track` is a hard demand where `simple` is best-effort.** In a
single-branch clone holding a hand-fetched ref, it fails the whole
command — after the branch name has already been taken:

```console
$ git clone --single-branch -b main <url> && git fetch origin release:refs/remotes/origin/release
$ wt switch --create release --base origin/release
fatal: cannot set up tracking information; starting point 'origin/release' is not a branch
```

That is the matching-name case, i.e. exactly what the feature exists
for, and it is the same "fails outright" class #3913 had just removed.

**Qualified spellings lose tracking.** `--base
refs/remotes/origin/release` and `--base remotes/origin/release` name
the same ref as `origin/release` but don't match `short_name`, so they
got `--no-track`.

</details>

All three come from `wt` computing the name match itself, which it
cannot do correctly — only git knows the refspec mapping. So `wt`
decides the *rule* and git applies it. The first two are now regression
tests (`test_switch_create_base_on_remote_with_slash`,
`test_switch_create_base_outside_fetch_refspec`), so a future
simplification to `--track` fails the suite rather than shipping.

## Verification

Measured end-to-end on scratch clones across all six
`branch.autoSetupMerge` values: a differing name gets no upstream and a
matching name tracks, identically in every one.

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 17:25:53 -07:00
Worktrunk Bot e9c839a194 feat(list): add [list] sort for configurable row order (#3933)
Row order in `wt list` and the `wt switch` picker was fixed — current
worktree first, primary second, then committer date descending — while
`[list] columns` already let a user pick and order the columns. This
adds `[list] sort`, a list of column names (most significant first, `-`
prefix for descending) that orders the rows, restricted to the columns
whose value is known before the table paints, per the maintainer's call
on #3922.

```toml
[list]
columns = ["path", "branch", "status"]
sort = ["path"]            # the reporter's case: the table mirrors the directory layout
# sort = ["-age"]          # oldest commit first
# sort = ["branch", "-age"]
```

Verified with `cargo run -- hook pre-merge --yes`: 4725/4728 passed, the
three failures being sandbox-environment artifacts that fail identically
on the base commit (a PowerShell profile and a Nushell autoload dir this
runner does have, and a `0664` umask). New coverage is 7 unit tests in
`src/commands/list/sort.rs` and 7 integration tests in
`tests/integration_tests/list_config.rs`.

Closes #3922

<details><summary>Design decisions</summary>

**Only skeleton-time columns are sortable** — `branch`, `path`,
`commit`, `age`, `message`, i.e. exactly the built-ins with no
background task. The rest (`status`, `ci`, `upstream`, the diff columns,
custom columns) stream in behind the first frame, so ordering on one
would mean either holding the table for a network round trip or
reordering rows under the cursor, which the progressive table doesn't
do. Naming one is an error that says why rather than a silently ignored
setting. `test_sortable_keys_are_exactly_the_task_free_columns` pins the
correspondence to `ColumnKind::required_tasks`, so a new task-free
column can't quietly become unsortable (or a streamed one sortable).

**A spec replaces the default order outright, pinned prefix included.**
`sort = ["path"]` exists to make the table read in path order; keeping
the current and primary worktrees pinned to the top would defeat exactly
that. Newest-commit-first survives as the final tiebreak, which means
the empty spec and the fallback are one code path — and rows a spec
can't separate keep the order they have today, e.g. branch-only rows
under `sort = ["path"]`, since only worktrees have a path.

**`age` ascending is newest first.** The column counts up from the
commit date, so the smallest age is the newest commit — the same
direction as the default order — and `-age` puts the oldest on top.

**Rows sort within their group, never across it**: worktrees, then
branch-only rows, then remote rows, as before.

**`--format json` follows `sort`**, unlike `columns`. The reasons
`columns` is excluded are the every-field contract and not letting a
display setting decide whether a machine-readable call reaches a forge
(#3787); reordering an array narrows no payload and adds no fetch, so
neither applies.

**A bad key aborts `wt list` and degrades the picker**, the same fork
`[list] columns` takes — the picker can't surface an abort mid-render,
so it stashes a warning and falls back to the default order.

</details>

<details><summary>Changes</summary>

- `src/commands/list/sort.rs` (new) — `SortKey`/`SortTerm`,
`parse_sort_spec`, and the `compare` comparator over per-row
`SortFacts`.
- `src/commands/list/collect/mod.rs` — parse the spec before the sort,
thread it through `sort_worktrees_with_cache` and the new
`sort_branch_rows` (which replaces the generic
`sort_by_timestamp_desc_with_cache`; both call sites passed the same
`Vec<(String, String)>`). Facts are precomputed per row, so the
commit-details lookup stays one per row rather than one per comparison.
- `src/config/user/sections.rs` — `ListConfig::sort`, merging wholesale
like `columns` (a spec is an ordering; merging terms across layers would
invent an order neither layer asked for).
- `src/cli/mod.rs` — a "Row order" section under `wt config`'s `[list]`
docs; the mirrors under `docs/`, `skills/`, `plugins/`,
`dev/config.example.toml`, and the help snapshots are regenerated.

</details>

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-08-28 17:01:35 -07:00
Maximilian Roos 48b8b3802a Stabilize mature interfaces and remove retired config (#3949)
Marks the mature `wt step eval`, `wt step for-each`, `wt step prune`,
LLM summary, config-state vars, and commit-template append interfaces as
stable.

Removes compatibility for the long-deprecated `template-file` and
`squash-template-file` settings, and retires the special migration for
the already-ignored `switch.picker.timeout-ms`. These keys now follow
the ordinary unknown-field path; the troubleshooting guide explains how
to move file contents into inline templates. The change also removes the
redundant project CI-platform accessor.

Validation: `cargo run -- hook pre-merge --yes` (4,710 tests passed; one
skipped), plus a production documentation build.

> _This was written by Codex on behalf of @max-sixty_
2026-08-28 11:08:04 -07:00
Maximilian Roos ec7db83136 docs(config): shorten default-branch override guidance (#3948)
Shortens the default-branch override guidance added in #3947 while
keeping the clone-local scope, linked-worktree behavior, and
local-branch requirement.

> _This was written by Codex on behalf of max-sixty_
2026-08-28 09:26:31 -07:00
Worktrunk Bot 4e9f706e94 fix(switch): track a remote base only when the names match (#3913)
## Problem

`wt switch --create <name> --base origin/<branch>` ran `git branch
--unset-upstream` afterwards, so the new branch could not push to the
base (#713). Nothing said so, and #3912 was the third thread to arrive
at the manual `git push --set-upstream origin <branch>` step. Reviewing
the docs for it (@max-sixty,
[comment](https://github.com/max-sixty/worktrunk/pull/3913#issuecomment-5427192580))
surfaced the better question: should this just copy git?

## Solution

Git already ships the rule the unset was approximating —
`branch.autoSetupMerge = simple` sets tracking only when the start point
is a remote-tracking branch **and** the new branch has the same name as
the remote branch. `wt` now defaults to it on the `--create` paths
instead of undoing git's `true` after the fact, and documents it in one
paragraph of `wt switch`'s `after_long_help`.

Three things follow:

- **A same-named branch keeps its tracking.** `--create release --base
origin/release` was getting unset too, though the tracking it lost was
correct.
- **An explicit `branch.autoSetupMerge` is honoured.** `wt` picks a
different default; it no longer overrides the setting.
- **`--create` off a remote base no longer fails outright** for anyone
whose config left git no upstream to unset. Under `branch.autoSetupMerge
= false` the command exited 128 — `fatal: branch 'feature' has no
upstream information` — after creating the branch and the worktree.

The DWIM paths (`wt switch release` when only `origin/release` exists)
are unaffected: they create `feature` from `origin/feature`, where
`simple` and `true` agree. `simple` needs git ≥ 2.37;
`MINIMUM_GIT_VERSION` is 2.43.

## Testing

`test_switch_create_from_remote_base_no_upstream` becomes
`test_switch_create_from_remote_base_upstream`, covering the same #713
property plus the three cases above. Full suite via `cargo run -- hook
pre-merge --yes`.

<details><summary>Verification (git 2.55.0, debug <code>wt</code>, bare
<code>origin</code> with <code>main</code> and
<code>release</code>)</summary>

Behavior, before and after, in a clone with no local `release`:

```console
$ wt switch --create feature --base origin/release --no-cd   # default config
$ git branch -vv
+ feature 9e98944 (…/src.feature) r2                          # no upstream — unchanged

$ git config branch.autoSetupMerge false                      # before: exit 1
$ wt switch --create feature --base origin/release --no-cd
✓ Created branch feature from origin/release and worktree @ …/src.feature
+ feature 9e98944 (…/src.feature) r2

$ git config branch.autoSetupMerge always                     # before: unset anyway
$ wt switch --create feature --base origin/release --no-cd
+ feature 9e98944 (…/src.feature) [origin/release] r2

$ wt switch release --no-cd                                   # DWIM, unchanged
+ release 9e98944 (…/src.release) [origin/release] r2
```

The failure this removes, on the pre-change binary:

```console
$ git config branch.autoSetupMerge false
$ wt switch --create feature --base origin/release --no-cd
✗ git branch --unset-upstream -- feature failed (exit 128)
  fatal: branch 'feature' has no upstream information
$ git worktree list
/tmp/asm2/src         257f014 [main]
/tmp/asm2/src.feature 653f0dd [feature]                       # created, then the command failed
```

`branch.autoSetupMerge` semantics, straight from git, no `wt` involved:

```console
$ git switch -c t-true origin/release                              # default `true`
* t-true aeb85e2 [origin/release] r2
$ git -c branch.autoSetupMerge=simple switch -c t-simple origin/release
* t-simple aeb85e2 r2
$ git -c branch.autoSetupMerge=simple switch -c release origin/release
* release aeb85e2 [origin/release] r2
```

And that `git worktree add -b` — the invocation `wt` actually runs —
honours it the same way:

```console
$ git -c branch.autoSetupMerge=simple worktree add -b wt-diff /tmp/asm/wt-diff origin/release
+ wt-diff aeb85e2 (/tmp/asm/wt-diff) r2
$ git -c branch.autoSetupMerge=simple worktree add -b foo /tmp/asm/foo origin/foo
+ foo aeb85e2 (/tmp/asm/foo) [origin/foo] r2
```

</details>

---

Refs #3912, #713 — automated triage

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-08-28 08:30:52 -07:00
Worktrunk Bot af04e80db0 docs(config): document the machine-local default-branch override (#3947)
## Problem

`wt config state default-branch set` writes `worktrunk.default-branch`
to the repository's *local* git config, so an override lives in
`.git/config` — never committed, never pushed, and shared across every
linked worktree of the clone. #3946 reports that nothing says so: the
detection list names the config key, but the docs never state that
setting it is a supported override. That matters for anyone working in a
repository they don't own (a client's, an upstream project's) whose
remote `HEAD` names a branch other than the one they integrate against —
the file-based config the docs point at is exactly what they can't add.
There is no default-branch key in `wt.toml` or user config either, so
the git config key is the only route.

## Solution

Two additions, no behavior change:

- `src/cli/config.rs` — a new "Overriding without a config file" section
in `wt config state default-branch`'s `after_long_help`, covering where
the value is written, that it stays on one machine, that it applies to
every linked worktree, that it's the only override there is, and that
the branch has to be checked out locally or `set` and every subsequent
`wt list` warn about it. The Detection section's existing drift sentence
gains the "expected for a deliberate override, and inspection-only
either way" qualifier rather than the new section restating it.
- `docs/src/content/docs/tips-patterns.md` — a matching tip beside the
existing "Reuse `default-branch`" section, which is where the reporter
looked.

The generated mirrors under `docs/`, `skills/`, and `plugins/` come from
the sync test.

## Testing

Verified against a scratch clone with the built binary: `wt config state
default-branch set integration` writes `[worktrunk] default-branch =
integration`, `git config --show-scope` reports `local`, and reading it
back from a linked worktree returns `integration`. With
`origin/integration` fetched but no local `integration`, `set` warns `▲
Branch integration does not exist locally` and every `wt list` repeats
`▲ Configured default branch integration does not exist locally` with
the `clear` hint; the summary line gains `1 ahead` only once
`integration` is checked out. The console example's output line is the
command's real success message.

`cargo test --test integration test_docs_are_in_sync` (twice —
regenerate, then clean), `cargo insta test --accept --test integration
-- test_help`, and `cargo fmt --check` all pass.

<details><summary>Scratch-repo verification</summary>

```console
$ wt config state default-branch set integration
✓ Set default branch to integration
$ git config --show-scope --get worktrunk.default-branch
local	integration
$ git worktree add -q ../wt-int integration && cd ../wt-int && wt config state default-branch
integration
```

Stale-override case, in a clone with only `origin/integration`:

```console
$ wt config state default-branch set integration
▲ Branch integration does not exist locally
✓ Set default branch to integration
$ wt list
▲ Configured default branch integration does not exist locally
↳ To reset, run wt config state default-branch clear
```

</details>

---
Closes #3946 — automated triage

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-08-28 08:29:55 -07:00
Worktrunk Bot 1133df8804 fix(switch): resolve pr:N before pre-switch hooks, and set pr_* for same-repo PRs (#3941)
Fixes #3934.

`wt switch pr:<n>` resolved the PR through the forge inside
`plan_switch`, which runs *after* `pre-switch` — so the hook got the raw
`pr:3933` token as `branch` and `target`, and no `target_worktree_path`
even when the PR's branch was already checked out. Separately,
`pr_number` / `pr_url` were read back off `CreationMethod::ForkRef`,
which a same-repo PR never produces, so the reporter's output shows both
unset in *every* hook although the help text promises them. This
resolves `pr:`/`mr:` before the hooks (the rule `-`/`@`/`^` already
follow, #2310) and gives the PR identity its own field, carried from
resolution through the plan into `pre-switch`, `pre-start`,
`post-start`, and `post-switch` alike.

Verified with two integration tests that fail on `main` (the
`pre-switch` hook aborts the switch with `undefined value`) and pass
here; full unit + integration suites, clippy `--all-targets
--all-features`, rustdoc, and pre-commit are green locally.

<details><summary>What each variable does now</summary>

For `wt switch pr:3933` where the PR's branch is
`feat/issue-3922-list-sort`:

| Variable | Before | After |
|---|---|---|
| `branch` / `target` in `pre-switch` | `pr:3933` |
`feat/issue-3922-list-sort` |
| `target_worktree_path` in `pre-switch` | unset even when that branch
had a worktree | set when it does |
| `pr_number` / `pr_url` (same-repo PR, all hooks) | unset | `3933` /
the PR URL |
| `pr_number` / `pr_url` in `pre-switch` (fork PR) | unset | set |

`worktree_path` in `pre-switch` is deliberately unchanged: on a switch
that *creates* a worktree there is no destination directory yet, so it
stays on the source. The help text claimed it was "the destination"
without that caveat —
[`src/cli/mod.rs`](https://github.com/max-sixty/worktrunk/blob/4c5e87eff3ebec31cdf6be1ad905bfda0bceeee8/src/cli/mod.rs#L1704)
now says which case is which and points at `pre-start` for work that
needs the new worktree (which is what the reporter's own `mise-trust`
hook already does).

</details>

<details><summary>Ordering consequences</summary>

The forge lookup and its `git fetch` now happen before `pre-switch`
runs, so a hook that aborts the switch aborts it after the PR was
fetched. That is inherent to the fix — the branch name is the forge's
answer. The lookup is still bounded to the argument form that asked for
it (`resolve_ref_shortcut_target` returns `None` for anything that isn't
`pr:`/`mr:`), and the resolved target is threaded into `plan_switch`, so
the forge is queried exactly once as before.

The hook-approval gate used to be what surfaced a malformed
`.config/wt.toml` before provider selection —
`configured_forge_platform` reports an unparsable config as "unset",
which would route an intended `forge.platform` override to the wrong
CLI. With the resolution now first, that config load is explicit at the
top of `resolve_ref_shortcut_target` rather than an accident of ordering
(`test_switch_pr_malformed_project_config_bails_before_provider_selection`
covers it and caught the regression).

`CreationMethod::ForkRef::ref_url` became dead once the identity moved
to its own field, so it is removed rather than left with an `#[allow]`.
`SwitchResult::Created`'s `pr_number` / `pr_url` are removed for the
same reason: the pipeline applies the identity from the resolved
argument, which is the only channel that also serves a switch onto an
existing worktree.

</details>

---------

Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
2026-08-27 20:00:13 -07:00
Maximilian Roos 03ae3b6f60 Polish terminal gutters and shell strings (#3942)
The homepage merge transcript rendered its ANSI gutter as separate
glyph-height blocks, leaving visible gaps between consecutive rows.
Quoted prompts in the parallel-agent example also shared the same color
as ordinary shell arguments.

This extends the existing gutter spans to fill the line box and adds a
shell-specific quoted-string role to the paired syntax themes. The role
stays scoped to Bash strings, retains distinct accessible colors in both
themes, and leaves TOML strings unchanged. WebKit coverage pins the
complete gutter rail, line geometry, scope boundary, theme contrast, and
mobile containment.

> _This was written by Codex on behalf of max-sixty_
2026-08-27 18:42:31 -07:00
Maximilian Roos d6300f1c1b Color homepage command comparison (#3939)
Extends the site’s semantic Bash colors to the homepage Worktrunk /
Plain git comparison in both responsive layouts. The build-only
transform keeps canonical Markdown, README, and skill mirrors plain
while preserving command text; WebKit verifies every colored token meets
WCAG contrast in both themes.

> _This was written by Codex on behalf of @max-sixty_
2026-08-27 17:40:02 -07:00
Maximilian Roos 750972d2c5 Restore docs code-block hierarchy (#3938)
The Astro site kept the documentation content but lost some of the old
site's visual hierarchy: shell commands flattened into one color,
generated Command References had no semantic roles, dim terminal rows
were barely distinguishable, and file excerpts read like generic
untitled code. This restores those distinctions at their natural
rendering boundaries.

## What changed

- Add paired Worktrunk Shiki themes for source and shell syntax,
preserving the established warm palette with accessible light and dark
contrast.
- Mark generated Command Reference fences at their producer and render
Clap commands, headings, options, values, metadata, possible-value rows,
continued Usage signatures, and variadic metavariables semantically.
- Replace opacity-based terminal dimming with explicit accessible colors
while retaining ANSI hues.
- Present file excerpts as compact attached path tabs with a restrained
craft accent.
- Cover all 30 generated references, every public route at 320px and
393px in both themes, unrelated-fence false positives, command-only
copying, horizontal containment, and WCAG contrast in real WebKit
rendering.

## Mobile demos

The light and dark mobile homepage demos were already re-recorded and
published in `max-sixty/worktrunk-assets` at `4de7bde` by #3936. This
change verifies responsive source selection and viewport containment
against those current recordings, so another lossy re-record is
unnecessary.

## Testing

- `cargo run -- hook pre-merge --yes` — 4,722 passed, 1 skipped
- `cargo test --test integration test_docs_are_in_sync -- --nocapture`
- `npm --prefix docs test`
- `npm --prefix docs run build`
- `npm --prefix docs run test:site`
- `npm --prefix docs run check`
- independent systematic, adversarial, and from-scratch architecture
reviews

> _This was written by Codex on behalf of max-sixty_
2026-08-27 16:07:46 -07:00