From 64850d9b437afde81c7847b8eeffba5b666011d4 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 1 Sep 2026 16:22:54 -0700 Subject: [PATCH 01/17] list: name the hidden columns in the footer, and document width-responsive dropping (#24) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The summary footer counted the columns the terminal was too narrow for ("3 columns hidden") without saying which, and the list page never explained that the table drops columns to fit at all — `[list] columns` was documented only on the config page. The footer now names them ("hidden: Path, Commit, Message"), so the line says what a wider terminal would add. `LayoutConfig` carries `hidden_columns: Vec` (headers, in display order) in place of the count. A new paragraph under `## Columns` covers the drop order, the footer, `[list] columns`, and `--format=json` showing everything. Co-Authored-By: Claude Fable 5.1 --- README.md | 2 +- docs/src/content/docs/config.md | 2 +- docs/src/content/docs/list.md | 8 ++-- docs/src/content/docs/tips-patterns.md | 2 +- docs/src/content/docs/worktrunk.md | 2 +- docs/src/generated/terminal-styles.json | 24 +++++----- .../skills/worktrunk/reference/README.md | 2 +- .../skills/worktrunk/reference/config.md | 2 +- .../skills/worktrunk/reference/list.md | 8 ++-- .../worktrunk/reference/tips-patterns.md | 2 +- .../skills/worktrunk/reference/worktrunk.md | 2 +- skills/worktrunk/reference/config.md | 2 +- skills/worktrunk/reference/list.md | 8 ++-- skills/worktrunk/reference/tips-patterns.md | 2 +- skills/worktrunk/reference/worktrunk.md | 2 +- src/cli/mod.rs | 8 ++-- src/commands/list/collect/mod.rs | 2 +- src/commands/list/layout.rs | 32 +++++++++---- src/commands/list/mod.rs | 48 +++++++++---------- src/commands/list/render.rs | 8 ++-- ...tegration_tests__help__help_list_long.snap | 8 ++-- ...tion_tests__help__help_list_narrow_80.snap | 13 +++-- ...egration_tests__list__quickstart_list.snap | 3 +- ...tion_tests__list__readme_example_list.snap | 3 +- ...s__list__readme_example_list_branches.snap | 3 +- ...tests__list__readme_example_list_full.snap | 3 +- ...sts__list__readme_example_list_marker.snap | 3 +- ...tests__list__tips_dev_server_workflow.snap | 3 +- ...egration_tests__list_layout__width_30.snap | 16 +++++-- 29 files changed, 134 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 0654012e90..459946671c 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ $ wt list @ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… ^ main ^⇡ ⇡1 0e631ad 1d Initial commit -○ Showing 2 worktrees, 1 with changes, 1 ahead, 1 column hidden +○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` The `@` marks the current worktree. `+` means staged changes, `↑1` means 1 commit ahead of main, `⇡` means unpushed commits. diff --git a/docs/src/content/docs/config.md b/docs/src/content/docs/config.md index b0a22c9e63..36ba9e4087 100644 --- a/docs/src/content/docs/config.md +++ b/docs/src/content/docs/config.md @@ -1387,7 +1387,7 @@ $ wt list + review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… + wip-docs ? – +1 33323bc 1d Initial commit -○ Showing 4 worktrees, 2 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path ``` ### Use cases diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index 6015fca402..53d79ddea5 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -35,7 +35,7 @@ $ wt list + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… + fix-typos _| | 41ee083 4d Merge fix-auth: h… -○ Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path ``` Include CI status and LLM summaries: @@ -48,7 +48,7 @@ $ wt list --full + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 + fix-typos _| | #410 41ee083 -○ Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message ``` Include branches that don't have worktrees: @@ -63,7 +63,7 @@ $ wt list --branches --full / exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922 / wip /↕ ↑1 ↓1 +33 Start API documentation b40716d -○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message ``` Output as JSON for scripting: @@ -93,6 +93,8 @@ $ wt list --format=json The `main` header label is used regardless of the default branch's actual name. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. + `main↕` and `main…±` measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local `main` trails `origin/main`, a branch reads as ahead of the real mainline, not of a stale local checkout. The `↑`/`↓`/`↕` Status symbols derive from these counts, so they track the upstream tip too. ### Gutter diff --git a/docs/src/content/docs/tips-patterns.md b/docs/src/content/docs/tips-patterns.md index d7b17b3a67..5c9be6000c 100644 --- a/docs/src/content/docs/tips-patterns.md +++ b/docs/src/content/docs/tips-patterns.md @@ -72,7 +72,7 @@ $ wt list + fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68 + fix-typos _| | http://localhost:14301 41ee083 -○ Showing 4 worktrees, 2 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path, Age, Message ``` diff --git a/docs/src/content/docs/worktrunk.md b/docs/src/content/docs/worktrunk.md index 058e90c0be..d00a05dc70 100644 --- a/docs/src/content/docs/worktrunk.md +++ b/docs/src/content/docs/worktrunk.md @@ -186,7 +186,7 @@ $ wt list @ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… ^ main ^⇡ ⇡1 0e631ad 1d Initial commit -○ Showing 2 worktrees, 1 with changes, 1 ahead, 1 column hidden +○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` diff --git a/docs/src/generated/terminal-styles.json b/docs/src/generated/terminal-styles.json index b71571cdd6..2db254331d 100644 --- a/docs/src/generated/terminal-styles.json +++ b/docs/src/generated/terminal-styles.json @@ -253,11 +253,11 @@ "classes": [ "wt-terminal-dim" ], - "text": "Showing 2 worktrees, 1 with changes, 1 ahead, 1 column hidden" + "text": "Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path" } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati…\n^ main ^⇡ ⇡1 0e631ad 1d Initial commit\n\n○ Showing 2 worktrees, 1 with changes, 1 ahead, 1 column hidden" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati…\n^ main ^⇡ ⇡1 0e631ad 1d Initial commit\n\n○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path" }, { "lines": [ @@ -710,11 +710,11 @@ "classes": [ "wt-terminal-dim" ], - "text": "Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden" + "text": "Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path" } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests\n^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h…\n+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token…\n+ fix-typos _| | 41ee083 4d Merge fix-auth: h…\n\n○ Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests\n^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h…\n+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token…\n+ fix-typos _| | 41ee083 4d Merge fix-auth: h…\n\n○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path" }, { "lines": [ @@ -1084,11 +1084,11 @@ "classes": [ "wt-terminal-dim" ], - "text": "Showing 4 worktrees, 2 with changes, 2 ahead, 1 column hidden" + "text": "Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path" } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ main ^⇡ ⇡1 33323bc 1d Initial commit\n+ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp…\n+ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com…\n+ wip-docs ? – +1 33323bc 1d Initial commit\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead, 1 column hidden" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ main ^⇡ ⇡1 33323bc 1d Initial commit\n+ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp…\n+ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com…\n+ wip-docs ? – +1 33323bc 1d Initial commit\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path" }, { "lines": [ @@ -1895,11 +1895,11 @@ "classes": [ "wt-terminal-dim" ], - "text": "Showing 4 worktrees, 2 with changes, 2 ahead, 3 columns hidden" + "text": "Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path, Age, Message" } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ URL Commit\n@ main ? ^⇅ +5 ⇡1 ⇣1 http://localhost:12107 41ee083\n+ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 http://localhost:10703 6814f02\n+ fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68\n+ fix-typos _| | http://localhost:14301 41ee083\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead, 3 columns hidden" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ URL Commit\n@ main ? ^⇅ +5 ⇡1 ⇣1 http://localhost:12107 41ee083\n+ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 http://localhost:10703 6814f02\n+ fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68\n+ fix-typos _| | http://localhost:14301 41ee083\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path, Age, Message" }, { "lines": [ @@ -2313,11 +2313,11 @@ "classes": [ "wt-terminal-dim" ], - "text": "Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden" + "text": "Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message" } ] ], - "plain": " Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02\n^ main ^⇅ ⇡1 ⇣1 # 41ee083\n+ fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68\n+ fix-typos _| | #410 41ee083\n\n○ Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden" + "plain": " Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02\n^ main ^⇅ ⇡1 ⇣1 # 41ee083\n+ fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68\n+ fix-typos _| | #410 41ee083\n\n○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message" }, { "lines": [ @@ -2849,11 +2849,11 @@ "classes": [ "wt-terminal-dim" ], - "text": "Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden" + "text": "Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message" } ] ], - "plain": " Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02\n^ main ^⇅ ⇡1 ⇣1 # 41ee083\n+ fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68\n+ fix-typos _| | #410 41ee083\n/ exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922\n/ wip /↕ ↑1 ↓1 +33 Start API documentation b40716d\n\n○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden" + "plain": " Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02\n^ main ^⇅ ⇡1 ⇣1 # 41ee083\n+ fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68\n+ fix-typos _| | #410 41ee083\n/ exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922\n/ wip /↕ ↑1 ↓1 +33 Start API documentation b40716d\n\n○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message" }, { "lines": [ diff --git a/plugins/worktrunk/skills/worktrunk/reference/README.md b/plugins/worktrunk/skills/worktrunk/reference/README.md index 0654012e90..459946671c 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/README.md +++ b/plugins/worktrunk/skills/worktrunk/reference/README.md @@ -168,7 +168,7 @@ $ wt list @ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… ^ main ^⇡ ⇡1 0e631ad 1d Initial commit -○ Showing 2 worktrees, 1 with changes, 1 ahead, 1 column hidden +○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` The `@` marks the current worktree. `+` means staged changes, `↑1` means 1 commit ahead of main, `⇡` means unpushed commits. diff --git a/plugins/worktrunk/skills/worktrunk/reference/config.md b/plugins/worktrunk/skills/worktrunk/reference/config.md index 297177fb1d..a8c275dcfc 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/config.md +++ b/plugins/worktrunk/skills/worktrunk/reference/config.md @@ -1379,7 +1379,7 @@ $ wt list + review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… + wip-docs ? – +1 33323bc 1d Initial commit -○ Showing 4 worktrees, 2 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path ``` ### Use cases diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 34b9d13812..01bb88201e 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -22,7 +22,7 @@ $ wt list + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… + fix-typos _| | 41ee083 4d Merge fix-auth: h… -○ Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path ``` Include CI status and LLM summaries: @@ -35,7 +35,7 @@ $ wt list --full + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 + fix-typos _| | #410 41ee083 -○ Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message ``` Include branches that don't have worktrees: @@ -50,7 +50,7 @@ $ wt list --branches --full / exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922 / wip /↕ ↑1 ↓1 +33 Start API documentation b40716d -○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message ``` Output as JSON for scripting: @@ -80,6 +80,8 @@ $ wt list --format=json The `main` header label is used regardless of the default branch's actual name. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. + `main↕` and `main…±` measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local `main` trails `origin/main`, a branch reads as ahead of the real mainline, not of a stale local checkout. The `↑`/`↓`/`↕` Status symbols derive from these counts, so they track the upstream tip too. ### Gutter diff --git a/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md b/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md index 96385f28a3..4500b77422 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md +++ b/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md @@ -66,7 +66,7 @@ $ wt list + fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68 + fix-typos _| | http://localhost:14301 41ee083 -○ Showing 4 worktrees, 2 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path, Age, Message ``` `fix-auth` always gets port 16460, on any machine. The URL dims if the server isn't running. diff --git a/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md b/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md index 6fc3c8db01..43f75eef31 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md +++ b/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md @@ -144,7 +144,7 @@ $ wt list @ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… ^ main ^⇡ ⇡1 0e631ad 1d Initial commit -○ Showing 2 worktrees, 1 with changes, 1 ahead, 1 column hidden +○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` The `@` marks the current worktree. `+` means staged changes, `↑1` means 1 commit ahead of main, `⇡` means unpushed commits. diff --git a/skills/worktrunk/reference/config.md b/skills/worktrunk/reference/config.md index 297177fb1d..a8c275dcfc 100644 --- a/skills/worktrunk/reference/config.md +++ b/skills/worktrunk/reference/config.md @@ -1379,7 +1379,7 @@ $ wt list + review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… + wip-docs ? – +1 33323bc 1d Initial commit -○ Showing 4 worktrees, 2 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path ``` ### Use cases diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 34b9d13812..01bb88201e 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -22,7 +22,7 @@ $ wt list + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… + fix-typos _| | 41ee083 4d Merge fix-auth: h… -○ Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path ``` Include CI status and LLM summaries: @@ -35,7 +35,7 @@ $ wt list --full + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 + fix-typos _| | #410 41ee083 -○ Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message ``` Include branches that don't have worktrees: @@ -50,7 +50,7 @@ $ wt list --branches --full / exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922 / wip /↕ ↑1 ↓1 +33 Start API documentation b40716d -○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message ``` Output as JSON for scripting: @@ -80,6 +80,8 @@ $ wt list --format=json The `main` header label is used regardless of the default branch's actual name. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. + `main↕` and `main…±` measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local `main` trails `origin/main`, a branch reads as ahead of the real mainline, not of a stale local checkout. The `↑`/`↓`/`↕` Status symbols derive from these counts, so they track the upstream tip too. ### Gutter diff --git a/skills/worktrunk/reference/tips-patterns.md b/skills/worktrunk/reference/tips-patterns.md index 96385f28a3..4500b77422 100644 --- a/skills/worktrunk/reference/tips-patterns.md +++ b/skills/worktrunk/reference/tips-patterns.md @@ -66,7 +66,7 @@ $ wt list + fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68 + fix-typos _| | http://localhost:14301 41ee083 -○ Showing 4 worktrees, 2 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path, Age, Message ``` `fix-auth` always gets port 16460, on any machine. The URL dims if the server isn't running. diff --git a/skills/worktrunk/reference/worktrunk.md b/skills/worktrunk/reference/worktrunk.md index 6fc3c8db01..43f75eef31 100644 --- a/skills/worktrunk/reference/worktrunk.md +++ b/skills/worktrunk/reference/worktrunk.md @@ -144,7 +144,7 @@ $ wt list @ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… ^ main ^⇡ ⇡1 0e631ad 1d Initial commit -○ Showing 2 worktrees, 1 with changes, 1 ahead, 1 column hidden +○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` The `@` marks the current worktree. `+` means staged changes, `↑1` means 1 commit ahead of main, `⇡` means unpushed commits. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 0e89c51fbe..4ad45620a5 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -801,7 +801,7 @@ $ wt list + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… + fix-typos _| | 41ee083 4d Merge fix-auth: h… -○ Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path ``` Include CI status and LLM summaries: @@ -815,7 +815,7 @@ $ wt list --full + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 + fix-typos _| | #410 41ee083 -○ Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message ``` Include branches that don't have worktrees: @@ -831,7 +831,7 @@ $ wt list --branches --full / exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922 / wip /↕ ↑1 ↓1 +33 Start API documentation b40716d -○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message ``` Output as JSON for scripting: @@ -861,6 +861,8 @@ $ wt list --format=json The `main` header label is used regardless of the default branch's actual name. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. + `main↕` and `main…±` measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local `main` trails `origin/main`, a branch reads as ahead of the real mainline, not of a stale local checkout. The `↑`/`↓`/`↕` Status symbols derive from these counts, so they track the upstream tip too. ### Gutter diff --git a/src/commands/list/collect/mod.rs b/src/commands/list/collect/mod.rs index a6752d0b38..68ffda0d0c 100644 --- a/src/commands/list/collect/mod.rs +++ b/src/commands/list/collect/mod.rs @@ -1954,7 +1954,7 @@ pub fn collect( summary: super::format_summary_message( &all_items, show_branches || show_remotes, - layout.hidden_column_count, + &layout.hidden_columns, timed_out_count, ), }); diff --git a/src/commands/list/layout.rs b/src/commands/list/layout.rs index 83ec9d64e4..6eaf80b702 100644 --- a/src/commands/list/layout.rs +++ b/src/commands/list/layout.rs @@ -526,7 +526,10 @@ pub struct LayoutConfig { pub main_worktree_path: PathBuf, pub max_message_len: usize, pub max_summary_len: usize, - pub hidden_column_count: usize, + /// Headers of the columns the terminal was too narrow for, in display + /// order. The summary footer names them, so a reader can tell what a wider + /// terminal (or `--format json`) would add. + pub hidden_columns: Vec, pub status_position_mask: super::model::PositionMask, /// How every cell in this layout presents a reference with a URL behind it. /// Set once for the whole render — see [`LinkStyle`]. @@ -1126,21 +1129,32 @@ fn allocate_columns_with_priority( }); } - // Count how many columns were hidden (not allocated). - // This includes both data columns and empty columns that could show with more width. + // Name the columns that were dropped (not allocated), in display order — + // the footer reports them, so it says what widening the terminal would add. + // This includes both data columns and empty columns that could show with + // more width. let allocated_kinds: std::collections::HashSet<_> = columns.iter().map(|col| col.kind).collect(); - let hidden_column_count = candidate_kinds + let mut hidden_kinds: Vec<_> = candidate_kinds .iter() + .copied() .filter(|kind| !allocated_kinds.contains(kind)) - .count(); + .collect(); + hidden_kinds.sort_by_key(|&kind| column_display_index(kind)); + let hidden_columns = hidden_kinds + .into_iter() + .map(|kind| match kind { + ColumnKind::Custom(i) => custom_columns[i as usize].name.clone(), + kind => kind.header().to_string(), + }) + .collect(); LayoutConfig { columns, main_worktree_path, max_message_len, max_summary_len, - hidden_column_count, + hidden_columns, status_position_mask: metadata.status_position_mask, link_style, } @@ -1927,7 +1941,7 @@ mod tests { // (they were never candidates). assert!(find_column(&layout, ColumnKind::Status).is_none()); assert!(find_column(&layout, ColumnKind::Message).is_none()); - assert_eq!(layout.hidden_column_count, 0); + assert!(layout.hidden_columns.is_empty()); } #[test] @@ -2733,7 +2747,7 @@ mod tests { // Values are final before layout, so an all-empty column is excluded // entirely — not allocated and not counted as hidden assert!(find_column(&layout, ColumnKind::Custom(0)).is_none()); - assert_eq!(layout.hidden_column_count, 0); + assert!(layout.hidden_columns.is_empty()); } #[test] @@ -2763,6 +2777,6 @@ mod tests { // unallocated candidate counts toward the hidden-column footer assert!(find_column(&narrow, ColumnKind::Custom(0)).is_none()); assert!(find_column(&narrow, ColumnKind::Branch).is_some()); - assert!(narrow.hidden_column_count > 0); + assert!(!narrow.hidden_columns.is_empty()); } } diff --git a/src/commands/list/mod.rs b/src/commands/list/mod.rs index 65acaccba6..117c386664 100644 --- a/src/commands/list/mod.rs +++ b/src/commands/list/mod.rs @@ -337,7 +337,7 @@ impl SummaryMetrics { pub(super) fn summary_parts( &self, include_branches: bool, - hidden_columns: usize, + hidden_columns: &[String], ) -> Vec { let mut parts = Vec::new(); @@ -363,13 +363,8 @@ impl SummaryMetrics { parts.push(format!("{} ahead", self.ahead_items)); } - if hidden_columns > 0 { - let plural = if hidden_columns == 1 { - "column" - } else { - "columns" - }; - parts.push(format!("{} {} hidden", hidden_columns, plural)); + if !hidden_columns.is_empty() { + parts.push(format!("hidden: {}", hidden_columns.join(", "))); } parts @@ -386,13 +381,13 @@ impl SummaryMetrics { pub(crate) fn format_summary_message( items: &[ListItem], show_branches: bool, - hidden_column_count: usize, + hidden_columns: &[String], timed_out_count: usize, ) -> String { let metrics = SummaryMetrics::from_items(items); let dim = Style::new().dimmed(); let summary = metrics - .summary_parts(show_branches, hidden_column_count) + .summary_parts(show_branches, hidden_columns) .join(", "); if timed_out_count > 0 { @@ -448,7 +443,7 @@ mod tests { dirty_worktrees: 0, ahead_items: 0, }; - let parts = metrics.summary_parts(false, 0); + let parts = metrics.summary_parts(false, &[]); assert_eq!(parts, vec!["1 worktree"]); } @@ -461,7 +456,7 @@ mod tests { dirty_worktrees: 0, ahead_items: 0, }; - let parts = metrics.summary_parts(false, 0); + let parts = metrics.summary_parts(false, &[]); assert_eq!(parts, vec!["3 worktrees"]); } @@ -474,7 +469,7 @@ mod tests { dirty_worktrees: 0, ahead_items: 0, }; - let parts = metrics.summary_parts(true, 0); + let parts = metrics.summary_parts(true, &[]); assert_eq!( parts, vec!["2 worktrees", "5 branches", "10 remote branches"] @@ -490,7 +485,7 @@ mod tests { dirty_worktrees: 2, ahead_items: 0, }; - let parts = metrics.summary_parts(false, 0); + let parts = metrics.summary_parts(false, &[]); assert_eq!(parts, vec!["3 worktrees", "2 with changes"]); } @@ -503,7 +498,7 @@ mod tests { dirty_worktrees: 0, ahead_items: 1, }; - let parts = metrics.summary_parts(false, 0); + let parts = metrics.summary_parts(false, &[]); assert_eq!(parts, vec!["2 worktrees", "1 ahead"]); } @@ -516,11 +511,14 @@ mod tests { dirty_worktrees: 0, ahead_items: 0, }; - let parts = metrics.summary_parts(false, 1); - assert_eq!(parts, vec!["1 worktree", "1 column hidden"]); + let parts = metrics.summary_parts(false, &["Message".to_string()]); + assert_eq!(parts, vec!["1 worktree", "hidden: Message"]); - let parts = metrics.summary_parts(false, 3); - assert_eq!(parts, vec!["1 worktree", "3 columns hidden"]); + let parts = metrics.summary_parts( + false, + &["Path".to_string(), "Commit".to_string(), "Age".to_string()], + ); + assert_eq!(parts, vec!["1 worktree", "hidden: Path, Commit, Age"]); } #[test] @@ -532,7 +530,7 @@ mod tests { dirty_worktrees: 0, ahead_items: 0, }; - let parts = metrics.summary_parts(true, 0); + let parts = metrics.summary_parts(true, &[]); assert_eq!(parts, vec!["2 worktrees", "5 remote branches"]); } @@ -545,7 +543,7 @@ mod tests { dirty_worktrees: 2, ahead_items: 4, }; - let parts = metrics.summary_parts(true, 2); + let parts = metrics.summary_parts(true, &["Commit".to_string(), "Age".to_string()]); assert_eq!( parts, vec![ @@ -554,7 +552,7 @@ mod tests { "8 remote branches", "2 with changes", "4 ahead", - "2 columns hidden" + "hidden: Commit, Age" ] ); } @@ -565,10 +563,10 @@ mod tests { // Nothing timed out. Failures alone leave the footer untouched — the // warning after the table names them and carries their count. - assert_snapshot!(format_summary_message(&[], false, 0, 0), @"○ Showing 0 worktrees"); + assert_snapshot!(format_summary_message(&[], false, &[], 0), @"○ Showing 0 worktrees"); // Single timeout - assert_snapshot!(format_summary_message(&[], false, 0, 1), @"○ Showing 0 worktrees; 1 task timed out"); + assert_snapshot!(format_summary_message(&[], false, &[], 1), @"○ Showing 0 worktrees; 1 task timed out"); // Several timeouts - assert_snapshot!(format_summary_message(&[], false, 0, 3), @"○ Showing 0 worktrees; 3 tasks timed out"); + assert_snapshot!(format_summary_message(&[], false, &[], 3), @"○ Showing 0 worktrees; 3 tasks timed out"); } } diff --git a/src/commands/list/render.rs b/src/commands/list/render.rs index b5a3da811a..2b4670bc0c 100644 --- a/src/commands/list/render.rs +++ b/src/commands/list/render.rs @@ -656,7 +656,7 @@ mod tests { main_worktree_path: PathBuf::from("/tmp"), max_message_len: 50, max_summary_len: 40, - hidden_column_count: 0, + hidden_columns: Vec::new(), status_position_mask: PositionMask::FULL, link_style: LinkStyle::Expanded, } @@ -1361,7 +1361,7 @@ mod tests { main_worktree_path: PathBuf::from("/tmp"), max_message_len: 0, max_summary_len: 10, - hidden_column_count: 0, + hidden_columns: Vec::new(), status_position_mask: PositionMask::FULL, link_style, }; @@ -1430,7 +1430,7 @@ mod tests { main_worktree_path: PathBuf::from("/tmp"), max_message_len: 0, max_summary_len: 10, - hidden_column_count: 0, + hidden_columns: Vec::new(), status_position_mask: PositionMask::FULL, link_style: LinkStyle::Expanded, }; @@ -1504,7 +1504,7 @@ mod tests { main_worktree_path: PathBuf::from("/tmp"), max_message_len: 20, max_summary_len: 10, - hidden_column_count: 0, + hidden_columns: Vec::new(), status_position_mask: PositionMask::FULL, link_style: LinkStyle::Expanded, }; diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 17f0b83663..a9eead51dc 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -109,7 +109,7 @@ List all worktrees:   + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token…   + fix-typos _| | 41ee083 4d Merge fix-auth: h…    -  ○ Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden +  ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path Include CI status and LLM summaries: @@ -120,7 +120,7 @@ Include CI status and LLM summaries:   + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68   + fix-typos _| | #410 41ee083    -  ○ Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden +  ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message Include branches that don't have worktrees: @@ -133,7 +133,7 @@ Include branches that don't have worktrees:   / exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922   / wip /↕ ↑1 ↓1 +33 Start API documentation b40716d    -  ○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden +  ○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message Output as JSON for scripting: @@ -160,6 +160,8 @@ Output as JSON for scripting: The main header label is used regardless of the default branch's actual name. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (hidden: Commit, Age, Message). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [list] columns; --format=json carries every field at any width. + main↕ and main…± measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local main trails origin/main, a branch reads as ahead of the real mainline, not of a stale local checkout. The ↑/↓/↕ Status symbols derive from these counts, so they track the upstream tip too. Gutter diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 2b7a73e7f0..e3b17f87f3 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -120,7 +120,7 @@ List all worktrees:   + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h …   + fix-typos _| | 41ee083 4d …    -  ○ Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden +  ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path Include CI status and LLM summaries: @@ -131,7 +131,7 @@ Include CI status and LLM summaries:   + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with cons…   + fix-typos _| …    -  ○ Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden +  ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message Include branches that don't have worktrees: @@ -144,7 +144,7 @@ Include branches that don't have worktrees:   / exp /↕ ↑2 ↓1 +137 Explore GraphQL schem…   / wip /↕ ↑1 ↓1 +33 Start API documentati…    -  ○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden +  ○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age… Output as JSON for scripting: @@ -174,6 +174,13 @@ Output as JSON for scripting: The main header label is used regardless of the default branch's actual name. +The table sizes itself to the terminal. When the columns don't all fit, the +least important go first — roughly right to left, since the order above runs +from identity to nice-to-have — and the summary footer names them (hidden: +Commit, Age, Message). A wider terminal brings them back. To pin a set rather +than leave it to the width, name the columns in [list] columns; --format=json +carries every field at any width. + main↕ and main…± measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local main trails origin/main, a branch reads as ahead of the real mainline, not of a stale local checkout. The ↑/↓/↕ diff --git a/tests/snapshots/integration__integration_tests__list__quickstart_list.snap b/tests/snapshots/integration__integration_tests__list__quickstart_list.snap index d47eb0dad3..c4953dcca3 100644 --- a/tests/snapshots/integration__integration_tests__list__quickstart_list.snap +++ b/tests/snapshots/integration__integration_tests__list__quickstart_list.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -61,4 +62,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 2 worktrees, 1 with changes, 1 ahead, 1 column hidden +○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path diff --git a/tests/snapshots/integration__integration_tests__list__readme_example_list.snap b/tests/snapshots/integration__integration_tests__list__readme_example_list.snap index 72b274f58d..da931640c2 100644 --- a/tests/snapshots/integration__integration_tests__list__readme_example_list.snap +++ b/tests/snapshots/integration__integration_tests__list__readme_example_list.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -63,4 +64,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 4 worktrees, 1 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path diff --git a/tests/snapshots/integration__integration_tests__list__readme_example_list_branches.snap b/tests/snapshots/integration__integration_tests__list__readme_example_list_branches.snap index d77082a516..d589a97432 100644 --- a/tests/snapshots/integration__integration_tests__list__readme_example_list_branches.snap +++ b/tests/snapshots/integration__integration_tests__list__readme_example_list_branches.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -67,4 +68,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message diff --git a/tests/snapshots/integration__integration_tests__list__readme_example_list_full.snap b/tests/snapshots/integration__integration_tests__list__readme_example_list_full.snap index f6be3ed8bf..1a0859677a 100644 --- a/tests/snapshots/integration__integration_tests__list__readme_example_list_full.snap +++ b/tests/snapshots/integration__integration_tests__list__readme_example_list_full.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -64,4 +65,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 4 worktrees, 1 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message diff --git a/tests/snapshots/integration__integration_tests__list__readme_example_list_marker.snap b/tests/snapshots/integration__integration_tests__list__readme_example_list_marker.snap index 5e26709628..d8866d9616 100644 --- a/tests/snapshots/integration__integration_tests__list__readme_example_list_marker.snap +++ b/tests/snapshots/integration__integration_tests__list__readme_example_list_marker.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -63,4 +64,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 4 worktrees, 2 with changes, 2 ahead, 1 column hidden +○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path diff --git a/tests/snapshots/integration__integration_tests__list__tips_dev_server_workflow.snap b/tests/snapshots/integration__integration_tests__list__tips_dev_server_workflow.snap index bc27c90a07..a7461f50ac 100644 --- a/tests/snapshots/integration__integration_tests__list__tips_dev_server_workflow.snap +++ b/tests/snapshots/integration__integration_tests__list__tips_dev_server_workflow.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -63,4 +64,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 4 worktrees, 2 with changes, 2 ahead, 3 columns hidden +○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path, Age, Message diff --git a/tests/snapshots/integration__integration_tests__list_layout__width_30.snap b/tests/snapshots/integration__integration_tests__list_layout__width_30.snap index 3b28b49662..1dd07b58b9 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__width_30.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__width_30.snap @@ -11,9 +11,18 @@ info: COLUMNS: "30" GIT_ALLOW_PROTOCOL: file GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" - GIT_CONFIG_GLOBAL: "[TEST_GIT_CONFIG]" - GIT_CONFIG_SYSTEM: /dev/null + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" GIT_TERMINAL_PROMPT: "0" HOME: "[TEST_HOME]" LANG: C @@ -39,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,4 +65,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 5 worktrees, 1 with changes, 3 ahead, 9 columns hidden +○ Showing 5 worktrees, 1 with changes, 3 ahead, hidden: Status, HEAD±, main↕, main…±, Remote⇅, Path, Commit, Age, Message From 5ea9a447b6fc50260cde6730ff995c365ed46bfc Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 1 Sep 2026 16:28:39 -0700 Subject: [PATCH 02/17] =?UTF-8?q?list:=20invert=20the=20JSON=20docs=20?= =?UTF-8?q?=E2=80=94=20schema=202=20gets=20the=20field=20reference,=20sche?= =?UTF-8?q?ma=201=20a=20mapping=20table=20(#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list page documented deprecated schema 1 at 159 lines and nine sub-tables while schema 2, the format a future release makes the default, got 88 lines and one table. Schema 2 now carries the full reference: envelope fields, item fields, and a sub-table per object (head, worktree, changes, default_branch, upstream, pr, checks, dev_server, display, repo) plus the three value vocabularies (display.state, integration reasons, review states). The worked jq recipes move with it and target schema 2. Schema 1 keeps a paragraph, the deprecation pointer, and a schema-1 → schema-2 mapping table covering every field it documented. The Status-symbols and CI-status tables named their JSON fields in schema-1 spelling; they now name the schema-2 paths, with the mapping table as the schema-1 route. Co-Authored-By: Claude Fable 5.1 --- docs/src/content/docs/list.md | 344 +++++----- .../skills/worktrunk/reference/list.md | 344 +++++----- skills/worktrunk/reference/list.md | 344 +++++----- src/cli/mod.rs | 344 +++++----- ...tegration_tests__help__help_list_long.snap | 429 ++++++------ ...tion_tests__help__help_list_narrow_80.snap | 643 ++++++++++-------- 6 files changed, 1327 insertions(+), 1121 deletions(-) diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index 53d79ddea5..68e0f10643 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -111,7 +111,7 @@ The leftmost column marks each row by physical presence, from most present to le ### CI status -The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Azure DevOps, `!3035` on GitLab — colored by pipeline status, or a bare `#` when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are `ci.status`; magenta/cyan are `ci.review_state`. The `Value` column is the matching JSON string from `--format=json`: +The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Azure DevOps, `!3035` on GitLab — colored by pipeline status, or a bare `#` when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are the pipeline state, magenta/cyan the review state. The `Value` column names each state; [checks object](#checks-object) and [review states](#review-states) give the JSON spelling — schema 2 reports three of the pipeline values by the shape of `pr` and `checks` rather than as a `checks.status` string: | Indicator | Value | Meaning | |-----------|-------|---------| @@ -123,13 +123,13 @@ The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Az | ⚠ yellow | `"error"` | CI status could not be fetched (rate limit, network, etc.) | | # magenta | `"changes_requested"` | A reviewer requested changes | | # cyan | `"pending"` | A review is required (e.g. branch protection) but not yet given | -| (blank) | `ci` absent | No upstream, or no PR/MR and no branch workflow | +| (blank) | `pr` and `checks` absent | No upstream, or no PR/MR and no branch workflow | -The two remaining `ci.review_state` values have no indicator of their own: `"draft"` only dims the cell and `"approved"` leaves the color unchanged. +The two remaining review states have no indicator of their own: `"draft"` only dims the cell and `"approved"` leaves the color unchanged. -Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain `ci.status` color — `ci.review_state` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. +Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — `pr.review` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`ci.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. ### LLM summaries @@ -150,19 +150,19 @@ A column that renders empty for every row is dropped from the table. Templates, ## Status symbols -The Status column packs several subcolumns, left to right, each mapping to a field in `--format=json`. Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. +The Status column packs several subcolumns, left to right, each mapping to a schema-2 field in `--format=json` (schema 1 spells several of them differently — see [Schema 1](#schema-1)). Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. ### Working tree -Independent flags from `git status`; several can show at once (e.g. `+!?`). Each maps to a boolean in the `working_tree` object: +Independent flags from `git status`; several can show at once (e.g. `+!?`). Each maps to a boolean in the [changes object](#changes-object): -| Symbol | working_tree | Meaning | -|--------|--------------|---------| +| Symbol | worktree.changes | Meaning | +|--------|------------------|---------| | `+` | `staged` | Staged files | | `!` | `modified` | Modified files (unstaged) | | `?` | `untracked` | Untracked files | -`working_tree` also reports `renamed` and `deleted`, which have no dedicated symbol in the column. +`worktree.changes` also reports `renamed` and `deleted`, which have no dedicated symbol in the column. ### Worktree @@ -170,24 +170,24 @@ An in-progress git operation, a worktree-location attribute, or a branch with no | Symbol | JSON | Meaning | |--------|------|---------| -| `✘` | `operation_state` `"conflicts"` | Merge conflicts | -| `↻` | `operation_state` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | -| `⊟` | `worktree.state` `"prunable"` | Prunable (worktree directory missing) | -| `⊞` | `worktree.state` `"locked"` | Locked worktree | -| `⚑` | `worktree.state` `"duplicate_branch"` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | -| `⚑` | `worktree.state` `"branch_worktree_mismatch"` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | -| `/` | `kind` `"branch"` | Branch without a worktree (no `worktree` object) | +| `✘` | `worktree.changes.conflicted` | Merge conflicts | +| `↻` | `worktree.operation` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | +| `⊟` | `worktree.prunable` | Prunable (worktree directory missing) | +| `⊞` | `worktree.locked` | Locked worktree | +| `⚑` | `worktree.duplicate_branch` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | +| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | +| `/` | no `worktree` object | Branch without a worktree | ### Default branch -The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one `main_state` value: +The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one `display.state` value: -| Symbol | main_state | Meaning | -|--------|------------|---------| +| Symbol | display.state | Meaning | +|--------|---------------|---------| | `^` | `"is_main"` | The main worktree (the repo's home worktree) | | `∅` | `"orphan"` | No common ancestor with the default branch | | `_` | `"empty"` | Same commit as the default branch, working tree clean — safe to remove; row dimmed | -| `⊂` | `"integrated"` | Content [integrated](/remove/#branch-cleanup) into the default branch or merge target via different history; the matching check is in `integration_reason`; row dimmed | +| `⊂` | `"integrated"` | Content [integrated](/remove/#branch-cleanup) into the default branch or merge target via different history; the matching check is in `default_branch.integration.reason`; row dimmed | | `✗` | `"would_conflict"` | Merging into the default branch would conflict (simulated with `git merge-tree`) and the branch isn't already integrated; with `--full`, the check includes tracked uncommitted changes | | `–` | `"same_commit"` | Same commit as the default branch, but with uncommitted changes | | `↕` | `"diverged"` | Both ahead of and behind the default branch | @@ -198,10 +198,10 @@ Rows are dimmed when [safe to delete](/remove/#branch-cleanup) — `_` (`"empty" ### Remote -Relation to the tracking branch, derived from the `remote.ahead` / `remote.behind` counts; blank when there is no upstream: +Relation to the tracking branch, derived from the `upstream.ahead` / `upstream.behind` counts; blank when there is no upstream: -| Symbol | remote | Meaning | -|--------|--------|---------| +| Symbol | upstream | Meaning | +|--------|----------|---------| | `\|` | `ahead` 0, `behind` 0 | In sync with remote | | `⇡` | `ahead` > 0 | Ahead of remote | | `⇣` | `behind` > 0 | Behind remote | @@ -271,117 +271,60 @@ How "no value" reads: jq treats absent and `null` identically in path expressions, so filters need no null checks; `has()` distinguishes the two when it matters. -Item fields: - -| Field | Description | -|-------|-------------| -| `branch` | Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in `remote` | -| `remote` | Remote name, present only on remote-only branch rows | -| `head` | `{sha, short_sha, subject, committed_at}`; null for unborn branches. `committed_at` is RFC 3339 UTC | -| `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | -| `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | -| `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full`. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | -| `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | -| `vars` | Per-branch variables from [`wt config state vars`](/config/#wt-config-state-vars) | -| `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | - -Schema 1 names map directly: `commit` → `head`, `working_tree` → -`worktree.changes`, `main` + `main_state` → `default_branch` + -`display.state`, `remote` → `upstream`, `ci` → `pr` + `checks`, `url` + -`url_active` → `dev_server`, `statusline`/`symbols`/`columns` → `display.*`, -and the per-item `repo` moves to the envelope's `repo.forge`. +Envelope fields: -```console -# Current worktree path (for scripts) -$ wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' - -# Branches with uncommitted changes -$ wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' - -# Integrated branches (safe to remove) -$ wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' - -# Worktrees ahead of upstream (needs pushing) -$ wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | .branch' -``` - -A JSON Schema for the envelope is published at -[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json). -It describes what `wt` writes, so a field the absence rule can omit is -optional there rather than required-and-null. - -### Schema 1 - -The original bare-array format, and the default while unset: - -```console -# Current worktree path (for scripts) -$ wt list --format=json | jq -r '.[] | select(.is_current) | .path' - -# Branches with uncommitted changes -$ wt list --format=json | jq '.[] | select(.working_tree.modified)' - -# Worktrees with merge conflicts -$ wt list --format=json | jq '.[] | select(.operation_state == "conflicts")' - -# Branches ahead of main (needs merging) -$ wt list --format=json | jq '.[] | select(.main.ahead > 0) | .branch' - -# Integrated branches (safe to remove) -$ wt list --format=json | jq '.[] | select(.main_state == "integrated" or .main_state == "empty") | .branch' - -# Branches without worktrees -$ wt list --format=json --branches | jq '.[] | select(.kind == "branch") | .branch' - -# Worktrees ahead of remote (needs pushing) -$ wt list --format=json | jq '.[] | select(.remote.ahead > 0) | {branch, ahead: .remote.ahead}' - -# Stale CI (local changes not reflected in CI) -$ wt list --format=json --full | jq '.[] | select(.ci.stale) | .branch' -``` +| Field | Type | Description | +|-------|------|-------------| +| `schema` | number | Format version; always `2` | +| `repo` | object | `{default_branch, forge}` — the branch every `default_branch` object measures against (absent when detection failed), and forge metadata derived from the primary remote (absent when no remote URL parses; see [repo object](#repo-object)) | +| `collected` | object | `{ci, summary}` — which gated fact families this run requested, so an absent `pr`/`checks`/`summary` reads as "not requested" rather than "none" | +| `items` | array | One object per row: a worktree, a local branch, or a remote-only branch | -**Fields:** +Item fields: | Field | Type | Description | |-------|------|-------------| -| `branch` | string/null | Branch name (null for detached HEAD) | -| `path` | string | Worktree path (absent for branches without worktrees) | -| `kind` | string | `"worktree"` or `"branch"` | -| `commit` | object | Commit info (see below) | -| `working_tree` | object | Working tree state (see below) | -| `main_state` | string | Relation to the default branch (see below) | -| `integration_reason` | string | Why branch is integrated (see below) | -| `operation_state` | string | `"conflicts"`, `"rebase"`, or `"merge"` (see [Worktree](#worktree)); absent when clean | -| `main` | object | Relationship to the default branch (see below); absent when is_main | -| `remote` | object | Tracking branch info (see below); absent when no tracking | -| `worktree` | object | Worktree metadata (see below) | -| `is_main` | boolean | Is the main worktree | -| `is_current` | boolean | Is the current worktree | -| `is_previous` | boolean | Previous worktree from wt switch | -| `ci` | object | CI status (see below); `--full` only, then absent when no PR/MR or branch workflow | -| `repo_url` | string | Repository web URL derived from the primary remote; absent when the remote URL cannot be parsed | -| `repo` | object | Structured repository metadata (see below); includes `remote` | -| `url` | string | Dev server URL from project config; absent when not configured | -| `url_active` | boolean | Whether the URL's port is listening; absent when not configured | -| `summary` | string | LLM-generated branch summary; `--full` only, then absent when not configured or no summary | -| `statusline` | string | Pre-formatted status with colors and links | -| `symbols` | string | Raw status symbols without colors (e.g., `"!?↓"`) | -| `vars` | object | Per-branch variables from [`wt config state vars`](/config/#wt-config-state-vars) (absent when empty) | -| `columns` | object | Rendered [custom column](#custom-columns) values keyed by header; empty cells omitted (absent when none configured) | - -### Commit object +| `branch` | string/null | Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in `remote` | +| `remote` | string | Remote name, present only on remote-only branch rows | +| `head` | object/null | HEAD commit (see [head object](#head-object)); null for unborn branches | +| `worktree` | object | Worktree facts (see [worktree object](#worktree-object)); absent on branch-only rows | +| `default_branch` | object | Relation to the default branch (see [default_branch object](#default-branch-object)); absent on the default branch itself | +| `upstream` | object | Tracking branch (see [upstream object](#upstream-object)); absent when none is configured | +| `pr` | object | Open PR/MR (see [pr object](#pr-object)); collected with `--full` | +| `checks` | object | CI pipeline (see [checks object](#checks-object)); collected with `--full` | +| `dev_server` | object | `{url, listening}` from the project's `list.url` template; absent when not configured | +| `summary` | string | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | +| `vars` | object | Per-branch variables from [`wt config state vars`](/config/#wt-config-state-vars) | +| `display` | object | Rendered strings (see [display object](#display-object)) | + +### head object | Field | Type | Description | |-------|------|-------------| | `sha` | string | Full commit SHA (40 chars) | | `short_sha` | string | Short commit SHA, abbreviated per `core.abbrev` (auto-extends for ambiguous prefixes) | -| `message` | string | Commit message (first line) | -| `timestamp` | number | Unix timestamp | +| `subject` | string/null | Commit subject (first line); null when not loaded, as for a prunable worktree | +| `committed_at` | string/null | Committer time, RFC 3339 UTC; null when not loaded | + +### worktree object -### working_tree object +Present only on worktree rows. The location attributes are independent, so they co-occur — see [Worktree](#worktree) for the symbols, which pick one: + +| Field | Type | Description | +|-------|------|-------------| +| `path` | string | Worktree path | +| `main` | boolean | Is the main worktree | +| `current` | boolean | Is the worktree the command ran from | +| `previous` | boolean | Is the previous worktree (`wt switch -`) | +| `detached` | boolean | HEAD is detached | +| `locked` | object | `{reason}`; absent when not locked | +| `prunable` | object | `{reason}`; absent when git doesn't report the worktree prunable | +| `branch_mismatch` | boolean | Worktree isn't at the path its branch implies | +| `duplicate_branch` | boolean | Another worktree has the same branch checked out | +| `operation` | string/null | In-progress operation: `"merge"`, `"rebase"`, `"cherry_pick"`, `"revert"`, `"bisect"`; absent when none | +| `changes` | object/null | Working-tree state (see [changes object](#changes-object)) | + +### changes object The five change flags map to the [Working tree](#working-tree) symbols (`renamed` and `deleted` have none of their own): @@ -392,53 +335,74 @@ The five change flags map to the [Working tree](#working-tree) symbols (`renamed | `untracked` | boolean | Has untracked files | | `renamed` | boolean | Has renamed files | | `deleted` | boolean | Has deleted files | -| `diff` | object | Lines changed vs HEAD: `{added, deleted}` | +| `conflicted` | boolean/null | Tracked files carry merge conflicts | +| `diff` | object/null | Lines changed vs HEAD: `{added, deleted}` | -### main object +### default_branch object + +Independent facts; the table's priority-collapsed symbol is `display.state`. | Field | Type | Description | |-------|------|-------------| -| `ahead` | number | Commits ahead of the default branch | -| `behind` | number | Commits behind the default branch | -| `diff` | object | Lines changed vs the default branch: `{added, deleted}` | +| `ahead` | number/null | Commits ahead of the default branch (null for orphans) | +| `behind` | number/null | Commits behind the default branch (null for orphans) | +| `diff` | object/null | Lines changed vs the default branch: `{added, deleted}` | +| `orphan` | boolean/null | No common ancestor with the default branch | +| `integration` | object/null | `{reason}` — which check found the content [integrated](/remove/#branch-cleanup) (see [integration reasons](#integration-reasons)); absent when determined not-integrated, null when a dirty tree skipped the checks | +| `merge_conflicts` | boolean/null | Merging into the default branch would conflict, simulated locally with `git merge-tree` | -### remote object +### upstream object `ahead` / `behind` drive the [Remote](#remote) divergence symbol: | Field | Type | Description | |-------|------|-------------| -| `name` | string | Remote name (e.g., `"origin"`) | -| `branch` | string | Remote branch name | +| `remote` | string | Remote name (e.g., `"origin"`) | +| `branch` | string/null | Branch name on the remote | | `ahead` | number | Commits ahead of remote | | `behind` | number | Commits behind remote | -### worktree object - -Present only for worktree-kind items. `state` is the worktree-location attribute — see [Worktree](#worktree) for its symbols: +### pr object | Field | Type | Description | |-------|------|-------------| -| `state` | string | `"branch_worktree_mismatch"`, `"duplicate_branch"`, `"prunable"`, or `"locked"` (absent when normal) | -| `reason` | string | Reason for locked/prunable state | -| `detached` | boolean | HEAD is detached | +| `number` | integer/null | PR/MR number | +| `url` | string/null | URL to the PR/MR page | +| `review` | string | Review state (see [review states](#review-states)); absent when the forge reports no review signal | +| `mergeable` | boolean/null | False when the forge reports conflicts, null otherwise — the fetch records only the conflicted case | +| `repo` | object | Structured metadata for the repository the PR/MR targets, the upstream for fork PRs (see [repo object](#repo-object)); absent when the URL doesn't parse | -### ci object +### checks object | Field | Type | Description | |-------|------|-------------| -| `status` | string | CI status (see below) | +| `status` | string/null | `"passed"`, `"running"`, or `"failed"`; null when a conflicts report masks it | | `source` | string | `"pr"` (PR/MR) or `"branch"` (branch workflow) | -| `number` | integer | PR/MR number; absent for branch workflows | | `stale` | boolean | Local HEAD differs from remote (unpushed changes) | -| `url` | string | URL to the PR/MR page | -| `repo_url` | string | Web URL of the repo the PR/MR targets (the upstream for fork PRs); absent when `url` is absent or unrecognized | -| `repo` | object | Structured metadata for the repository the PR/MR targets; never includes `remote` | -| `review_state` | string | Review state (see below); absent when the forge reports no review signal | + +Three [CI status](#ci-status) values have no `checks.status` of their own, because the shape of `pr` and `checks` reports them: a fetch error (`⚠`) makes both null, no CI leaves `checks` absent, and merge conflicts leave `checks.status` null with `pr.mergeable` false. + +### dev_server object + +| Field | Type | Description | +|-------|------|-------------| +| `url` | string | Dev server URL from project config | +| `listening` | boolean/null | Whether the URL's port is listening | + +### display object + +Presentation only — every value here renders facts that appear elsewhere in the item: + +| Field | Type | Description | +|-------|------|-------------| +| `state` | string | The table's collapsed default-branch state (see [state values](#state-values)); absent when none applies | +| `symbols` | string | Raw status symbols without colors (e.g., `"!?↓"`) | +| `statusline` | string | Pre-formatted status with colors and links | +| `columns` | object | Rendered [custom column](#custom-columns) values keyed by header; empty cells omitted | ### repo object -Top-level `repo` describes the local checkout's repository as derived from the primary remote. `ci.repo` describes the repository targeted by the PR/MR URL in `ci.url` (for fork PRs, this is the upstream target). Existing `repo_url` and `ci.repo_url` fields remain available and carry the same URL as `repo.url` / `ci.repo.url`. +`repo.forge` describes the local checkout's repository as derived from the primary remote. `pr.repo` describes the repository targeted by the PR/MR (for fork PRs, the upstream target). | Field | Type | Description | |-------|------|-------------| @@ -448,27 +412,95 @@ Top-level `repo` describes the local checkout's repository as derived from the p | `owner` | string | Owner, organization, or namespace path | | `name` | string | Repository name | | `project` | string | Azure DevOps project name; absent for other providers | -| `remote` | string | Local remote name used for top-level repo metadata; absent from `ci.repo` | +| `remote` | string | Local remote name; present on `repo.forge`, absent from `pr.repo` | -### main_state values +### state values The single highest-priority state describing the branch's relation to the default branch; absent when none applies (a normal up-to-date branch). Each value is one Default-branch symbol — see [Default branch](#default-branch) for the symbol and the full meaning of each value (`"is_main"`, `"orphan"`, `"empty"`, `"integrated"`, `"would_conflict"`, `"same_commit"`, `"diverged"`, `"ahead"`, `"behind"`). -### integration_reason values +### integration reasons -Set only when `main_state == "integrated"` (the `⊂` symbol), recording which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same `⊂`: +`default_branch.integration.reason` records which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same `⊂`: | Value | Meaning | |-------|---------| +| `"same_commit"` | Branch HEAD is the default branch's commit | | `"ancestor"` | Branch HEAD is an ancestor of the default branch, which has moved past it | -| `"no-added-changes"` | The three-dot diff (`main...branch`) is empty — no file changes beyond the merge-base | -| `"trees-match"` | Different history, but the branch's tree is identical to the default branch's | -| `"merge-adds-nothing"` | The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) | -| `"patch-id-match"` | The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) | +| `"no_added_changes"` | The three-dot diff (`main...branch`) is empty — no file changes beyond the merge-base | +| `"trees_match"` | Different history, but the branch's tree is identical to the default branch's | +| `"merge_adds_nothing"` | The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) | +| `"patch_id_match"` | The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) | + +### review states + +`pr.review` is one of `"changes_requested"`, `"pending"`, `"draft"`, `"approved"`, absent when the forge reports no review signal. The [CI status](#ci-status) section is the single source: its table maps the colored values, and the notes below it cover `"draft"` and `"approved"`. The vocabulary matches Claude Code's statusline `pr.review_state` field. + +```console +# Current worktree path (for scripts) +$ wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' + +# Branches with uncommitted changes +$ wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' + +# Worktrees with merge conflicts +$ wt list --format=json | jq '.items[] | select(.worktree.changes.conflicted)' + +# Branches ahead of main (needs merging) +$ wt list --format=json | jq '.items[] | select(.default_branch.ahead > 0) | .branch' + +# Integrated branches (safe to remove) +$ wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' + +# Branches without worktrees +$ wt list --format=json --branches | jq '.items[] | select(.worktree == null) | .branch' + +# Worktrees ahead of upstream (needs pushing) +$ wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | {branch, ahead: .upstream.ahead}' + +# Stale CI (local changes not reflected in CI) +$ wt list --format=json --full | jq '.items[] | select(.checks.stale) | .branch' +``` -### ci.status and ci.review_state values +A JSON Schema for the envelope is published at +[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json). +It describes what `wt` writes, so a field the absence rule can omit is +optional there rather than required-and-null. + +### Schema 1 -The [CI status](#ci-status) section above is the single source for both fields: the table maps each colored value, and the notes below it cover `"draft"` and `"approved"`. `ci.status` is one of `"passed"`, `"running"`, `"failed"`, `"conflicts"`, `"no-ci"`, `"error"`; `ci.review_state` is one of `"changes_requested"`, `"pending"`, `"draft"`, `"approved"`, absent when the forge reports no review signal. The vocabulary matches Claude Code's statusline `pr.review_state` field. +The original bare-array format — one object per row, no envelope — and the +default while `[list] json-schema` is unset. It is on its way out: `wt config +update` adopts `= 2`, a future release flips the default, and a later one +removes schema 1. Its fields all have a schema-2 home: + +| Schema 1 | Schema 2 | +|----------|----------| +| `branch` | `branch` | +| `path` | `worktree.path` | +| `kind` | the row's shape — a `worktree` object means `"worktree"`, no `worktree` object means `"branch"` | +| `commit.sha`, `.short_sha`, `.message` | `head.sha`, `.short_sha`, `.subject` | +| `commit.timestamp` (Unix) | `head.committed_at` (RFC 3339 UTC) | +| `working_tree.staged`, `.modified`, `.untracked`, `.renamed`, `.deleted`, `.diff` | `worktree.changes.*` | +| `operation_state` `"conflicts"` | `worktree.changes.conflicted` | +| `operation_state` `"rebase"`, `"merge"`, … | `worktree.operation` | +| `main_state` | `display.state` | +| `integration_reason` | `default_branch.integration.reason` (snake_case, and it reports `"same_commit"` rather than folding it into `main_state`) | +| `main.ahead`, `.behind`, `.diff` | `default_branch.ahead`, `.behind`, `.diff` | +| `remote.name`, `.branch`, `.ahead`, `.behind` | `upstream.remote`, `.branch`, `.ahead`, `.behind` | +| `worktree.state` | `worktree.locked`, `.prunable`, `.duplicate_branch`, `.branch_mismatch` — independent, so they co-occur | +| `worktree.reason` | `worktree.locked.reason`, `worktree.prunable.reason` | +| `worktree.detached` | `worktree.detached` | +| `is_main`, `is_current`, `is_previous` | `worktree.main`, `.current`, `.previous` | +| `ci.status` | `checks.status`, plus the shapes described under [checks object](#checks-object) | +| `ci.source`, `ci.stale` | `checks.source`, `checks.stale` | +| `ci.number`, `ci.url`, `ci.review_state` | `pr.number`, `pr.url`, `pr.review` | +| `ci.repo`, `ci.repo_url` | `pr.repo`, `pr.repo.url` | +| `repo`, `repo_url` | the envelope's `repo.forge`, `repo.forge.url` | +| `url`, `url_active` | `dev_server.url`, `dev_server.listening` | +| `summary`, `vars` | `summary`, `vars` | +| `statusline`, `symbols`, `columns` | `display.statusline`, `display.symbols`, `display.columns` | + +The envelope's `repo.default_branch` and `collected` have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see [How "no value" reads](#schema-2). Missing a field that would be generally useful? [Open an issue](https://github.com/max-sixty/worktrunk/issues). diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 01bb88201e..013d0229ce 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -98,7 +98,7 @@ The leftmost column marks each row by physical presence, from most present to le ### CI status -The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Azure DevOps, `!3035` on GitLab — colored by pipeline status, or a bare `#` when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are `ci.status`; magenta/cyan are `ci.review_state`. The `Value` column is the matching JSON string from `--format=json`: +The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Azure DevOps, `!3035` on GitLab — colored by pipeline status, or a bare `#` when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are the pipeline state, magenta/cyan the review state. The `Value` column names each state; [checks object](#checks-object) and [review states](#review-states) give the JSON spelling — schema 2 reports three of the pipeline values by the shape of `pr` and `checks` rather than as a `checks.status` string: | Indicator | Value | Meaning | |-----------|-------|---------| @@ -110,13 +110,13 @@ The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Az | `⚠` yellow | `"error"` | CI status could not be fetched (rate limit, network, etc.) | | `#` magenta | `"changes_requested"` | A reviewer requested changes | | `#` cyan | `"pending"` | A review is required (e.g. branch protection) but not yet given | -| (blank) | `ci` absent | No upstream, or no PR/MR and no branch workflow | +| (blank) | `pr` and `checks` absent | No upstream, or no PR/MR and no branch workflow | -The two remaining `ci.review_state` values have no indicator of their own: `"draft"` only dims the cell and `"approved"` leaves the color unchanged. +The two remaining review states have no indicator of their own: `"draft"` only dims the cell and `"approved"` leaves the color unchanged. -Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain `ci.status` color — `ci.review_state` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. +Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — `pr.review` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`ci.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. ### LLM summaries @@ -135,19 +135,19 @@ A column that renders empty for every row is dropped from the table. Templates, ## Status symbols -The Status column packs several subcolumns, left to right, each mapping to a field in `--format=json`. Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. +The Status column packs several subcolumns, left to right, each mapping to a schema-2 field in `--format=json` (schema 1 spells several of them differently — see [Schema 1](#schema-1)). Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. ### Working tree -Independent flags from `git status`; several can show at once (e.g. `+!?`). Each maps to a boolean in the `working_tree` object: +Independent flags from `git status`; several can show at once (e.g. `+!?`). Each maps to a boolean in the [changes object](#changes-object): -| Symbol | working_tree | Meaning | -|--------|--------------|---------| +| Symbol | worktree.changes | Meaning | +|--------|------------------|---------| | `+` | `staged` | Staged files | | `!` | `modified` | Modified files (unstaged) | | `?` | `untracked` | Untracked files | -`working_tree` also reports `renamed` and `deleted`, which have no dedicated symbol in the column. +`worktree.changes` also reports `renamed` and `deleted`, which have no dedicated symbol in the column. ### Worktree @@ -155,24 +155,24 @@ An in-progress git operation, a worktree-location attribute, or a branch with no | Symbol | JSON | Meaning | |--------|------|---------| -| `✘` | `operation_state` `"conflicts"` | Merge conflicts | -| `↻` | `operation_state` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | -| `⊟` | `worktree.state` `"prunable"` | Prunable (worktree directory missing) | -| `⊞` | `worktree.state` `"locked"` | Locked worktree | -| `⚑` | `worktree.state` `"duplicate_branch"` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | -| `⚑` | `worktree.state` `"branch_worktree_mismatch"` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | -| `/` | `kind` `"branch"` | Branch without a worktree (no `worktree` object) | +| `✘` | `worktree.changes.conflicted` | Merge conflicts | +| `↻` | `worktree.operation` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | +| `⊟` | `worktree.prunable` | Prunable (worktree directory missing) | +| `⊞` | `worktree.locked` | Locked worktree | +| `⚑` | `worktree.duplicate_branch` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | +| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | +| `/` | no `worktree` object | Branch without a worktree | ### Default branch -The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one `main_state` value: +The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one `display.state` value: -| Symbol | main_state | Meaning | -|--------|------------|---------| +| Symbol | display.state | Meaning | +|--------|---------------|---------| | `^` | `"is_main"` | The main worktree (the repo's home worktree) | | `∅` | `"orphan"` | No common ancestor with the default branch | | `_` | `"empty"` | Same commit as the default branch, working tree clean — safe to remove; row dimmed | -| `⊂` | `"integrated"` | Content [integrated](https://worktrunk.dev/remove/#branch-cleanup) into the default branch or merge target via different history; the matching check is in `integration_reason`; row dimmed | +| `⊂` | `"integrated"` | Content [integrated](https://worktrunk.dev/remove/#branch-cleanup) into the default branch or merge target via different history; the matching check is in `default_branch.integration.reason`; row dimmed | | `✗` | `"would_conflict"` | Merging into the default branch would conflict (simulated with `git merge-tree`) and the branch isn't already integrated; with `--full`, the check includes tracked uncommitted changes | | `–` | `"same_commit"` | Same commit as the default branch, but with uncommitted changes | | `↕` | `"diverged"` | Both ahead of and behind the default branch | @@ -183,10 +183,10 @@ Rows are dimmed when [safe to delete](https://worktrunk.dev/remove/#branch-clean ### Remote -Relation to the tracking branch, derived from the `remote.ahead` / `remote.behind` counts; blank when there is no upstream: +Relation to the tracking branch, derived from the `upstream.ahead` / `upstream.behind` counts; blank when there is no upstream: -| Symbol | remote | Meaning | -|--------|--------|---------| +| Symbol | upstream | Meaning | +|--------|----------|---------| | `\|` | `ahead` 0, `behind` 0 | In sync with remote | | `⇡` | `ahead` > 0 | Ahead of remote | | `⇣` | `behind` > 0 | Behind remote | @@ -256,117 +256,60 @@ How "no value" reads: jq treats absent and `null` identically in path expressions, so filters need no null checks; `has()` distinguishes the two when it matters. -Item fields: - -| Field | Description | -|-------|-------------| -| `branch` | Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in `remote` | -| `remote` | Remote name, present only on remote-only branch rows | -| `head` | `{sha, short_sha, subject, committed_at}`; null for unborn branches. `committed_at` is RFC 3339 UTC | -| `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | -| `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | -| `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full`. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | -| `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | -| `vars` | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | -| `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | - -Schema 1 names map directly: `commit` → `head`, `working_tree` → -`worktree.changes`, `main` + `main_state` → `default_branch` + -`display.state`, `remote` → `upstream`, `ci` → `pr` + `checks`, `url` + -`url_active` → `dev_server`, `statusline`/`symbols`/`columns` → `display.*`, -and the per-item `repo` moves to the envelope's `repo.forge`. +Envelope fields: -```console -# Current worktree path (for scripts) -$ wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' - -# Branches with uncommitted changes -$ wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' - -# Integrated branches (safe to remove) -$ wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' - -# Worktrees ahead of upstream (needs pushing) -$ wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | .branch' -``` - -A JSON Schema for the envelope is published at -[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json). -It describes what `wt` writes, so a field the absence rule can omit is -optional there rather than required-and-null. - -### Schema 1 - -The original bare-array format, and the default while unset: - -```console -# Current worktree path (for scripts) -$ wt list --format=json | jq -r '.[] | select(.is_current) | .path' - -# Branches with uncommitted changes -$ wt list --format=json | jq '.[] | select(.working_tree.modified)' - -# Worktrees with merge conflicts -$ wt list --format=json | jq '.[] | select(.operation_state == "conflicts")' - -# Branches ahead of main (needs merging) -$ wt list --format=json | jq '.[] | select(.main.ahead > 0) | .branch' - -# Integrated branches (safe to remove) -$ wt list --format=json | jq '.[] | select(.main_state == "integrated" or .main_state == "empty") | .branch' - -# Branches without worktrees -$ wt list --format=json --branches | jq '.[] | select(.kind == "branch") | .branch' - -# Worktrees ahead of remote (needs pushing) -$ wt list --format=json | jq '.[] | select(.remote.ahead > 0) | {branch, ahead: .remote.ahead}' - -# Stale CI (local changes not reflected in CI) -$ wt list --format=json --full | jq '.[] | select(.ci.stale) | .branch' -``` +| Field | Type | Description | +|-------|------|-------------| +| `schema` | number | Format version; always `2` | +| `repo` | object | `{default_branch, forge}` — the branch every `default_branch` object measures against (absent when detection failed), and forge metadata derived from the primary remote (absent when no remote URL parses; see [repo object](#repo-object)) | +| `collected` | object | `{ci, summary}` — which gated fact families this run requested, so an absent `pr`/`checks`/`summary` reads as "not requested" rather than "none" | +| `items` | array | One object per row: a worktree, a local branch, or a remote-only branch | -**Fields:** +Item fields: | Field | Type | Description | |-------|------|-------------| -| `branch` | string/null | Branch name (null for detached HEAD) | -| `path` | string | Worktree path (absent for branches without worktrees) | -| `kind` | string | `"worktree"` or `"branch"` | -| `commit` | object | Commit info (see below) | -| `working_tree` | object | Working tree state (see below) | -| `main_state` | string | Relation to the default branch (see below) | -| `integration_reason` | string | Why branch is integrated (see below) | -| `operation_state` | string | `"conflicts"`, `"rebase"`, or `"merge"` (see [Worktree](#worktree)); absent when clean | -| `main` | object | Relationship to the default branch (see below); absent when is_main | -| `remote` | object | Tracking branch info (see below); absent when no tracking | -| `worktree` | object | Worktree metadata (see below) | -| `is_main` | boolean | Is the main worktree | -| `is_current` | boolean | Is the current worktree | -| `is_previous` | boolean | Previous worktree from wt switch | -| `ci` | object | CI status (see below); `--full` only, then absent when no PR/MR or branch workflow | -| `repo_url` | string | Repository web URL derived from the primary remote; absent when the remote URL cannot be parsed | -| `repo` | object | Structured repository metadata (see below); includes `remote` | -| `url` | string | Dev server URL from project config; absent when not configured | -| `url_active` | boolean | Whether the URL's port is listening; absent when not configured | -| `summary` | string | LLM-generated branch summary; `--full` only, then absent when not configured or no summary | -| `statusline` | string | Pre-formatted status with colors and links | -| `symbols` | string | Raw status symbols without colors (e.g., `"!?↓"`) | -| `vars` | object | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) (absent when empty) | -| `columns` | object | Rendered [custom column](#custom-columns) values keyed by header; empty cells omitted (absent when none configured) | - -### Commit object +| `branch` | string/null | Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in `remote` | +| `remote` | string | Remote name, present only on remote-only branch rows | +| `head` | object/null | HEAD commit (see [head object](#head-object)); null for unborn branches | +| `worktree` | object | Worktree facts (see [worktree object](#worktree-object)); absent on branch-only rows | +| `default_branch` | object | Relation to the default branch (see [default_branch object](#default-branch-object)); absent on the default branch itself | +| `upstream` | object | Tracking branch (see [upstream object](#upstream-object)); absent when none is configured | +| `pr` | object | Open PR/MR (see [pr object](#pr-object)); collected with `--full` | +| `checks` | object | CI pipeline (see [checks object](#checks-object)); collected with `--full` | +| `dev_server` | object | `{url, listening}` from the project's `list.url` template; absent when not configured | +| `summary` | string | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | +| `vars` | object | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | +| `display` | object | Rendered strings (see [display object](#display-object)) | + +### head object | Field | Type | Description | |-------|------|-------------| | `sha` | string | Full commit SHA (40 chars) | | `short_sha` | string | Short commit SHA, abbreviated per `core.abbrev` (auto-extends for ambiguous prefixes) | -| `message` | string | Commit message (first line) | -| `timestamp` | number | Unix timestamp | +| `subject` | string/null | Commit subject (first line); null when not loaded, as for a prunable worktree | +| `committed_at` | string/null | Committer time, RFC 3339 UTC; null when not loaded | + +### worktree object -### working_tree object +Present only on worktree rows. The location attributes are independent, so they co-occur — see [Worktree](#worktree) for the symbols, which pick one: + +| Field | Type | Description | +|-------|------|-------------| +| `path` | string | Worktree path | +| `main` | boolean | Is the main worktree | +| `current` | boolean | Is the worktree the command ran from | +| `previous` | boolean | Is the previous worktree (`wt switch -`) | +| `detached` | boolean | HEAD is detached | +| `locked` | object | `{reason}`; absent when not locked | +| `prunable` | object | `{reason}`; absent when git doesn't report the worktree prunable | +| `branch_mismatch` | boolean | Worktree isn't at the path its branch implies | +| `duplicate_branch` | boolean | Another worktree has the same branch checked out | +| `operation` | string/null | In-progress operation: `"merge"`, `"rebase"`, `"cherry_pick"`, `"revert"`, `"bisect"`; absent when none | +| `changes` | object/null | Working-tree state (see [changes object](#changes-object)) | + +### changes object The five change flags map to the [Working tree](#working-tree) symbols (`renamed` and `deleted` have none of their own): @@ -377,53 +320,74 @@ The five change flags map to the [Working tree](#working-tree) symbols (`renamed | `untracked` | boolean | Has untracked files | | `renamed` | boolean | Has renamed files | | `deleted` | boolean | Has deleted files | -| `diff` | object | Lines changed vs HEAD: `{added, deleted}` | +| `conflicted` | boolean/null | Tracked files carry merge conflicts | +| `diff` | object/null | Lines changed vs HEAD: `{added, deleted}` | -### main object +### default_branch object + +Independent facts; the table's priority-collapsed symbol is `display.state`. | Field | Type | Description | |-------|------|-------------| -| `ahead` | number | Commits ahead of the default branch | -| `behind` | number | Commits behind the default branch | -| `diff` | object | Lines changed vs the default branch: `{added, deleted}` | +| `ahead` | number/null | Commits ahead of the default branch (null for orphans) | +| `behind` | number/null | Commits behind the default branch (null for orphans) | +| `diff` | object/null | Lines changed vs the default branch: `{added, deleted}` | +| `orphan` | boolean/null | No common ancestor with the default branch | +| `integration` | object/null | `{reason}` — which check found the content [integrated](https://worktrunk.dev/remove/#branch-cleanup) (see [integration reasons](#integration-reasons)); absent when determined not-integrated, null when a dirty tree skipped the checks | +| `merge_conflicts` | boolean/null | Merging into the default branch would conflict, simulated locally with `git merge-tree` | -### remote object +### upstream object `ahead` / `behind` drive the [Remote](#remote) divergence symbol: | Field | Type | Description | |-------|------|-------------| -| `name` | string | Remote name (e.g., `"origin"`) | -| `branch` | string | Remote branch name | +| `remote` | string | Remote name (e.g., `"origin"`) | +| `branch` | string/null | Branch name on the remote | | `ahead` | number | Commits ahead of remote | | `behind` | number | Commits behind remote | -### worktree object - -Present only for worktree-kind items. `state` is the worktree-location attribute — see [Worktree](#worktree) for its symbols: +### pr object | Field | Type | Description | |-------|------|-------------| -| `state` | string | `"branch_worktree_mismatch"`, `"duplicate_branch"`, `"prunable"`, or `"locked"` (absent when normal) | -| `reason` | string | Reason for locked/prunable state | -| `detached` | boolean | HEAD is detached | +| `number` | integer/null | PR/MR number | +| `url` | string/null | URL to the PR/MR page | +| `review` | string | Review state (see [review states](#review-states)); absent when the forge reports no review signal | +| `mergeable` | boolean/null | False when the forge reports conflicts, null otherwise — the fetch records only the conflicted case | +| `repo` | object | Structured metadata for the repository the PR/MR targets, the upstream for fork PRs (see [repo object](#repo-object)); absent when the URL doesn't parse | -### ci object +### checks object | Field | Type | Description | |-------|------|-------------| -| `status` | string | CI status (see below) | +| `status` | string/null | `"passed"`, `"running"`, or `"failed"`; null when a conflicts report masks it | | `source` | string | `"pr"` (PR/MR) or `"branch"` (branch workflow) | -| `number` | integer | PR/MR number; absent for branch workflows | | `stale` | boolean | Local HEAD differs from remote (unpushed changes) | -| `url` | string | URL to the PR/MR page | -| `repo_url` | string | Web URL of the repo the PR/MR targets (the upstream for fork PRs); absent when `url` is absent or unrecognized | -| `repo` | object | Structured metadata for the repository the PR/MR targets; never includes `remote` | -| `review_state` | string | Review state (see below); absent when the forge reports no review signal | + +Three [CI status](#ci-status) values have no `checks.status` of their own, because the shape of `pr` and `checks` reports them: a fetch error (`⚠`) makes both null, no CI leaves `checks` absent, and merge conflicts leave `checks.status` null with `pr.mergeable` false. + +### dev_server object + +| Field | Type | Description | +|-------|------|-------------| +| `url` | string | Dev server URL from project config | +| `listening` | boolean/null | Whether the URL's port is listening | + +### display object + +Presentation only — every value here renders facts that appear elsewhere in the item: + +| Field | Type | Description | +|-------|------|-------------| +| `state` | string | The table's collapsed default-branch state (see [state values](#state-values)); absent when none applies | +| `symbols` | string | Raw status symbols without colors (e.g., `"!?↓"`) | +| `statusline` | string | Pre-formatted status with colors and links | +| `columns` | object | Rendered [custom column](#custom-columns) values keyed by header; empty cells omitted | ### repo object -Top-level `repo` describes the local checkout's repository as derived from the primary remote. `ci.repo` describes the repository targeted by the PR/MR URL in `ci.url` (for fork PRs, this is the upstream target). Existing `repo_url` and `ci.repo_url` fields remain available and carry the same URL as `repo.url` / `ci.repo.url`. +`repo.forge` describes the local checkout's repository as derived from the primary remote. `pr.repo` describes the repository targeted by the PR/MR (for fork PRs, the upstream target). | Field | Type | Description | |-------|------|-------------| @@ -433,27 +397,95 @@ Top-level `repo` describes the local checkout's repository as derived from the p | `owner` | string | Owner, organization, or namespace path | | `name` | string | Repository name | | `project` | string | Azure DevOps project name; absent for other providers | -| `remote` | string | Local remote name used for top-level repo metadata; absent from `ci.repo` | +| `remote` | string | Local remote name; present on `repo.forge`, absent from `pr.repo` | -### main_state values +### state values The single highest-priority state describing the branch's relation to the default branch; absent when none applies (a normal up-to-date branch). Each value is one Default-branch symbol — see [Default branch](#default-branch) for the symbol and the full meaning of each value (`"is_main"`, `"orphan"`, `"empty"`, `"integrated"`, `"would_conflict"`, `"same_commit"`, `"diverged"`, `"ahead"`, `"behind"`). -### integration_reason values +### integration reasons -Set only when `main_state == "integrated"` (the `⊂` symbol), recording which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same `⊂`: +`default_branch.integration.reason` records which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same `⊂`: | Value | Meaning | |-------|---------| +| `"same_commit"` | Branch HEAD is the default branch's commit | | `"ancestor"` | Branch HEAD is an ancestor of the default branch, which has moved past it | -| `"no-added-changes"` | The three-dot diff (`main...branch`) is empty — no file changes beyond the merge-base | -| `"trees-match"` | Different history, but the branch's tree is identical to the default branch's | -| `"merge-adds-nothing"` | The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) | -| `"patch-id-match"` | The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) | +| `"no_added_changes"` | The three-dot diff (`main...branch`) is empty — no file changes beyond the merge-base | +| `"trees_match"` | Different history, but the branch's tree is identical to the default branch's | +| `"merge_adds_nothing"` | The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) | +| `"patch_id_match"` | The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) | + +### review states + +`pr.review` is one of `"changes_requested"`, `"pending"`, `"draft"`, `"approved"`, absent when the forge reports no review signal. The [CI status](#ci-status) section is the single source: its table maps the colored values, and the notes below it cover `"draft"` and `"approved"`. The vocabulary matches Claude Code's statusline `pr.review_state` field. + +```console +# Current worktree path (for scripts) +$ wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' + +# Branches with uncommitted changes +$ wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' + +# Worktrees with merge conflicts +$ wt list --format=json | jq '.items[] | select(.worktree.changes.conflicted)' + +# Branches ahead of main (needs merging) +$ wt list --format=json | jq '.items[] | select(.default_branch.ahead > 0) | .branch' + +# Integrated branches (safe to remove) +$ wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' + +# Branches without worktrees +$ wt list --format=json --branches | jq '.items[] | select(.worktree == null) | .branch' + +# Worktrees ahead of upstream (needs pushing) +$ wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | {branch, ahead: .upstream.ahead}' + +# Stale CI (local changes not reflected in CI) +$ wt list --format=json --full | jq '.items[] | select(.checks.stale) | .branch' +``` -### ci.status and ci.review_state values +A JSON Schema for the envelope is published at +[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json). +It describes what `wt` writes, so a field the absence rule can omit is +optional there rather than required-and-null. + +### Schema 1 -The [CI status](#ci-status) section above is the single source for both fields: the table maps each colored value, and the notes below it cover `"draft"` and `"approved"`. `ci.status` is one of `"passed"`, `"running"`, `"failed"`, `"conflicts"`, `"no-ci"`, `"error"`; `ci.review_state` is one of `"changes_requested"`, `"pending"`, `"draft"`, `"approved"`, absent when the forge reports no review signal. The vocabulary matches Claude Code's statusline `pr.review_state` field. +The original bare-array format — one object per row, no envelope — and the +default while `[list] json-schema` is unset. It is on its way out: `wt config +update` adopts `= 2`, a future release flips the default, and a later one +removes schema 1. Its fields all have a schema-2 home: + +| Schema 1 | Schema 2 | +|----------|----------| +| `branch` | `branch` | +| `path` | `worktree.path` | +| `kind` | the row's shape — a `worktree` object means `"worktree"`, no `worktree` object means `"branch"` | +| `commit.sha`, `.short_sha`, `.message` | `head.sha`, `.short_sha`, `.subject` | +| `commit.timestamp` (Unix) | `head.committed_at` (RFC 3339 UTC) | +| `working_tree.staged`, `.modified`, `.untracked`, `.renamed`, `.deleted`, `.diff` | `worktree.changes.*` | +| `operation_state` `"conflicts"` | `worktree.changes.conflicted` | +| `operation_state` `"rebase"`, `"merge"`, … | `worktree.operation` | +| `main_state` | `display.state` | +| `integration_reason` | `default_branch.integration.reason` (snake_case, and it reports `"same_commit"` rather than folding it into `main_state`) | +| `main.ahead`, `.behind`, `.diff` | `default_branch.ahead`, `.behind`, `.diff` | +| `remote.name`, `.branch`, `.ahead`, `.behind` | `upstream.remote`, `.branch`, `.ahead`, `.behind` | +| `worktree.state` | `worktree.locked`, `.prunable`, `.duplicate_branch`, `.branch_mismatch` — independent, so they co-occur | +| `worktree.reason` | `worktree.locked.reason`, `worktree.prunable.reason` | +| `worktree.detached` | `worktree.detached` | +| `is_main`, `is_current`, `is_previous` | `worktree.main`, `.current`, `.previous` | +| `ci.status` | `checks.status`, plus the shapes described under [checks object](#checks-object) | +| `ci.source`, `ci.stale` | `checks.source`, `checks.stale` | +| `ci.number`, `ci.url`, `ci.review_state` | `pr.number`, `pr.url`, `pr.review` | +| `ci.repo`, `ci.repo_url` | `pr.repo`, `pr.repo.url` | +| `repo`, `repo_url` | the envelope's `repo.forge`, `repo.forge.url` | +| `url`, `url_active` | `dev_server.url`, `dev_server.listening` | +| `summary`, `vars` | `summary`, `vars` | +| `statusline`, `symbols`, `columns` | `display.statusline`, `display.symbols`, `display.columns` | + +The envelope's `repo.default_branch` and `collected` have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see [How "no value" reads](#schema-2). Missing a field that would be generally useful? Open an issue at https://github.com/max-sixty/worktrunk. diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 01bb88201e..013d0229ce 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -98,7 +98,7 @@ The leftmost column marks each row by physical presence, from most present to le ### CI status -The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Azure DevOps, `!3035` on GitLab — colored by pipeline status, or a bare `#` when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are `ci.status`; magenta/cyan are `ci.review_state`. The `Value` column is the matching JSON string from `--format=json`: +The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Azure DevOps, `!3035` on GitLab — colored by pipeline status, or a bare `#` when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are the pipeline state, magenta/cyan the review state. The `Value` column names each state; [checks object](#checks-object) and [review states](#review-states) give the JSON spelling — schema 2 reports three of the pipeline values by the shape of `pr` and `checks` rather than as a `checks.status` string: | Indicator | Value | Meaning | |-----------|-------|---------| @@ -110,13 +110,13 @@ The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Az | `⚠` yellow | `"error"` | CI status could not be fetched (rate limit, network, etc.) | | `#` magenta | `"changes_requested"` | A reviewer requested changes | | `#` cyan | `"pending"` | A review is required (e.g. branch protection) but not yet given | -| (blank) | `ci` absent | No upstream, or no PR/MR and no branch workflow | +| (blank) | `pr` and `checks` absent | No upstream, or no PR/MR and no branch workflow | -The two remaining `ci.review_state` values have no indicator of their own: `"draft"` only dims the cell and `"approved"` leaves the color unchanged. +The two remaining review states have no indicator of their own: `"draft"` only dims the cell and `"approved"` leaves the color unchanged. -Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain `ci.status` color — `ci.review_state` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. +Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — `pr.review` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`ci.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. ### LLM summaries @@ -135,19 +135,19 @@ A column that renders empty for every row is dropped from the table. Templates, ## Status symbols -The Status column packs several subcolumns, left to right, each mapping to a field in `--format=json`. Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. +The Status column packs several subcolumns, left to right, each mapping to a schema-2 field in `--format=json` (schema 1 spells several of them differently — see [Schema 1](#schema-1)). Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. ### Working tree -Independent flags from `git status`; several can show at once (e.g. `+!?`). Each maps to a boolean in the `working_tree` object: +Independent flags from `git status`; several can show at once (e.g. `+!?`). Each maps to a boolean in the [changes object](#changes-object): -| Symbol | working_tree | Meaning | -|--------|--------------|---------| +| Symbol | worktree.changes | Meaning | +|--------|------------------|---------| | `+` | `staged` | Staged files | | `!` | `modified` | Modified files (unstaged) | | `?` | `untracked` | Untracked files | -`working_tree` also reports `renamed` and `deleted`, which have no dedicated symbol in the column. +`worktree.changes` also reports `renamed` and `deleted`, which have no dedicated symbol in the column. ### Worktree @@ -155,24 +155,24 @@ An in-progress git operation, a worktree-location attribute, or a branch with no | Symbol | JSON | Meaning | |--------|------|---------| -| `✘` | `operation_state` `"conflicts"` | Merge conflicts | -| `↻` | `operation_state` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | -| `⊟` | `worktree.state` `"prunable"` | Prunable (worktree directory missing) | -| `⊞` | `worktree.state` `"locked"` | Locked worktree | -| `⚑` | `worktree.state` `"duplicate_branch"` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | -| `⚑` | `worktree.state` `"branch_worktree_mismatch"` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | -| `/` | `kind` `"branch"` | Branch without a worktree (no `worktree` object) | +| `✘` | `worktree.changes.conflicted` | Merge conflicts | +| `↻` | `worktree.operation` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | +| `⊟` | `worktree.prunable` | Prunable (worktree directory missing) | +| `⊞` | `worktree.locked` | Locked worktree | +| `⚑` | `worktree.duplicate_branch` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | +| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | +| `/` | no `worktree` object | Branch without a worktree | ### Default branch -The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one `main_state` value: +The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one `display.state` value: -| Symbol | main_state | Meaning | -|--------|------------|---------| +| Symbol | display.state | Meaning | +|--------|---------------|---------| | `^` | `"is_main"` | The main worktree (the repo's home worktree) | | `∅` | `"orphan"` | No common ancestor with the default branch | | `_` | `"empty"` | Same commit as the default branch, working tree clean — safe to remove; row dimmed | -| `⊂` | `"integrated"` | Content [integrated](https://worktrunk.dev/remove/#branch-cleanup) into the default branch or merge target via different history; the matching check is in `integration_reason`; row dimmed | +| `⊂` | `"integrated"` | Content [integrated](https://worktrunk.dev/remove/#branch-cleanup) into the default branch or merge target via different history; the matching check is in `default_branch.integration.reason`; row dimmed | | `✗` | `"would_conflict"` | Merging into the default branch would conflict (simulated with `git merge-tree`) and the branch isn't already integrated; with `--full`, the check includes tracked uncommitted changes | | `–` | `"same_commit"` | Same commit as the default branch, but with uncommitted changes | | `↕` | `"diverged"` | Both ahead of and behind the default branch | @@ -183,10 +183,10 @@ Rows are dimmed when [safe to delete](https://worktrunk.dev/remove/#branch-clean ### Remote -Relation to the tracking branch, derived from the `remote.ahead` / `remote.behind` counts; blank when there is no upstream: +Relation to the tracking branch, derived from the `upstream.ahead` / `upstream.behind` counts; blank when there is no upstream: -| Symbol | remote | Meaning | -|--------|--------|---------| +| Symbol | upstream | Meaning | +|--------|----------|---------| | `\|` | `ahead` 0, `behind` 0 | In sync with remote | | `⇡` | `ahead` > 0 | Ahead of remote | | `⇣` | `behind` > 0 | Behind remote | @@ -256,117 +256,60 @@ How "no value" reads: jq treats absent and `null` identically in path expressions, so filters need no null checks; `has()` distinguishes the two when it matters. -Item fields: - -| Field | Description | -|-------|-------------| -| `branch` | Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in `remote` | -| `remote` | Remote name, present only on remote-only branch rows | -| `head` | `{sha, short_sha, subject, committed_at}`; null for unborn branches. `committed_at` is RFC 3339 UTC | -| `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | -| `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | -| `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full`. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | -| `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | -| `vars` | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | -| `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | - -Schema 1 names map directly: `commit` → `head`, `working_tree` → -`worktree.changes`, `main` + `main_state` → `default_branch` + -`display.state`, `remote` → `upstream`, `ci` → `pr` + `checks`, `url` + -`url_active` → `dev_server`, `statusline`/`symbols`/`columns` → `display.*`, -and the per-item `repo` moves to the envelope's `repo.forge`. +Envelope fields: -```console -# Current worktree path (for scripts) -$ wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' - -# Branches with uncommitted changes -$ wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' - -# Integrated branches (safe to remove) -$ wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' - -# Worktrees ahead of upstream (needs pushing) -$ wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | .branch' -``` - -A JSON Schema for the envelope is published at -[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json). -It describes what `wt` writes, so a field the absence rule can omit is -optional there rather than required-and-null. - -### Schema 1 - -The original bare-array format, and the default while unset: - -```console -# Current worktree path (for scripts) -$ wt list --format=json | jq -r '.[] | select(.is_current) | .path' - -# Branches with uncommitted changes -$ wt list --format=json | jq '.[] | select(.working_tree.modified)' - -# Worktrees with merge conflicts -$ wt list --format=json | jq '.[] | select(.operation_state == "conflicts")' - -# Branches ahead of main (needs merging) -$ wt list --format=json | jq '.[] | select(.main.ahead > 0) | .branch' - -# Integrated branches (safe to remove) -$ wt list --format=json | jq '.[] | select(.main_state == "integrated" or .main_state == "empty") | .branch' - -# Branches without worktrees -$ wt list --format=json --branches | jq '.[] | select(.kind == "branch") | .branch' - -# Worktrees ahead of remote (needs pushing) -$ wt list --format=json | jq '.[] | select(.remote.ahead > 0) | {branch, ahead: .remote.ahead}' - -# Stale CI (local changes not reflected in CI) -$ wt list --format=json --full | jq '.[] | select(.ci.stale) | .branch' -``` +| Field | Type | Description | +|-------|------|-------------| +| `schema` | number | Format version; always `2` | +| `repo` | object | `{default_branch, forge}` — the branch every `default_branch` object measures against (absent when detection failed), and forge metadata derived from the primary remote (absent when no remote URL parses; see [repo object](#repo-object)) | +| `collected` | object | `{ci, summary}` — which gated fact families this run requested, so an absent `pr`/`checks`/`summary` reads as "not requested" rather than "none" | +| `items` | array | One object per row: a worktree, a local branch, or a remote-only branch | -**Fields:** +Item fields: | Field | Type | Description | |-------|------|-------------| -| `branch` | string/null | Branch name (null for detached HEAD) | -| `path` | string | Worktree path (absent for branches without worktrees) | -| `kind` | string | `"worktree"` or `"branch"` | -| `commit` | object | Commit info (see below) | -| `working_tree` | object | Working tree state (see below) | -| `main_state` | string | Relation to the default branch (see below) | -| `integration_reason` | string | Why branch is integrated (see below) | -| `operation_state` | string | `"conflicts"`, `"rebase"`, or `"merge"` (see [Worktree](#worktree)); absent when clean | -| `main` | object | Relationship to the default branch (see below); absent when is_main | -| `remote` | object | Tracking branch info (see below); absent when no tracking | -| `worktree` | object | Worktree metadata (see below) | -| `is_main` | boolean | Is the main worktree | -| `is_current` | boolean | Is the current worktree | -| `is_previous` | boolean | Previous worktree from wt switch | -| `ci` | object | CI status (see below); `--full` only, then absent when no PR/MR or branch workflow | -| `repo_url` | string | Repository web URL derived from the primary remote; absent when the remote URL cannot be parsed | -| `repo` | object | Structured repository metadata (see below); includes `remote` | -| `url` | string | Dev server URL from project config; absent when not configured | -| `url_active` | boolean | Whether the URL's port is listening; absent when not configured | -| `summary` | string | LLM-generated branch summary; `--full` only, then absent when not configured or no summary | -| `statusline` | string | Pre-formatted status with colors and links | -| `symbols` | string | Raw status symbols without colors (e.g., `"!?↓"`) | -| `vars` | object | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) (absent when empty) | -| `columns` | object | Rendered [custom column](#custom-columns) values keyed by header; empty cells omitted (absent when none configured) | - -### Commit object +| `branch` | string/null | Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in `remote` | +| `remote` | string | Remote name, present only on remote-only branch rows | +| `head` | object/null | HEAD commit (see [head object](#head-object)); null for unborn branches | +| `worktree` | object | Worktree facts (see [worktree object](#worktree-object)); absent on branch-only rows | +| `default_branch` | object | Relation to the default branch (see [default_branch object](#default-branch-object)); absent on the default branch itself | +| `upstream` | object | Tracking branch (see [upstream object](#upstream-object)); absent when none is configured | +| `pr` | object | Open PR/MR (see [pr object](#pr-object)); collected with `--full` | +| `checks` | object | CI pipeline (see [checks object](#checks-object)); collected with `--full` | +| `dev_server` | object | `{url, listening}` from the project's `list.url` template; absent when not configured | +| `summary` | string | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | +| `vars` | object | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | +| `display` | object | Rendered strings (see [display object](#display-object)) | + +### head object | Field | Type | Description | |-------|------|-------------| | `sha` | string | Full commit SHA (40 chars) | | `short_sha` | string | Short commit SHA, abbreviated per `core.abbrev` (auto-extends for ambiguous prefixes) | -| `message` | string | Commit message (first line) | -| `timestamp` | number | Unix timestamp | +| `subject` | string/null | Commit subject (first line); null when not loaded, as for a prunable worktree | +| `committed_at` | string/null | Committer time, RFC 3339 UTC; null when not loaded | + +### worktree object -### working_tree object +Present only on worktree rows. The location attributes are independent, so they co-occur — see [Worktree](#worktree) for the symbols, which pick one: + +| Field | Type | Description | +|-------|------|-------------| +| `path` | string | Worktree path | +| `main` | boolean | Is the main worktree | +| `current` | boolean | Is the worktree the command ran from | +| `previous` | boolean | Is the previous worktree (`wt switch -`) | +| `detached` | boolean | HEAD is detached | +| `locked` | object | `{reason}`; absent when not locked | +| `prunable` | object | `{reason}`; absent when git doesn't report the worktree prunable | +| `branch_mismatch` | boolean | Worktree isn't at the path its branch implies | +| `duplicate_branch` | boolean | Another worktree has the same branch checked out | +| `operation` | string/null | In-progress operation: `"merge"`, `"rebase"`, `"cherry_pick"`, `"revert"`, `"bisect"`; absent when none | +| `changes` | object/null | Working-tree state (see [changes object](#changes-object)) | + +### changes object The five change flags map to the [Working tree](#working-tree) symbols (`renamed` and `deleted` have none of their own): @@ -377,53 +320,74 @@ The five change flags map to the [Working tree](#working-tree) symbols (`renamed | `untracked` | boolean | Has untracked files | | `renamed` | boolean | Has renamed files | | `deleted` | boolean | Has deleted files | -| `diff` | object | Lines changed vs HEAD: `{added, deleted}` | +| `conflicted` | boolean/null | Tracked files carry merge conflicts | +| `diff` | object/null | Lines changed vs HEAD: `{added, deleted}` | -### main object +### default_branch object + +Independent facts; the table's priority-collapsed symbol is `display.state`. | Field | Type | Description | |-------|------|-------------| -| `ahead` | number | Commits ahead of the default branch | -| `behind` | number | Commits behind the default branch | -| `diff` | object | Lines changed vs the default branch: `{added, deleted}` | +| `ahead` | number/null | Commits ahead of the default branch (null for orphans) | +| `behind` | number/null | Commits behind the default branch (null for orphans) | +| `diff` | object/null | Lines changed vs the default branch: `{added, deleted}` | +| `orphan` | boolean/null | No common ancestor with the default branch | +| `integration` | object/null | `{reason}` — which check found the content [integrated](https://worktrunk.dev/remove/#branch-cleanup) (see [integration reasons](#integration-reasons)); absent when determined not-integrated, null when a dirty tree skipped the checks | +| `merge_conflicts` | boolean/null | Merging into the default branch would conflict, simulated locally with `git merge-tree` | -### remote object +### upstream object `ahead` / `behind` drive the [Remote](#remote) divergence symbol: | Field | Type | Description | |-------|------|-------------| -| `name` | string | Remote name (e.g., `"origin"`) | -| `branch` | string | Remote branch name | +| `remote` | string | Remote name (e.g., `"origin"`) | +| `branch` | string/null | Branch name on the remote | | `ahead` | number | Commits ahead of remote | | `behind` | number | Commits behind remote | -### worktree object - -Present only for worktree-kind items. `state` is the worktree-location attribute — see [Worktree](#worktree) for its symbols: +### pr object | Field | Type | Description | |-------|------|-------------| -| `state` | string | `"branch_worktree_mismatch"`, `"duplicate_branch"`, `"prunable"`, or `"locked"` (absent when normal) | -| `reason` | string | Reason for locked/prunable state | -| `detached` | boolean | HEAD is detached | +| `number` | integer/null | PR/MR number | +| `url` | string/null | URL to the PR/MR page | +| `review` | string | Review state (see [review states](#review-states)); absent when the forge reports no review signal | +| `mergeable` | boolean/null | False when the forge reports conflicts, null otherwise — the fetch records only the conflicted case | +| `repo` | object | Structured metadata for the repository the PR/MR targets, the upstream for fork PRs (see [repo object](#repo-object)); absent when the URL doesn't parse | -### ci object +### checks object | Field | Type | Description | |-------|------|-------------| -| `status` | string | CI status (see below) | +| `status` | string/null | `"passed"`, `"running"`, or `"failed"`; null when a conflicts report masks it | | `source` | string | `"pr"` (PR/MR) or `"branch"` (branch workflow) | -| `number` | integer | PR/MR number; absent for branch workflows | | `stale` | boolean | Local HEAD differs from remote (unpushed changes) | -| `url` | string | URL to the PR/MR page | -| `repo_url` | string | Web URL of the repo the PR/MR targets (the upstream for fork PRs); absent when `url` is absent or unrecognized | -| `repo` | object | Structured metadata for the repository the PR/MR targets; never includes `remote` | -| `review_state` | string | Review state (see below); absent when the forge reports no review signal | + +Three [CI status](#ci-status) values have no `checks.status` of their own, because the shape of `pr` and `checks` reports them: a fetch error (`⚠`) makes both null, no CI leaves `checks` absent, and merge conflicts leave `checks.status` null with `pr.mergeable` false. + +### dev_server object + +| Field | Type | Description | +|-------|------|-------------| +| `url` | string | Dev server URL from project config | +| `listening` | boolean/null | Whether the URL's port is listening | + +### display object + +Presentation only — every value here renders facts that appear elsewhere in the item: + +| Field | Type | Description | +|-------|------|-------------| +| `state` | string | The table's collapsed default-branch state (see [state values](#state-values)); absent when none applies | +| `symbols` | string | Raw status symbols without colors (e.g., `"!?↓"`) | +| `statusline` | string | Pre-formatted status with colors and links | +| `columns` | object | Rendered [custom column](#custom-columns) values keyed by header; empty cells omitted | ### repo object -Top-level `repo` describes the local checkout's repository as derived from the primary remote. `ci.repo` describes the repository targeted by the PR/MR URL in `ci.url` (for fork PRs, this is the upstream target). Existing `repo_url` and `ci.repo_url` fields remain available and carry the same URL as `repo.url` / `ci.repo.url`. +`repo.forge` describes the local checkout's repository as derived from the primary remote. `pr.repo` describes the repository targeted by the PR/MR (for fork PRs, the upstream target). | Field | Type | Description | |-------|------|-------------| @@ -433,27 +397,95 @@ Top-level `repo` describes the local checkout's repository as derived from the p | `owner` | string | Owner, organization, or namespace path | | `name` | string | Repository name | | `project` | string | Azure DevOps project name; absent for other providers | -| `remote` | string | Local remote name used for top-level repo metadata; absent from `ci.repo` | +| `remote` | string | Local remote name; present on `repo.forge`, absent from `pr.repo` | -### main_state values +### state values The single highest-priority state describing the branch's relation to the default branch; absent when none applies (a normal up-to-date branch). Each value is one Default-branch symbol — see [Default branch](#default-branch) for the symbol and the full meaning of each value (`"is_main"`, `"orphan"`, `"empty"`, `"integrated"`, `"would_conflict"`, `"same_commit"`, `"diverged"`, `"ahead"`, `"behind"`). -### integration_reason values +### integration reasons -Set only when `main_state == "integrated"` (the `⊂` symbol), recording which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same `⊂`: +`default_branch.integration.reason` records which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same `⊂`: | Value | Meaning | |-------|---------| +| `"same_commit"` | Branch HEAD is the default branch's commit | | `"ancestor"` | Branch HEAD is an ancestor of the default branch, which has moved past it | -| `"no-added-changes"` | The three-dot diff (`main...branch`) is empty — no file changes beyond the merge-base | -| `"trees-match"` | Different history, but the branch's tree is identical to the default branch's | -| `"merge-adds-nothing"` | The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) | -| `"patch-id-match"` | The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) | +| `"no_added_changes"` | The three-dot diff (`main...branch`) is empty — no file changes beyond the merge-base | +| `"trees_match"` | Different history, but the branch's tree is identical to the default branch's | +| `"merge_adds_nothing"` | The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) | +| `"patch_id_match"` | The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) | + +### review states + +`pr.review` is one of `"changes_requested"`, `"pending"`, `"draft"`, `"approved"`, absent when the forge reports no review signal. The [CI status](#ci-status) section is the single source: its table maps the colored values, and the notes below it cover `"draft"` and `"approved"`. The vocabulary matches Claude Code's statusline `pr.review_state` field. + +```console +# Current worktree path (for scripts) +$ wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' + +# Branches with uncommitted changes +$ wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' + +# Worktrees with merge conflicts +$ wt list --format=json | jq '.items[] | select(.worktree.changes.conflicted)' + +# Branches ahead of main (needs merging) +$ wt list --format=json | jq '.items[] | select(.default_branch.ahead > 0) | .branch' + +# Integrated branches (safe to remove) +$ wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' + +# Branches without worktrees +$ wt list --format=json --branches | jq '.items[] | select(.worktree == null) | .branch' + +# Worktrees ahead of upstream (needs pushing) +$ wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | {branch, ahead: .upstream.ahead}' + +# Stale CI (local changes not reflected in CI) +$ wt list --format=json --full | jq '.items[] | select(.checks.stale) | .branch' +``` -### ci.status and ci.review_state values +A JSON Schema for the envelope is published at +[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json). +It describes what `wt` writes, so a field the absence rule can omit is +optional there rather than required-and-null. + +### Schema 1 -The [CI status](#ci-status) section above is the single source for both fields: the table maps each colored value, and the notes below it cover `"draft"` and `"approved"`. `ci.status` is one of `"passed"`, `"running"`, `"failed"`, `"conflicts"`, `"no-ci"`, `"error"`; `ci.review_state` is one of `"changes_requested"`, `"pending"`, `"draft"`, `"approved"`, absent when the forge reports no review signal. The vocabulary matches Claude Code's statusline `pr.review_state` field. +The original bare-array format — one object per row, no envelope — and the +default while `[list] json-schema` is unset. It is on its way out: `wt config +update` adopts `= 2`, a future release flips the default, and a later one +removes schema 1. Its fields all have a schema-2 home: + +| Schema 1 | Schema 2 | +|----------|----------| +| `branch` | `branch` | +| `path` | `worktree.path` | +| `kind` | the row's shape — a `worktree` object means `"worktree"`, no `worktree` object means `"branch"` | +| `commit.sha`, `.short_sha`, `.message` | `head.sha`, `.short_sha`, `.subject` | +| `commit.timestamp` (Unix) | `head.committed_at` (RFC 3339 UTC) | +| `working_tree.staged`, `.modified`, `.untracked`, `.renamed`, `.deleted`, `.diff` | `worktree.changes.*` | +| `operation_state` `"conflicts"` | `worktree.changes.conflicted` | +| `operation_state` `"rebase"`, `"merge"`, … | `worktree.operation` | +| `main_state` | `display.state` | +| `integration_reason` | `default_branch.integration.reason` (snake_case, and it reports `"same_commit"` rather than folding it into `main_state`) | +| `main.ahead`, `.behind`, `.diff` | `default_branch.ahead`, `.behind`, `.diff` | +| `remote.name`, `.branch`, `.ahead`, `.behind` | `upstream.remote`, `.branch`, `.ahead`, `.behind` | +| `worktree.state` | `worktree.locked`, `.prunable`, `.duplicate_branch`, `.branch_mismatch` — independent, so they co-occur | +| `worktree.reason` | `worktree.locked.reason`, `worktree.prunable.reason` | +| `worktree.detached` | `worktree.detached` | +| `is_main`, `is_current`, `is_previous` | `worktree.main`, `.current`, `.previous` | +| `ci.status` | `checks.status`, plus the shapes described under [checks object](#checks-object) | +| `ci.source`, `ci.stale` | `checks.source`, `checks.stale` | +| `ci.number`, `ci.url`, `ci.review_state` | `pr.number`, `pr.url`, `pr.review` | +| `ci.repo`, `ci.repo_url` | `pr.repo`, `pr.repo.url` | +| `repo`, `repo_url` | the envelope's `repo.forge`, `repo.forge.url` | +| `url`, `url_active` | `dev_server.url`, `dev_server.listening` | +| `summary`, `vars` | `summary`, `vars` | +| `statusline`, `symbols`, `columns` | `display.statusline`, `display.symbols`, `display.columns` | + +The envelope's `repo.default_branch` and `collected` have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see [How "no value" reads](#schema-2). Missing a field that would be generally useful? Open an issue at https://github.com/max-sixty/worktrunk. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 4ad45620a5..97e6a1b682 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -879,7 +879,7 @@ The leftmost column marks each row by physical presence, from most present to le ### CI status -The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Azure DevOps, `!3035` on GitLab — colored by pipeline status, or a bare `#` when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are `ci.status`; magenta/cyan are `ci.review_state`. The `Value` column is the matching JSON string from `--format=json`: +The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Azure DevOps, `!3035` on GitLab — colored by pipeline status, or a bare `#` when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are the pipeline state, magenta/cyan the review state. The `Value` column names each state; [checks object](#checks-object) and [review states](#review-states) give the JSON spelling — schema 2 reports three of the pipeline values by the shape of `pr` and `checks` rather than as a `checks.status` string: | Indicator | Value | Meaning | |-----------|-------|---------| @@ -891,13 +891,13 @@ The CI column shows the branch's open PR/MR — `#3035` on GitHub, Gitea, and Az | `⚠` yellow | `"error"` | CI status could not be fetched (rate limit, network, etc.) | | `#` magenta | `"changes_requested"` | A reviewer requested changes | | `#` cyan | `"pending"` | A review is required (e.g. branch protection) but not yet given | -| (blank) | `ci` absent | No upstream, or no PR/MR and no branch workflow | +| (blank) | `pr` and `checks` absent | No upstream, or no PR/MR and no branch workflow | -The two remaining `ci.review_state` values have no indicator of their own: `"draft"` only dims the cell and `"approved"` leaves the color unchanged. +The two remaining review states have no indicator of their own: `"draft"` only dims the cell and `"approved"` leaves the color unchanged. -Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain `ci.status` color — `ci.review_state` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. +Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — `pr.review` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`ci.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. ### LLM summaries @@ -916,19 +916,19 @@ A column that renders empty for every row is dropped from the table. Templates, ## Status symbols -The Status column packs several subcolumns, left to right, each mapping to a field in `--format=json`. Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. +The Status column packs several subcolumns, left to right, each mapping to a schema-2 field in `--format=json` (schema 1 spells several of them differently — see [Schema 1](#schema-1)). Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. ### Working tree -Independent flags from `git status`; several can show at once (e.g. `+!?`). Each maps to a boolean in the `working_tree` object: +Independent flags from `git status`; several can show at once (e.g. `+!?`). Each maps to a boolean in the [changes object](#changes-object): -| Symbol | working_tree | Meaning | -|--------|--------------|---------| +| Symbol | worktree.changes | Meaning | +|--------|------------------|---------| | `+` | `staged` | Staged files | | `!` | `modified` | Modified files (unstaged) | | `?` | `untracked` | Untracked files | -`working_tree` also reports `renamed` and `deleted`, which have no dedicated symbol in the column. +`worktree.changes` also reports `renamed` and `deleted`, which have no dedicated symbol in the column. ### Worktree @@ -936,24 +936,24 @@ An in-progress git operation, a worktree-location attribute, or a branch with no | Symbol | JSON | Meaning | |--------|------|---------| -| `✘` | `operation_state` `"conflicts"` | Merge conflicts | -| `↻` | `operation_state` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | -| `⊟` | `worktree.state` `"prunable"` | Prunable (worktree directory missing) | -| `⊞` | `worktree.state` `"locked"` | Locked worktree | -| `⚑` | `worktree.state` `"duplicate_branch"` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | -| `⚑` | `worktree.state` `"branch_worktree_mismatch"` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | -| `/` | `kind` `"branch"` | Branch without a worktree (no `worktree` object) | +| `✘` | `worktree.changes.conflicted` | Merge conflicts | +| `↻` | `worktree.operation` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | +| `⊟` | `worktree.prunable` | Prunable (worktree directory missing) | +| `⊞` | `worktree.locked` | Locked worktree | +| `⚑` | `worktree.duplicate_branch` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | +| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | +| `/` | no `worktree` object | Branch without a worktree | ### Default branch -The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one `main_state` value: +The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one `display.state` value: -| Symbol | main_state | Meaning | -|--------|------------|---------| +| Symbol | display.state | Meaning | +|--------|---------------|---------| | `^` | `"is_main"` | The main worktree (the repo's home worktree) | | `∅` | `"orphan"` | No common ancestor with the default branch | | `_` | `"empty"` | Same commit as the default branch, working tree clean — safe to remove; row dimmed | -| `⊂` | `"integrated"` | Content [integrated](/remove/#branch-cleanup) into the default branch or merge target via different history; the matching check is in `integration_reason`; row dimmed | +| `⊂` | `"integrated"` | Content [integrated](/remove/#branch-cleanup) into the default branch or merge target via different history; the matching check is in `default_branch.integration.reason`; row dimmed | | `✗` | `"would_conflict"` | Merging into the default branch would conflict (simulated with `git merge-tree`) and the branch isn't already integrated; with `--full`, the check includes tracked uncommitted changes | | `–` | `"same_commit"` | Same commit as the default branch, but with uncommitted changes | | `↕` | `"diverged"` | Both ahead of and behind the default branch | @@ -964,10 +964,10 @@ Rows are dimmed when [safe to delete](/remove/#branch-cleanup) — `_` (`"empty" ### Remote -Relation to the tracking branch, derived from the `remote.ahead` / `remote.behind` counts; blank when there is no upstream: +Relation to the tracking branch, derived from the `upstream.ahead` / `upstream.behind` counts; blank when there is no upstream: -| Symbol | remote | Meaning | -|--------|--------|---------| +| Symbol | upstream | Meaning | +|--------|----------|---------| | `\|` | `ahead` 0, `behind` 0 | In sync with remote | | `⇡` | `ahead` > 0 | Ahead of remote | | `⇣` | `behind` > 0 | Behind remote | @@ -1037,117 +1037,60 @@ How "no value" reads: jq treats absent and `null` identically in path expressions, so filters need no null checks; `has()` distinguishes the two when it matters. -Item fields: - -| Field | Description | -|-------|-------------| -| `branch` | Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in `remote` | -| `remote` | Remote name, present only on remote-only branch rows | -| `head` | `{sha, short_sha, subject, committed_at}`; null for unborn branches. `committed_at` is RFC 3339 UTC | -| `worktree` | `{path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}`; absent on branch-only rows. `locked`/`prunable` are `{reason}` objects and can co-occur; `operation` is `"rebase"` or `"merge"`; `changes` holds the five working-tree flags plus `conflicted` and `diff {added, deleted}` | -| `default_branch` | Relation to the default branch: `{ahead, behind, diff, orphan, integration, merge_conflicts}`; absent on the default branch itself. `integration.reason` is one of `same_commit`, `ancestor`, `no_added_changes`, `trees_match`, `merge_adds_nothing`, `patch_id_match`; a dirty tree skips the checks, leaving `integration` null | -| `upstream` | Tracking branch: `{remote, branch, ahead, behind}`; absent when none is configured | -| `pr` | Open PR/MR: `{number, url, review, mergeable, repo}`; collected with `--full`. `review` uses the schema 1 `ci.review_state` vocabulary; `mergeable` is false when the forge reports conflicts, null otherwise | -| `checks` | CI pipeline: `{status, source, stale}`; collected with `--full`. `status` is `passed`, `running`, or `failed` — null when a conflicts report masks it | -| `dev_server` | `{url, listening}` from the project's `list.url` template | -| `summary` | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | -| `vars` | Per-branch variables from [`wt config state vars`](/config/#wt-config-state-vars) | -| `display` | Rendered strings: `state` (schema 1's `main_state` vocabulary), `symbols`, `statusline` (with ANSI colors and OSC 8 hyperlinks), `columns` (custom-column cells keyed by header) | - -Schema 1 names map directly: `commit` → `head`, `working_tree` → -`worktree.changes`, `main` + `main_state` → `default_branch` + -`display.state`, `remote` → `upstream`, `ci` → `pr` + `checks`, `url` + -`url_active` → `dev_server`, `statusline`/`symbols`/`columns` → `display.*`, -and the per-item `repo` moves to the envelope's `repo.forge`. +Envelope fields: -```console -# Current worktree path (for scripts) -$ wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' - -# Branches with uncommitted changes -$ wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' - -# Integrated branches (safe to remove) -$ wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' - -# Worktrees ahead of upstream (needs pushing) -$ wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | .branch' -``` - -A JSON Schema for the envelope is published at -[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json). -It describes what `wt` writes, so a field the absence rule can omit is -optional there rather than required-and-null. - -### Schema 1 - -The original bare-array format, and the default while unset: - -```console -# Current worktree path (for scripts) -$ wt list --format=json | jq -r '.[] | select(.is_current) | .path' - -# Branches with uncommitted changes -$ wt list --format=json | jq '.[] | select(.working_tree.modified)' - -# Worktrees with merge conflicts -$ wt list --format=json | jq '.[] | select(.operation_state == "conflicts")' - -# Branches ahead of main (needs merging) -$ wt list --format=json | jq '.[] | select(.main.ahead > 0) | .branch' - -# Integrated branches (safe to remove) -$ wt list --format=json | jq '.[] | select(.main_state == "integrated" or .main_state == "empty") | .branch' - -# Branches without worktrees -$ wt list --format=json --branches | jq '.[] | select(.kind == "branch") | .branch' - -# Worktrees ahead of remote (needs pushing) -$ wt list --format=json | jq '.[] | select(.remote.ahead > 0) | {branch, ahead: .remote.ahead}' - -# Stale CI (local changes not reflected in CI) -$ wt list --format=json --full | jq '.[] | select(.ci.stale) | .branch' -``` +| Field | Type | Description | +|-------|------|-------------| +| `schema` | number | Format version; always `2` | +| `repo` | object | `{default_branch, forge}` — the branch every `default_branch` object measures against (absent when detection failed), and forge metadata derived from the primary remote (absent when no remote URL parses; see [repo object](#repo-object)) | +| `collected` | object | `{ci, summary}` — which gated fact families this run requested, so an absent `pr`/`checks`/`summary` reads as "not requested" rather than "none" | +| `items` | array | One object per row: a worktree, a local branch, or a remote-only branch | -**Fields:** +Item fields: | Field | Type | Description | |-------|------|-------------| -| `branch` | string/null | Branch name (null for detached HEAD) | -| `path` | string | Worktree path (absent for branches without worktrees) | -| `kind` | string | `"worktree"` or `"branch"` | -| `commit` | object | Commit info (see below) | -| `working_tree` | object | Working tree state (see below) | -| `main_state` | string | Relation to the default branch (see below) | -| `integration_reason` | string | Why branch is integrated (see below) | -| `operation_state` | string | `"conflicts"`, `"rebase"`, or `"merge"` (see [Worktree](#worktree)); absent when clean | -| `main` | object | Relationship to the default branch (see below); absent when is_main | -| `remote` | object | Tracking branch info (see below); absent when no tracking | -| `worktree` | object | Worktree metadata (see below) | -| `is_main` | boolean | Is the main worktree | -| `is_current` | boolean | Is the current worktree | -| `is_previous` | boolean | Previous worktree from wt switch | -| `ci` | object | CI status (see below); `--full` only, then absent when no PR/MR or branch workflow | -| `repo_url` | string | Repository web URL derived from the primary remote; absent when the remote URL cannot be parsed | -| `repo` | object | Structured repository metadata (see below); includes `remote` | -| `url` | string | Dev server URL from project config; absent when not configured | -| `url_active` | boolean | Whether the URL's port is listening; absent when not configured | -| `summary` | string | LLM-generated branch summary; `--full` only, then absent when not configured or no summary | -| `statusline` | string | Pre-formatted status with colors and links | -| `symbols` | string | Raw status symbols without colors (e.g., `"!?↓"`) | -| `vars` | object | Per-branch variables from [`wt config state vars`](/config/#wt-config-state-vars) (absent when empty) | -| `columns` | object | Rendered [custom column](#custom-columns) values keyed by header; empty cells omitted (absent when none configured) | - -### Commit object +| `branch` | string/null | Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in `remote` | +| `remote` | string | Remote name, present only on remote-only branch rows | +| `head` | object/null | HEAD commit (see [head object](#head-object)); null for unborn branches | +| `worktree` | object | Worktree facts (see [worktree object](#worktree-object)); absent on branch-only rows | +| `default_branch` | object | Relation to the default branch (see [default_branch object](#default-branch-object)); absent on the default branch itself | +| `upstream` | object | Tracking branch (see [upstream object](#upstream-object)); absent when none is configured | +| `pr` | object | Open PR/MR (see [pr object](#pr-object)); collected with `--full` | +| `checks` | object | CI pipeline (see [checks object](#checks-object)); collected with `--full` | +| `dev_server` | object | `{url, listening}` from the project's `list.url` template; absent when not configured | +| `summary` | string | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | +| `vars` | object | Per-branch variables from [`wt config state vars`](/config/#wt-config-state-vars) | +| `display` | object | Rendered strings (see [display object](#display-object)) | + +### head object | Field | Type | Description | |-------|------|-------------| | `sha` | string | Full commit SHA (40 chars) | | `short_sha` | string | Short commit SHA, abbreviated per `core.abbrev` (auto-extends for ambiguous prefixes) | -| `message` | string | Commit message (first line) | -| `timestamp` | number | Unix timestamp | +| `subject` | string/null | Commit subject (first line); null when not loaded, as for a prunable worktree | +| `committed_at` | string/null | Committer time, RFC 3339 UTC; null when not loaded | + +### worktree object -### working_tree object +Present only on worktree rows. The location attributes are independent, so they co-occur — see [Worktree](#worktree) for the symbols, which pick one: + +| Field | Type | Description | +|-------|------|-------------| +| `path` | string | Worktree path | +| `main` | boolean | Is the main worktree | +| `current` | boolean | Is the worktree the command ran from | +| `previous` | boolean | Is the previous worktree (`wt switch -`) | +| `detached` | boolean | HEAD is detached | +| `locked` | object | `{reason}`; absent when not locked | +| `prunable` | object | `{reason}`; absent when git doesn't report the worktree prunable | +| `branch_mismatch` | boolean | Worktree isn't at the path its branch implies | +| `duplicate_branch` | boolean | Another worktree has the same branch checked out | +| `operation` | string/null | In-progress operation: `"merge"`, `"rebase"`, `"cherry_pick"`, `"revert"`, `"bisect"`; absent when none | +| `changes` | object/null | Working-tree state (see [changes object](#changes-object)) | + +### changes object The five change flags map to the [Working tree](#working-tree) symbols (`renamed` and `deleted` have none of their own): @@ -1158,53 +1101,74 @@ The five change flags map to the [Working tree](#working-tree) symbols (`renamed | `untracked` | boolean | Has untracked files | | `renamed` | boolean | Has renamed files | | `deleted` | boolean | Has deleted files | -| `diff` | object | Lines changed vs HEAD: `{added, deleted}` | +| `conflicted` | boolean/null | Tracked files carry merge conflicts | +| `diff` | object/null | Lines changed vs HEAD: `{added, deleted}` | -### main object +### default_branch object + +Independent facts; the table's priority-collapsed symbol is `display.state`. | Field | Type | Description | |-------|------|-------------| -| `ahead` | number | Commits ahead of the default branch | -| `behind` | number | Commits behind the default branch | -| `diff` | object | Lines changed vs the default branch: `{added, deleted}` | +| `ahead` | number/null | Commits ahead of the default branch (null for orphans) | +| `behind` | number/null | Commits behind the default branch (null for orphans) | +| `diff` | object/null | Lines changed vs the default branch: `{added, deleted}` | +| `orphan` | boolean/null | No common ancestor with the default branch | +| `integration` | object/null | `{reason}` — which check found the content [integrated](/remove/#branch-cleanup) (see [integration reasons](#integration-reasons)); absent when determined not-integrated, null when a dirty tree skipped the checks | +| `merge_conflicts` | boolean/null | Merging into the default branch would conflict, simulated locally with `git merge-tree` | -### remote object +### upstream object `ahead` / `behind` drive the [Remote](#remote) divergence symbol: | Field | Type | Description | |-------|------|-------------| -| `name` | string | Remote name (e.g., `"origin"`) | -| `branch` | string | Remote branch name | +| `remote` | string | Remote name (e.g., `"origin"`) | +| `branch` | string/null | Branch name on the remote | | `ahead` | number | Commits ahead of remote | | `behind` | number | Commits behind remote | -### worktree object - -Present only for worktree-kind items. `state` is the worktree-location attribute — see [Worktree](#worktree) for its symbols: +### pr object | Field | Type | Description | |-------|------|-------------| -| `state` | string | `"branch_worktree_mismatch"`, `"duplicate_branch"`, `"prunable"`, or `"locked"` (absent when normal) | -| `reason` | string | Reason for locked/prunable state | -| `detached` | boolean | HEAD is detached | +| `number` | integer/null | PR/MR number | +| `url` | string/null | URL to the PR/MR page | +| `review` | string | Review state (see [review states](#review-states)); absent when the forge reports no review signal | +| `mergeable` | boolean/null | False when the forge reports conflicts, null otherwise — the fetch records only the conflicted case | +| `repo` | object | Structured metadata for the repository the PR/MR targets, the upstream for fork PRs (see [repo object](#repo-object)); absent when the URL doesn't parse | -### ci object +### checks object | Field | Type | Description | |-------|------|-------------| -| `status` | string | CI status (see below) | +| `status` | string/null | `"passed"`, `"running"`, or `"failed"`; null when a conflicts report masks it | | `source` | string | `"pr"` (PR/MR) or `"branch"` (branch workflow) | -| `number` | integer | PR/MR number; absent for branch workflows | | `stale` | boolean | Local HEAD differs from remote (unpushed changes) | -| `url` | string | URL to the PR/MR page | -| `repo_url` | string | Web URL of the repo the PR/MR targets (the upstream for fork PRs); absent when `url` is absent or unrecognized | -| `repo` | object | Structured metadata for the repository the PR/MR targets; never includes `remote` | -| `review_state` | string | Review state (see below); absent when the forge reports no review signal | + +Three [CI status](#ci-status) values have no `checks.status` of their own, because the shape of `pr` and `checks` reports them: a fetch error (`⚠`) makes both null, no CI leaves `checks` absent, and merge conflicts leave `checks.status` null with `pr.mergeable` false. + +### dev_server object + +| Field | Type | Description | +|-------|------|-------------| +| `url` | string | Dev server URL from project config | +| `listening` | boolean/null | Whether the URL's port is listening | + +### display object + +Presentation only — every value here renders facts that appear elsewhere in the item: + +| Field | Type | Description | +|-------|------|-------------| +| `state` | string | The table's collapsed default-branch state (see [state values](#state-values)); absent when none applies | +| `symbols` | string | Raw status symbols without colors (e.g., `"!?↓"`) | +| `statusline` | string | Pre-formatted status with colors and links | +| `columns` | object | Rendered [custom column](#custom-columns) values keyed by header; empty cells omitted | ### repo object -Top-level `repo` describes the local checkout's repository as derived from the primary remote. `ci.repo` describes the repository targeted by the PR/MR URL in `ci.url` (for fork PRs, this is the upstream target). Existing `repo_url` and `ci.repo_url` fields remain available and carry the same URL as `repo.url` / `ci.repo.url`. +`repo.forge` describes the local checkout's repository as derived from the primary remote. `pr.repo` describes the repository targeted by the PR/MR (for fork PRs, the upstream target). | Field | Type | Description | |-------|------|-------------| @@ -1214,27 +1178,95 @@ Top-level `repo` describes the local checkout's repository as derived from the p | `owner` | string | Owner, organization, or namespace path | | `name` | string | Repository name | | `project` | string | Azure DevOps project name; absent for other providers | -| `remote` | string | Local remote name used for top-level repo metadata; absent from `ci.repo` | +| `remote` | string | Local remote name; present on `repo.forge`, absent from `pr.repo` | -### main_state values +### state values The single highest-priority state describing the branch's relation to the default branch; absent when none applies (a normal up-to-date branch). Each value is one Default-branch symbol — see [Default branch](#default-branch) for the symbol and the full meaning of each value (`"is_main"`, `"orphan"`, `"empty"`, `"integrated"`, `"would_conflict"`, `"same_commit"`, `"diverged"`, `"ahead"`, `"behind"`). -### integration_reason values +### integration reasons -Set only when `main_state == "integrated"` (the `⊂` symbol), recording which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same `⊂`: +`default_branch.integration.reason` records which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same `⊂`: | Value | Meaning | |-------|---------| +| `"same_commit"` | Branch HEAD is the default branch's commit | | `"ancestor"` | Branch HEAD is an ancestor of the default branch, which has moved past it | -| `"no-added-changes"` | The three-dot diff (`main...branch`) is empty — no file changes beyond the merge-base | -| `"trees-match"` | Different history, but the branch's tree is identical to the default branch's | -| `"merge-adds-nothing"` | The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) | -| `"patch-id-match"` | The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) | +| `"no_added_changes"` | The three-dot diff (`main...branch`) is empty — no file changes beyond the merge-base | +| `"trees_match"` | Different history, but the branch's tree is identical to the default branch's | +| `"merge_adds_nothing"` | The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) | +| `"patch_id_match"` | The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) | + +### review states + +`pr.review` is one of `"changes_requested"`, `"pending"`, `"draft"`, `"approved"`, absent when the forge reports no review signal. The [CI status](#ci-status) section is the single source: its table maps the colored values, and the notes below it cover `"draft"` and `"approved"`. The vocabulary matches Claude Code's statusline `pr.review_state` field. + +```console +# Current worktree path (for scripts) +$ wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' + +# Branches with uncommitted changes +$ wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' + +# Worktrees with merge conflicts +$ wt list --format=json | jq '.items[] | select(.worktree.changes.conflicted)' + +# Branches ahead of main (needs merging) +$ wt list --format=json | jq '.items[] | select(.default_branch.ahead > 0) | .branch' + +# Integrated branches (safe to remove) +$ wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' + +# Branches without worktrees +$ wt list --format=json --branches | jq '.items[] | select(.worktree == null) | .branch' + +# Worktrees ahead of upstream (needs pushing) +$ wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | {branch, ahead: .upstream.ahead}' + +# Stale CI (local changes not reflected in CI) +$ wt list --format=json --full | jq '.items[] | select(.checks.stale) | .branch' +``` -### ci.status and ci.review_state values +A JSON Schema for the envelope is published at +[worktrunk.dev/schema/list-v2.json](https://worktrunk.dev/schema/list-v2.json). +It describes what `wt` writes, so a field the absence rule can omit is +optional there rather than required-and-null. + +### Schema 1 -The [CI status](#ci-status) section above is the single source for both fields: the table maps each colored value, and the notes below it cover `"draft"` and `"approved"`. `ci.status` is one of `"passed"`, `"running"`, `"failed"`, `"conflicts"`, `"no-ci"`, `"error"`; `ci.review_state` is one of `"changes_requested"`, `"pending"`, `"draft"`, `"approved"`, absent when the forge reports no review signal. The vocabulary matches Claude Code's statusline `pr.review_state` field. +The original bare-array format — one object per row, no envelope — and the +default while `[list] json-schema` is unset. It is on its way out: `wt config +update` adopts `= 2`, a future release flips the default, and a later one +removes schema 1. Its fields all have a schema-2 home: + +| Schema 1 | Schema 2 | +|----------|----------| +| `branch` | `branch` | +| `path` | `worktree.path` | +| `kind` | the row's shape — a `worktree` object means `"worktree"`, no `worktree` object means `"branch"` | +| `commit.sha`, `.short_sha`, `.message` | `head.sha`, `.short_sha`, `.subject` | +| `commit.timestamp` (Unix) | `head.committed_at` (RFC 3339 UTC) | +| `working_tree.staged`, `.modified`, `.untracked`, `.renamed`, `.deleted`, `.diff` | `worktree.changes.*` | +| `operation_state` `"conflicts"` | `worktree.changes.conflicted` | +| `operation_state` `"rebase"`, `"merge"`, … | `worktree.operation` | +| `main_state` | `display.state` | +| `integration_reason` | `default_branch.integration.reason` (snake_case, and it reports `"same_commit"` rather than folding it into `main_state`) | +| `main.ahead`, `.behind`, `.diff` | `default_branch.ahead`, `.behind`, `.diff` | +| `remote.name`, `.branch`, `.ahead`, `.behind` | `upstream.remote`, `.branch`, `.ahead`, `.behind` | +| `worktree.state` | `worktree.locked`, `.prunable`, `.duplicate_branch`, `.branch_mismatch` — independent, so they co-occur | +| `worktree.reason` | `worktree.locked.reason`, `worktree.prunable.reason` | +| `worktree.detached` | `worktree.detached` | +| `is_main`, `is_current`, `is_previous` | `worktree.main`, `.current`, `.previous` | +| `ci.status` | `checks.status`, plus the shapes described under [checks object](#checks-object) | +| `ci.source`, `ci.stale` | `checks.source`, `checks.stale` | +| `ci.number`, `ci.url`, `ci.review_state` | `pr.number`, `pr.url`, `pr.review` | +| `ci.repo`, `ci.repo_url` | `pr.repo`, `pr.repo.url` | +| `repo`, `repo_url` | the envelope's `repo.forge`, `repo.forge.url` | +| `url`, `url_active` | `dev_server.url`, `dev_server.listening` | +| `summary`, `vars` | `summary`, `vars` | +| `statusline`, `symbols`, `columns` | `display.statusline`, `display.symbols`, `display.columns` | + +The envelope's `repo.default_branch` and `collected` have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see [How "no value" reads](#schema-2). Missing a field that would be generally useful? Open an issue at https://github.com/max-sixty/worktrunk. diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index a9eead51dc..27ec01811c 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -178,26 +178,27 @@ The leftmost column marks each row by physical presence, from most present to le CI status -The CI column shows the branch's open PR/MR — #3035 on GitHub, Gitea, and Azure DevOps, !3035 on GitLab — colored by pipeline status, or a bare # when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are ci.status; magenta/cyan are ci.review_state. The Value column is the matching JSON string from --format=json: +The CI column shows the branch's open PR/MR — #3035 on GitHub, Gitea, and Azure DevOps, !3035 on GitLab — colored by pipeline status, or a bare # when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON fields: green/blue/red/yellow/gray are the pipeline state, magenta/cyan the review state. The Value column names each state; checks object and review states give the JSON spelling — schema 2 reports three of the pipeline values by the shape of pr and checks +rather than as a checks.status string: - Indicator Value Meaning - ───────── ─────────────────── ─────────────────────────────────────────────────────────────── - # green "passed" All checks passed - # blue "running" Checks in progress - # red "failed" One or more checks failed - # yellow "conflicts" Merge conflicts with the target branch - # gray "no-ci" No PR/MR, or no checks configured - ⚠ yellow "error" CI status could not be fetched (rate limit, network, etc.) - # magenta "changes_requested" A reviewer requested changes - # cyan "pending" A review is required (e.g. branch protection) but not yet given - (blank) ci absent No upstream, or no PR/MR and no branch workflow + Indicator Value Meaning + ───────── ──────────────────── ─────────────────────────────────────────────────────────────── + # green "passed" All checks passed + # blue "running" Checks in progress + # red "failed" One or more checks failed + # yellow "conflicts" Merge conflicts with the target branch + # gray "no-ci" No PR/MR, or no checks configured + ⚠ yellow "error" CI status could not be fetched (rate limit, network, etc.) + # magenta "changes_requested" A reviewer requested changes + # cyan "pending" A review is required (e.g. branch protection) but not yet given + (blank) pr and checks absent No upstream, or no PR/MR and no branch workflow -The two remaining ci.review_state values have no indicator of their own: "draft" only dims the cell and "approved" leaves the color unchanged. +The two remaining review states have no indicator of their own: "draft" only dims the cell and "approved" leaves the color unchanged. -Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain ci.status color — ci.review_state is then "approved" or absent, respectively. GitLab MR data carries only "pending" and "draft" — no -approved or changes-requested signal. +Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — pr.review is then "approved" or absent, respectively. GitLab MR data carries only "pending" and "draft" — no approved +or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR ("draft") or when unpushed local changes make the status stale (ci.stale). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with --remotes — get CI status detection. Results are cached for 30-60 seconds; use wt config state to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR ("draft") or when unpushed local changes make the status stale (checks.stale). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with --remotes — get CI status detection. Results are cached for 30-60 seconds; use wt config state to view or clear. LLM summaries @@ -214,44 +215,44 @@ A column that renders empty for every row is dropped from the table. Templates, Status symbols -The Status column packs several subcolumns, left to right, each mapping to a field in --format=json. Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. +The Status column packs several subcolumns, left to right, each mapping to a schema-2 field in --format=json (schema 1 spells several of them differently — see Schema 1). Working-tree flags are independent and co-occur — any combination shows at once. The other subcolumns are mutually exclusive: each shows a single symbol, the highest-priority state in top-to-bottom table order, and is blank when nothing applies. Working tree -Independent flags from git status; several can show at once (e.g. +!?). Each maps to a boolean in the working_tree object: +Independent flags from git status; several can show at once (e.g. +!?). Each maps to a boolean in the changes object: - Symbol working_tree Meaning - ────── ──────────── ───────────────────────── - + staged Staged files - ! modified Modified files (unstaged) - ? untracked Untracked files + Symbol worktree.changes Meaning + ────── ──────────────── ───────────────────────── + + staged Staged files + ! modified Modified files (unstaged) + ? untracked Untracked files -working_tree also reports renamed and deleted, which have no dedicated symbol in the column. +worktree.changes also reports renamed and deleted, which have no dedicated symbol in the column. Worktree An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (✘ > ↻ > ⊟ > ⊞ > ⚑ > /): - Symbol JSON Meaning - ────── ──────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - ✘ operation_state "conflicts" Merge conflicts - ↻ operation_state "rebase", "merge", "cherry_pick", "revert", "bisect" A git operation is in progress; git status names it - ⊟ worktree.state "prunable" Prunable (worktree directory missing) - ⊞ worktree.state "locked" Locked worktree - ⚑ worktree.state "duplicate_branch" Branch checked out in more than one worktree, so wt resolves it to whichever git lists first; every worktree on the branch is flagged - ⚑ worktree.state "branch_worktree_mismatch" Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home - / kind "branch" Branch without a worktree (no worktree object) + Symbol JSON Meaning + ────── ─────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + ✘ worktree.changes.conflicted Merge conflicts + ↻ worktree.operation "rebase", "merge", "cherry_pick", "revert", "bisect" A git operation is in progress; git status names it + ⊟ worktree.prunable Prunable (worktree directory missing) + ⊞ worktree.locked Locked worktree + ⚑ worktree.duplicate_branch Branch checked out in more than one worktree, so wt resolves it to whichever git lists first; every worktree on the branch is flagged + ⚑ worktree.branch_mismatch Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home + / no worktree object Branch without a worktree Default branch -The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one main_state value: +The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each symbol is one display.state value: - Symbol main_state Meaning + Symbol display.state Meaning ────── ──────────────── ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ^ "is_main" The main worktree (the repo's home worktree) ∅ "orphan" No common ancestor with the default branch _ "empty" Same commit as the default branch, working tree clean — safe to remove; row dimmed - ⊂ "integrated" Content integrated into the default branch or merge target via different history; the matching check is in integration_reason; row dimmed + ⊂ "integrated" Content integrated into the default branch or merge target via different history; the matching check is in default_branch.integration.reason; row dimmed ✗ "would_conflict" Merging into the default branch would conflict (simulated with git merge-tree) and the branch isn't already integrated; with --full, the check includes tracked uncommitted changes – "same_commit" Same commit as the default branch, but with uncommitted changes ↕ "diverged" Both ahead of and behind the default branch @@ -262,9 +263,9 @@ Rows are dimmed when safe to delete — _ ("empty") or ⊂[ Remote -Relation to the tracking branch, derived from the remote.ahead / remote.behind counts; blank when there is no upstream: +Relation to the tracking branch, derived from the upstream.ahead / upstream.behind counts; blank when there is no upstream: - Symbol remote Meaning + Symbol upstream Meaning ────── ───────────────────── ──────────────────── | ahead 0, behind 0 In sync with remote ⇡ ahead > 0 Ahead of remote @@ -333,200 +334,234 @@ How "no value" reads: jq treats absent and null identically in path expressions, so filters need no null checks; has() distinguishes the two when it matters. -Item fields: +Envelope fields: - Field Description - ────────────── ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── - branch Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in remote - remote Remote name, present only on remote-only branch rows - head {sha, short_sha, subject, committed_at}; null for unborn branches. committed_at is RFC 3339 UTC - worktree {path, main, current, previous, detached, locked, prunable, branch_mismatch, duplicate_branch, operation, changes}; absent on branch-only rows. locked/prunable are {reason} objects and can co-occur; operation is "rebase" or "merge"; changes holds the five working-tree flags plus conflicted and diff {added, deleted} - default_branch Relation to the default branch: {ahead, behind, diff, orphan, integration, merge_conflicts}; absent on the default branch itself. integration.reason is one of same_commit, ancestor, no_added_changes, trees_match, merge_adds_nothing, patch_id_match; a dirty tree skips the checks, leaving integration null - upstream Tracking branch: {remote, branch, ahead, behind}; absent when none is configured - pr Open PR/MR: {number, url, review, mergeable, repo}; collected with --full. review uses the schema 1 ci.review_state vocabulary; mergeable is false when the forge reports conflicts, null otherwise - checks CI pipeline: {status, source, stale}; collected with --full. status is passed, running, or failed — null when a conflicts report masks it - dev_server {url, listening} from the project's list.url template - summary LLM branch summary; needs --full, [list] summary = true, and a [commit.generation] command - vars Per-branch variables from wt config state vars - display Rendered strings: state (schema 1's main_state vocabulary), symbols, statusline (with ANSI colors and OSC 8 hyperlinks), columns (custom-column cells keyed by header) - -Schema 1 names map directly: commit → head, working_tree → -worktree.changes, main + main_state → default_branch + -display.state, remote → upstream, ci → pr + checks, url + -url_active → dev_server, statusline/symbols/columns → display.*, -and the per-item repo moves to the envelope's repo.forge. + Field Type Description + ───────── ────── ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + schema number Format version; always 2 + repo object {default_branch, forge} — the branch every default_branch object measures against (absent when detection failed), and forge metadata derived from the primary remote (absent when no remote URL parses; see repo object) + collected object {ci, summary} — which gated fact families this run requested, so an absent pr/checks/summary reads as "not requested" rather than "none" + items array One object per row: a worktree, a local branch, or a remote-only branch -  # Current worktree path (for scripts) -  wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' -   -  # Branches with uncommitted changes -  wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' -   -  # Integrated branches (safe to remove) -  wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' -   -  # Worktrees ahead of upstream (needs pushing) -  wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | .branch' +Item fields: -A JSON Schema for the envelope is published at -worktrunk.dev/schema/list-v2.json. -It describes what wt writes, so a field the absence rule can omit is -optional there rather than required-and-null. + Field Type Description + ────────────── ─────────── ───────────────────────────────────────────────────────────────────────────────────────────────────────── + branch string/null Branch name; null for a detached-HEAD worktree. Remote rows carry the bare name with the remote in remote + remote string Remote name, present only on remote-only branch rows + head object/null HEAD commit (see head object); null for unborn branches + worktree object Worktree facts (see worktree object); absent on branch-only rows + default_branch object Relation to the default branch (see default_branch object); absent on the default branch itself + upstream object Tracking branch (see upstream object); absent when none is configured + pr object Open PR/MR (see pr object); collected with --full + checks object CI pipeline (see checks object); collected with --full + dev_server object {url, listening} from the project's list.url template; absent when not configured + summary string LLM branch summary; needs --full, [list] summary = true, and a [commit.generation] command + vars object Per-branch variables from wt config state vars + display object Rendered strings (see display object) + +head object + + Field Type Description + ──────────── ─────────── ─────────────────────────────────────────────────────────────────────────────────── + sha string Full commit SHA (40 chars) + short_sha string Short commit SHA, abbreviated per core.abbrev (auto-extends for ambiguous prefixes) + subject string/null Commit subject (first line); null when not loaded, as for a prunable worktree + committed_at string/null Committer time, RFC 3339 UTC; null when not loaded -Schema 1 +worktree object -The original bare-array format, and the default while unset: +Present only on worktree rows. The location attributes are independent, so they co-occur — see Worktree for the symbols, which pick one: -  # Current worktree path (for scripts) -  wt list --format=json | jq -r '.[] | select(.is_current) | .path' -   -  # Branches with uncommitted changes -  wt list --format=json | jq '.[] | select(.working_tree.modified)' -   -  # Worktrees with merge conflicts -  wt list --format=json | jq '.[] | select(.operation_state == "conflicts")' -   -  # Branches ahead of main (needs merging) -  wt list --format=json | jq '.[] | select(.main.ahead > 0) | .branch' -   -  # Integrated branches (safe to remove) -  wt list --format=json | jq '.[] | select(.main_state == "integrated" or .main_state == "empty") | .branch' -   -  # Branches without worktrees -  wt list --format=json --branches | jq '.[] | select(.kind == "branch") | .branch' -   -  # Worktrees ahead of remote (needs pushing) -  wt list --format=json | jq '.[] | select(.remote.ahead > 0) | {branch, ahead: .remote.ahead}' -   -  # Stale CI (local changes not reflected in CI) -  wt list --format=json --full | jq '.[] | select(.ci.stale) | .branch' - -Fields: - - Field Type Description - ────────────────── ─────────── ──────────────────────────────────────────────────────────────────────────────────────────────── - branch string/null Branch name (null for detached HEAD) - path string Worktree path (absent for branches without worktrees) - kind string "worktree" or "branch" - commit object Commit info (see below) - working_tree object Working tree state (see below) - main_state string Relation to the default branch (see below) - integration_reason string Why branch is integrated (see below) - operation_state string "conflicts", "rebase", or "merge" (see Worktree); absent when clean - main object Relationship to the default branch (see below); absent when is_main - remote object Tracking branch info (see below); absent when no tracking - worktree object Worktree metadata (see below) - is_main boolean Is the main worktree - is_current boolean Is the current worktree - is_previous boolean Previous worktree from wt switch - ci object CI status (see below); --full only, then absent when no PR/MR or branch workflow - repo_url string Repository web URL derived from the primary remote; absent when the remote URL cannot be parsed - repo object Structured repository metadata (see below); includes remote - url string Dev server URL from project config; absent when not configured - url_active boolean Whether the URL's port is listening; absent when not configured - summary string LLM-generated branch summary; --full only, then absent when not configured or no summary - statusline string Pre-formatted status with colors and links - symbols string Raw status symbols without colors (e.g., "!?↓") - vars object Per-branch variables from wt config state vars (absent when empty) - columns object Rendered custom column values keyed by header; empty cells omitted (absent when none configured) - -Commit object - - Field Type Description - ───────── ────── ─────────────────────────────────────────────────────────────────────────────────── - sha string Full commit SHA (40 chars) - short_sha string Short commit SHA, abbreviated per core.abbrev (auto-extends for ambiguous prefixes) - message string Commit message (first line) - timestamp number Unix timestamp - -working_tree object + Field Type Description + ──────────────── ─────────── ───────────────────────────────────────────────────────────────────────────────────────────── + path string Worktree path + main boolean Is the main worktree + current boolean Is the worktree the command ran from + previous boolean Is the previous worktree (wt switch -) + detached boolean HEAD is detached + locked object {reason}; absent when not locked + prunable object {reason}; absent when git doesn't report the worktree prunable + branch_mismatch boolean Worktree isn't at the path its branch implies + duplicate_branch boolean Another worktree has the same branch checked out + operation string/null In-progress operation: "merge", "rebase", "cherry_pick", "revert", "bisect"; absent when none + changes object/null Working-tree state (see changes object) + +changes object The five change flags map to the Working tree symbols (renamed and deleted have none of their own): - Field Type Description - ───────── ─────── ─────────────────────────────────────── - staged boolean Has staged files - modified boolean Has modified files (unstaged) - untracked boolean Has untracked files - renamed boolean Has renamed files - deleted boolean Has deleted files - diff object Lines changed vs HEAD: {added, deleted} + Field Type Description + ────────── ──────────── ─────────────────────────────────────── + staged boolean Has staged files + modified boolean Has modified files (unstaged) + untracked boolean Has untracked files + renamed boolean Has renamed files + deleted boolean Has deleted files + conflicted boolean/null Tracked files carry merge conflicts + diff object/null Lines changed vs HEAD: {added, deleted} + +default_branch object -main object +Independent facts; the table's priority-collapsed symbol is display.state. - Field Type Description - ────── ────── ───────────────────────────────────────────────────── - ahead number Commits ahead of the default branch - behind number Commits behind the default branch - diff object Lines changed vs the default branch: {added, deleted} + Field Type Description + ─────────────── ──────────── ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + ahead number/null Commits ahead of the default branch (null for orphans) + behind number/null Commits behind the default branch (null for orphans) + diff object/null Lines changed vs the default branch: {added, deleted} + orphan boolean/null No common ancestor with the default branch + integration object/null {reason} — which check found the content integrated (see integration reasons); absent when determined not-integrated, null when a dirty tree skipped the checks + merge_conflicts boolean/null Merging into the default branch would conflict, simulated locally with git merge-tree -remote object +upstream object ahead / behind drive the Remote divergence symbol: - Field Type Description - ────── ────── ──────────────────────────── - name string Remote name (e.g., "origin") - branch string Remote branch name - ahead number Commits ahead of remote - behind number Commits behind remote + Field Type Description + ────── ─────────── ──────────────────────────── + remote string Remote name (e.g., "origin") + branch string/null Branch name on the remote + ahead number Commits ahead of remote + behind number Commits behind remote -worktree object +pr object + + Field Type Description + ───────── ──────────── ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + number integer/null PR/MR number + url string/null URL to the PR/MR page + review string Review state (see review states); absent when the forge reports no review signal + mergeable boolean/null False when the forge reports conflicts, null otherwise — the fetch records only the conflicted case + repo object Structured metadata for the repository the PR/MR targets, the upstream for fork PRs (see repo object); absent when the URL doesn't parse + +checks object + + Field Type Description + ────── ─────────── ─────────────────────────────────────────────────────────────────────── + status string/null "passed", "running", or "failed"; null when a conflicts report masks it + source string "pr" (PR/MR) or "branch" (branch workflow) + stale boolean Local HEAD differs from remote (unpushed changes) + +Three CI status values have no checks.status of their own, because the shape of pr and checks reports them: a fetch error (⚠) makes both null, no CI leaves checks absent, and merge conflicts leave checks.status null with pr.mergeable false. -Present only for worktree-kind items. state is the worktree-location attribute — see Worktree for its symbols: +dev_server object - Field Type Description - ──────── ─────── ──────────────────────────────────────────────────────────────────────────────────────────── - state string "branch_worktree_mismatch", "duplicate_branch", "prunable", or "locked" (absent when normal) - reason string Reason for locked/prunable state - detached boolean HEAD is detached + Field Type Description + ───────── ──────────── ─────────────────────────────────── + url string Dev server URL from project config + listening boolean/null Whether the URL's port is listening -ci object +display object - Field Type Description - ──────────── ─────── ──────────────────────────────────────────────────────────────────────────────────────────────────────────── - status string CI status (see below) - source string "pr" (PR/MR) or "branch" (branch workflow) - number integer PR/MR number; absent for branch workflows - stale boolean Local HEAD differs from remote (unpushed changes) - url string URL to the PR/MR page - repo_url string Web URL of the repo the PR/MR targets (the upstream for fork PRs); absent when url is absent or unrecognized - repo object Structured metadata for the repository the PR/MR targets; never includes remote - review_state string Review state (see below); absent when the forge reports no review signal +Presentation only — every value here renders facts that appear elsewhere in the item: + + Field Type Description + ────────── ────── ─────────────────────────────────────────────────────────────────────────────────────── + state string The table's collapsed default-branch state (see state values); absent when none applies + symbols string Raw status symbols without colors (e.g., "!?↓") + statusline string Pre-formatted status with colors and links + columns object Rendered custom column values keyed by header; empty cells omitted repo object -Top-level repo describes the local checkout's repository as derived from the primary remote. ci.repo describes the repository targeted by the PR/MR URL in ci.url (for fork PRs, this is the upstream target). Existing repo_url and ci.repo_url fields remain available and carry the same URL as repo.url / ci.repo.url. +repo.forge describes the local checkout's repository as derived from the primary remote. pr.repo describes the repository targeted by the PR/MR (for fork PRs, the upstream target). - Field Type Description - ──────── ────── ─────────────────────────────────────────────────────────────────────── - url string Repository web URL - provider string "github", "gitlab", "gitea", "azure-devops", or "unknown" - host string Repository web host - owner string Owner, organization, or namespace path - name string Repository name - project string Azure DevOps project name; absent for other providers - remote string Local remote name used for top-level repo metadata; absent from ci.repo + Field Type Description + ──────── ────── ───────────────────────────────────────────────────────────── + url string Repository web URL + provider string "github", "gitlab", "gitea", "azure-devops", or "unknown" + host string Repository web host + owner string Owner, organization, or namespace path + name string Repository name + project string Azure DevOps project name; absent for other providers + remote string Local remote name; present on repo.forge, absent from pr.repo -main_state values +state values The single highest-priority state describing the branch's relation to the default branch; absent when none applies (a normal up-to-date branch). Each value is one Default-branch symbol — see Default branch for the symbol and the full meaning of each value ("is_main", "orphan", "empty", "integrated", "would_conflict", "same_commit", "diverged", "ahead", "behind"). -integration_reason values +integration reasons -Set only when main_state == "integrated" (the ⊂ symbol), recording which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same ⊂: +default_branch.integration.reason records which check matched. Checks run cheapest-first and the first match wins. JSON-only — every reason renders as the same ⊂: Value Meaning ──────────────────── ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + "same_commit" Branch HEAD is the default branch's commit "ancestor" Branch HEAD is an ancestor of the default branch, which has moved past it - "no-added-changes" The three-dot diff (main...branch) is empty — no file changes beyond the merge-base - "trees-match" Different history, but the branch's tree is identical to the default branch's - "merge-adds-nothing" The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) - "patch-id-match" The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) + "no_added_changes" The three-dot diff (main...branch) is empty — no file changes beyond the merge-base + "trees_match" Different history, but the branch's tree is identical to the default branch's + "merge_adds_nothing" The branch has changes, but merging them leaves the default branch's tree unchanged (e.g. a squash merge where the target advanced on other files) + "patch_id_match" The branch's squashed diff matches a single commit on the default branch (e.g. a GitHub/GitLab squash merge) + +review states -ci.status and ci.review_state values +pr.review is one of "changes_requested", "pending", "draft", "approved", absent when the forge reports no review signal. The CI status section is the single source: its table maps the colored values, and the notes below it cover "draft" and "approved". The vocabulary matches Claude Code's statusline pr.review_state field. + +  # Current worktree path (for scripts) +  wt list --format=json | jq -r '.items[] | select(.worktree.current) | .worktree.path' +   +  # Branches with uncommitted changes +  wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' +   +  # Worktrees with merge conflicts +  wt list --format=json | jq '.items[] | select(.worktree.changes.conflicted)' +   +  # Branches ahead of main (needs merging) +  wt list --format=json | jq '.items[] | select(.default_branch.ahead > 0) | .branch' +   +  # Integrated branches (safe to remove) +  wt list --format=json | jq '.items[] | select(.display.state == "integrated" or .display.state == "empty") | .branch' +   +  # Branches without worktrees +  wt list --format=json --branches | jq '.items[] | select(.worktree == null) | .branch' +   +  # Worktrees ahead of upstream (needs pushing) +  wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | {branch, ahead: .upstream.ahead}' +   +  # Stale CI (local changes not reflected in CI) +  wt list --format=json --full | jq '.items[] | select(.checks.stale) | .branch' + +A JSON Schema for the envelope is published at +worktrunk.dev/schema/list-v2.json. +It describes what wt writes, so a field the absence rule can omit is +optional there rather than required-and-null. + +Schema 1 -The CI status section above is the single source for both fields: the table maps each colored value, and the notes below it cover "draft" and "approved". ci.status is one of "passed", "running", "failed", "conflicts", "no-ci", "error"; ci.review_state is one of "changes_requested", "pending", "draft", "approved", absent when the forge reports no review signal. The vocabulary matches Claude Code's statusline pr.review_state field. +The original bare-array format — one object per row, no envelope — and the +default while [list] json-schema is unset. It is on its way out: wt config +update adopts = 2, a future release flips the default, and a later one +removes schema 1. Its fields all have a schema-2 home: + + Schema 1 Schema 2 + ───────────────────────────────────────────────────────────────────── ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + branch branch + path worktree.path + kind the row's shape — a worktree object means "worktree", no worktree object means "branch" + commit.sha, .short_sha, .message head.sha, .short_sha, .subject + commit.timestamp (Unix) head.committed_at (RFC 3339 UTC) + working_tree.staged, .modified, .untracked, .renamed, .deleted, .diff worktree.changes.* + operation_state "conflicts" worktree.changes.conflicted + operation_state "rebase", "merge", … worktree.operation + main_state display.state + integration_reason default_branch.integration.reason (snake_case, and it reports "same_commit" rather than folding it into main_state) + main.ahead, .behind, .diff default_branch.ahead, .behind, .diff + remote.name, .branch, .ahead, .behind upstream.remote, .branch, .ahead, .behind + worktree.state worktree.locked, .prunable, .duplicate_branch, .branch_mismatch — independent, so they co-occur + worktree.reason worktree.locked.reason, worktree.prunable.reason + worktree.detached worktree.detached + is_main, is_current, is_previous worktree.main, .current, .previous + ci.status checks.status, plus the shapes described under checks object + ci.source, ci.stale checks.source, checks.stale + ci.number, ci.url, ci.review_state pr.number, pr.url, pr.review + ci.repo, ci.repo_url pr.repo, pr.repo.url + repo, repo_url the envelope's repo.forge, repo.forge.url + url, url_active dev_server.url, dev_server.listening + summary, vars summary, vars + statusline, symbols, columns display.statusline, display.symbols, display.columns + +The envelope's repo.default_branch and collected have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see How "no value" reads. Missing a field that would be generally useful? Open an issue at https://github.com/max-sixty/worktrunk. diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index e3b17f87f3..3ea5eea3e2 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -204,38 +204,39 @@ least: The CI column shows the branch's open PR/MR — #3035 on GitHub, Gitea, and Azure DevOps, !3035 on GitLab — colored by pipeline status, or a bare # when no number is available (e.g. branch workflows without a PR/MR). One color folds two JSON -fields: green/blue/red/yellow/gray are ci.status; magenta/cyan are -ci.review_state. The Value column is the matching JSON string from ---format=json: - - Indicator Value Meaning - ───────── ─────────────────── ──────────────────────────────────────────────── - # green "passed" All checks passed - # blue "running" Checks in progress - # red "failed" One or more checks failed - # yellow "conflicts" Merge conflicts with the target branch - # gray "no-ci" No PR/MR, or no checks configured - ⚠ yellow "error" CI status could not be fetched (rate limit, - network, etc.) - # magenta "changes_requested" A reviewer requested changes - # cyan "pending" A review is required (e.g. branch protection) - but not yet given - (blank) ci absent No upstream, or no PR/MR and no branch workflow - -The two remaining ci.review_state values have no indicator of their own: "draft" - only dims the cell and "approved" leaves the color unchanged. +fields: green/blue/red/yellow/gray are the pipeline state, magenta/cyan the +review state. The Value column names each state; checks object and review states + give the JSON spelling — schema 2 reports three of the pipeline values by the +shape of pr and checks rather than as a checks.status string: + + Indicator Value Meaning + ───────── ──────────────────── ─────────────────────────────────────────────── + # green "passed" All checks passed + # blue "running" Checks in progress + # red "failed" One or more checks failed + # yellow "conflicts" Merge conflicts with the target branch + # gray "no-ci" No PR/MR, or no checks configured + ⚠ yellow "error" CI status could not be fetched (rate limit, + network, etc.) + # magenta "changes_requested" A reviewer requested changes + # cyan "pending" A review is required (e.g. branch protection) + but not yet given + (blank) pr and checks absent No upstream, or no PR/MR and no branch workflow + +The two remaining review states have no indicator of their own: "draft" only +dims the cell and "approved" leaves the color unchanged. Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no -required reviewers and no reviews), keeps its plain ci.status color — -ci.review_state is then "approved" or absent, respectively. GitLab MR data -carries only "pending" and "draft" — no approved or changes-requested signal. +required reviewers and no reviews), keeps its plain pipeline color — pr.review +is then "approved" or absent, respectively. GitLab MR data carries only +"pending" and "draft" — no approved or changes-requested signal. CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR ("draft") or when unpushed local changes make the status stale -(ci.stale). PRs/MRs are checked first, then branch workflows/pipelines for +(checks.stale). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with --remotes — get CI status detection. Results are cached for 30-60 seconds; use wt config state to view or clear. @@ -264,83 +265,83 @@ widths, and drop priority: custom columns config. Status symbols The Status column packs several subcolumns, left to right, each mapping to a -field in --format=json. Working-tree flags are independent and co-occur — any -combination shows at once. The other subcolumns are mutually exclusive: each -shows a single symbol, the highest-priority state in top-to-bottom table order, -and is blank when nothing applies. +schema-2 field in --format=json (schema 1 spells several of them differently — +see Schema 1). Working-tree flags are independent and co-occur — any combination + shows at once. The other subcolumns are mutually exclusive: each shows a single + symbol, the highest-priority state in top-to-bottom table order, and is blank +when nothing applies. Working tree Independent flags from git status; several can show at once (e.g. +!?). Each -maps to a boolean in the working_tree object: +maps to a boolean in the changes object: - Symbol working_tree Meaning - ────── ──────────── ───────────────────────── - + staged Staged files - ! modified Modified files (unstaged) - ? untracked Untracked files + Symbol worktree.changes Meaning + ────── ──────────────── ───────────────────────── + + staged Staged files + ! modified Modified files (unstaged) + ? untracked Untracked files -working_tree also reports renamed and deleted, which have no dedicated symbol in - the column. +worktree.changes also reports renamed and deleted, which have no dedicated +symbol in the column. Worktree An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (✘ > ↻ > ⊟ > ⊞ > ⚑ > /): - Symb JSON Meaning + Symb JSON Meaning ol - ──── ────────────────────────────── ────────────────────────────────────────── - ✘ operation_state "conflicts" Merge conflicts - ↻ operation_state "rebase", A git operation is in progress; git status - "merge", "cherry_pick", names it - "revert", "bisect" - ⊟ worktree.state "prunable" Prunable (worktree directory missing) - ⊞ worktree.state "locked" Locked worktree - ⚑ worktree.state Branch checked out in more than one - "duplicate_branch" worktree, so wt resolves it to whichever - git lists first; every worktree on the - branch is flagged - ⚑ worktree.state Worktree isn't at the path its branch - "branch_worktree_mismatch" implies — including a detached one, which - has no branch to imply a path and so is - never at home - / kind "branch" Branch without a worktree (no worktree - object) + ──── ────────────────────────── ────────────────────────────────────────────── + ✘ worktree.changes.conflicte Merge conflicts + d + ↻ worktree.operation A git operation is in progress; git status + "rebase", "merge", names it + "cherry_pick", "revert", + "bisect" + ⊟ worktree.prunable Prunable (worktree directory missing) + ⊞ worktree.locked Locked worktree + ⚑ worktree.duplicate_branch Branch checked out in more than one worktree, + so wt resolves it to whichever git lists + first; every worktree on the branch is flagged + ⚑ worktree.branch_mismatch Worktree isn't at the path its branch implies + — including a detached one, which has no + branch to imply a path and so is never at home + / no worktree object Branch without a worktree Default branch The single highest-priority state describing the branch's relation to the default branch; blank when none applies (a normal up-to-date branch). Each -symbol is one main_state value: - - Symb main_state Meaning - ol - ──── ────────── ────────────────────────────────────────────────────────────── - ^ "is_main" The main worktree (the repo's home worktree) - ∅ "orphan" No common ancestor with the default branch - _ "empty" Same commit as the default branch, working tree clean — safe - to remove; row dimmed - ⊂ "integrate Content integrated into the default branch or merge target via - d" different history; the matching check is in integration_reason - ; row dimmed - ✗ "would_con Merging into the default branch would conflict (simulated with - flict" git merge-tree) and the branch isn't already integrated; with - --full, the check includes tracked uncommitted changes - – "same_comm Same commit as the default branch, but with uncommitted - it" changes - ↕ "diverged" Both ahead of and behind the default branch - ↑ "ahead" Has commits the default branch doesn't - ↓ "behind" Missing commits the default branch has +symbol is one display.state value: + + Symb display.sta Meaning + ol te + ──── ─────────── ───────────────────────────────────────────────────────────── + ^ "is_main" The main worktree (the repo's home worktree) + ∅ "orphan" No common ancestor with the default branch + _ "empty" Same commit as the default branch, working tree clean — safe + to remove; row dimmed + ⊂ "integrated Content integrated into the default branch or merge target + " via different history; the matching check is in + default_branch.integration.reason; row dimmed + ✗ "would_conf Merging into the default branch would conflict (simulated + lict" with git merge-tree) and the branch isn't already integrated; + with --full, the check includes tracked uncommitted changes + – "same_commi Same commit as the default branch, but with uncommitted + t" changes + ↕ "diverged" Both ahead of and behind the default branch + ↑ "ahead" Has commits the default branch doesn't + ↓ "behind" Missing commits the default branch has Rows are dimmed when safe to delete — _ ("empty") or ⊂ ("integrated"). Remote -Relation to the tracking branch, derived from the remote.ahead / remote.behind -counts; blank when there is no upstream: +Relation to the tracking branch, derived from the upstream.ahead / +upstream.behind counts; blank when there is no upstream: - Symbol remote Meaning + Symbol upstream Meaning ────── ───────────────────── ──────────────────── | ahead 0, behind 0 In sync with remote ⇡ ahead > 0 Ahead of remote @@ -413,232 +414,189 @@ How "no value" reads: jq treats absent and null identically in path expressions, so filters need no null checks; has() distinguishes the two when it matters. +Envelope fields: + + Field Type Description + ────── ────── ──────────────────────────────────────────────────────────────── + schema number Format version; always 2 + repo object {default_branch, forge} — the branch every default_branch object + measures against (absent when detection failed), and forge + metadata derived from the primary remote (absent when no remote + URL parses; see repo object) + collec object {ci, summary} — which gated fact families this run requested, so + ted an absent pr/checks/summary reads as "not requested" rather than + "none" + items array One object per row: a worktree, a local branch, or a remote-only + branch + Item fields: - Field Description - ─────── ────────────────────────────────────────────────────────────────────── - branch Branch name; null for a detached-HEAD worktree. Remote rows carry the - bare name with the remote in remote - remote Remote name, present only on remote-only branch rows - head {sha, short_sha, subject, committed_at}; null for unborn branches. - committed_at is RFC 3339 UTC - worktre {path, main, current, previous, detached, locked, prunable, branch_mis - e match, duplicate_branch, operation, changes}; absent on branch-only - rows. locked/prunable are {reason} objects and can co-occur; operation - is "rebase" or "merge"; changes holds the five working-tree flags plus - conflicted and diff {added, deleted} - default Relation to the default branch: - _branch {ahead, behind, diff, orphan, integration, merge_conflicts}; absent on - the default branch itself. integration.reason is one of same_commit, - ancestor, no_added_changes, trees_match, merge_adds_nothing, - patch_id_match; a dirty tree skips the checks, leaving integration + Field Type Description + ─────── ─────── ────────────────────────────────────────────────────────────── + branch string/ Branch name; null for a detached-HEAD worktree. Remote rows + null carry the bare name with the remote in remote + remote string Remote name, present only on remote-only branch rows + head object/ HEAD commit (see head object); null for unborn branches null - upstrea Tracking branch: {remote, branch, ahead, behind}; absent when none is - m configured - pr Open PR/MR: {number, url, review, mergeable, repo}; collected with - --full. review uses the schema 1 ci.review_state vocabulary; mergeable - is false when the forge reports conflicts, null otherwise - checks CI pipeline: {status, source, stale}; collected with --full. status is - passed, running, or failed — null when a conflicts report masks it - dev_ser {url, listening} from the project's list.url template - ver - summary LLM branch summary; needs --full, [list] summary = true, and a - [commit.generation] command - vars Per-branch variables from wt config state vars - display Rendered strings: state (schema 1's main_state vocabulary), symbols, - statusline (with ANSI colors and OSC 8 hyperlinks), columns - (custom-column cells keyed by header) - -Schema 1 names map directly: commit → head, working_tree → -worktree.changes, main + main_state → default_branch + -display.state, remote → upstream, ci → pr + checks, url + -url_active → dev_server, statusline/symbols/columns → display.*, -and the per-item repo moves to the envelope's repo.forge. - -  # Current worktree path (for scripts) -  wt list --format=json | jq -r '.items[] | select(.worktree.current) |  -  .worktree.path' -   -  # Branches with uncommitted changes -  wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' -   -  # Integrated branches (safe to remove) -  wt list --format=json | jq '.items[] | select(.display.state == "integrated"  -  or .display.state == "empty") | .branch' -   -  # Worktrees ahead of upstream (needs pushing) -  wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | .branch' + worktre object Worktree facts (see worktree object); absent on branch-only + e rows + default object Relation to the default branch (see default_branch object); + _branch absent on the default branch itself + upstrea object Tracking branch (see upstream object); absent when none is + m configured + pr object Open PR/MR (see pr object); collected with --full + checks object CI pipeline (see checks object); collected with --full + dev_ser object {url, listening} from the project's list.url template; absent + ver when not configured + summary string LLM branch summary; needs --full, [list] summary = true, and a + [commit.generation] command + vars object Per-branch variables from wt config state vars + display object Rendered strings (see display object) + +head object + + Field Type Description + ──────────── ─────────── ───────────────────────────────────────────────────── + sha string Full commit SHA (40 chars) + short_sha string Short commit SHA, abbreviated per core.abbrev + (auto-extends for ambiguous prefixes) + subject string/null Commit subject (first line); null when not loaded, as + for a prunable worktree + committed_at string/null Committer time, RFC 3339 UTC; null when not loaded -A JSON Schema for the envelope is published at -worktrunk.dev/schema/list-v2.json. -It describes what wt writes, so a field the absence rule can omit is -optional there rather than required-and-null. - -Schema 1 - -The original bare-array format, and the default while unset: +worktree object -  # Current worktree path (for scripts) -  wt list --format=json | jq -r '.[] | select(.is_current) | .path' -   -  # Branches with uncommitted changes -  wt list --format=json | jq '.[] | select(.working_tree.modified)' -   -  # Worktrees with merge conflicts -  wt list --format=json | jq '.[] | select(.operation_state == "conflicts")' -   -  # Branches ahead of main (needs merging) -  wt list --format=json | jq '.[] | select(.main.ahead > 0) | .branch' -   -  # Integrated branches (safe to remove) -  wt list --format=json | jq '.[] | select(.main_state == "integrated" or  -  .main_state == "empty") | .branch' -   -  # Branches without worktrees -  wt list --format=json --branches | jq '.[] | select(.kind == "branch") |  -  .branch' -   -  # Worktrees ahead of remote (needs pushing) -  wt list --format=json | jq '.[] | select(.remote.ahead > 0) | {branch, ahead:  -  .remote.ahead}' -   -  # Stale CI (local changes not reflected in CI) -  wt list --format=json --full | jq '.[] | select(.ci.stale) | .branch' - -Fields: - - Field Type Description - ─────────────── ─────────── ────────────────────────────────────────────────── - branch string/null Branch name (null for detached HEAD) - path string Worktree path (absent for branches without - worktrees) - kind string "worktree" or "branch" - commit object Commit info (see below) - working_tree object Working tree state (see below) - main_state string Relation to the default branch (see below) - integration_rea string Why branch is integrated (see below) - son - operation_state string "conflicts", "rebase", or "merge" (see Worktree); - absent when clean - main object Relationship to the default branch (see below); - absent when is_main - remote object Tracking branch info (see below); absent when no - tracking - worktree object Worktree metadata (see below) - is_main boolean Is the main worktree - is_current boolean Is the current worktree - is_previous boolean Previous worktree from wt switch - ci object CI status (see below); --full only, then absent - when no PR/MR or branch workflow - repo_url string Repository web URL derived from the primary - remote; absent when the remote URL cannot be - parsed - repo object Structured repository metadata (see below); - includes remote - url string Dev server URL from project config; absent when - not configured - url_active boolean Whether the URL's port is listening; absent when - not configured - summary string LLM-generated branch summary; --full only, then - absent when not configured or no summary - statusline string Pre-formatted status with colors and links - symbols string Raw status symbols without colors (e.g., "!?↓") - vars object Per-branch variables from wt config state vars - (absent when empty) - columns object Rendered custom column values keyed by header; - empty cells omitted (absent when none configured) - -Commit object - - Field Type Description - ───────── ────── ───────────────────────────────────────────────────────────── - sha string Full commit SHA (40 chars) - short_sha string Short commit SHA, abbreviated per core.abbrev (auto-extends - for ambiguous prefixes) - message string Commit message (first line) - timestamp number Unix timestamp - -working_tree object +Present only on worktree rows. The location attributes are independent, so they +co-occur — see Worktree for the symbols, which pick one: + + Field Type Description + ──────────────── ─────────── ───────────────────────────────────────────────── + path string Worktree path + main boolean Is the main worktree + current boolean Is the worktree the command ran from + previous boolean Is the previous worktree (wt switch -) + detached boolean HEAD is detached + locked object {reason}; absent when not locked + prunable object {reason}; absent when git doesn't report the + worktree prunable + branch_mismatch boolean Worktree isn't at the path its branch implies + duplicate_branch boolean Another worktree has the same branch checked out + operation string/null In-progress operation: "merge", "rebase", + "cherry_pick", "revert", "bisect"; absent when + none + changes object/null Working-tree state (see changes object) + +changes object The five change flags map to the Working tree symbols (renamed and deleted have none of their own): - Field Type Description - ───────── ─────── ─────────────────────────────────────── - staged boolean Has staged files - modified boolean Has modified files (unstaged) - untracked boolean Has untracked files - renamed boolean Has renamed files - deleted boolean Has deleted files - diff object Lines changed vs HEAD: {added, deleted} - -main object - - Field Type Description - ────── ────── ───────────────────────────────────────────────────── - ahead number Commits ahead of the default branch - behind number Commits behind the default branch - diff object Lines changed vs the default branch: {added, deleted} - -remote object + Field Type Description + ────────── ──────────── ─────────────────────────────────────── + staged boolean Has staged files + modified boolean Has modified files (unstaged) + untracked boolean Has untracked files + renamed boolean Has renamed files + deleted boolean Has deleted files + conflicted boolean/null Tracked files carry merge conflicts + diff object/null Lines changed vs HEAD: {added, deleted} + +default_branch object + +Independent facts; the table's priority-collapsed symbol is display.state. + + Field Type Description + ──────── ─────────── ───────────────────────────────────────────────────────── + ahead number/null Commits ahead of the default branch (null for orphans) + behind number/null Commits behind the default branch (null for orphans) + diff object/null Lines changed vs the default branch: {added, deleted} + orphan boolean/nul No common ancestor with the default branch + l + integrat object/null {reason} — which check found the content integrated (see + ion integration reasons); absent when determined + not-integrated, null when a dirty tree skipped the checks + merge_co boolean/nul Merging into the default branch would conflict, simulated + nflicts l locally with git merge-tree + +upstream object ahead / behind drive the Remote divergence symbol: - Field Type Description - ────── ────── ──────────────────────────── - name string Remote name (e.g., "origin") - branch string Remote branch name - ahead number Commits ahead of remote - behind number Commits behind remote - -worktree object + Field Type Description + ────── ─────────── ──────────────────────────── + remote string Remote name (e.g., "origin") + branch string/null Branch name on the remote + ahead number Commits ahead of remote + behind number Commits behind remote -Present only for worktree-kind items. state is the worktree-location attribute — - see Worktree for its symbols: +pr object Field Type Description - ──────── ─────── ───────────────────────────────────────────────────────────── - state string "branch_worktree_mismatch", "duplicate_branch", "prunable", - or "locked" (absent when normal) - reason string Reason for locked/prunable state - detached boolean HEAD is detached - -ci object - - Field Type Description - ──────────── ─────── ───────────────────────────────────────────────────────── - status string CI status (see below) - source string "pr" (PR/MR) or "branch" (branch workflow) - number integer PR/MR number; absent for branch workflows - stale boolean Local HEAD differs from remote (unpushed changes) - url string URL to the PR/MR page - repo_url string Web URL of the repo the PR/MR targets (the upstream for - fork PRs); absent when url is absent or unrecognized - repo object Structured metadata for the repository the PR/MR targets; - never includes remote - review_state string Review state (see below); absent when the forge reports - no review signal + ─────── ───────── ──────────────────────────────────────────────────────────── + number integer/n PR/MR number + ull + url string/nu URL to the PR/MR page + ll + review string Review state (see review states); absent when the forge + reports no review signal + mergeab boolean/n False when the forge reports conflicts, null otherwise — the + le ull fetch records only the conflicted case + repo object Structured metadata for the repository the PR/MR targets, + the upstream for fork PRs (see repo object); absent when the + URL doesn't parse + +checks object + + Field Type Description + ────── ─────────── ─────────────────────────────────────────────────────────── + status string/null "passed", "running", or "failed"; null when a conflicts + report masks it + source string "pr" (PR/MR) or "branch" (branch workflow) + stale boolean Local HEAD differs from remote (unpushed changes) + +Three CI status values have no checks.status of their own, because the shape of +pr and checks reports them: a fetch error (⚠) makes both null, no CI leaves +checks absent, and merge conflicts leave checks.status null with pr.mergeable +false. + +dev_server object + + Field Type Description + ───────── ──────────── ─────────────────────────────────── + url string Dev server URL from project config + listening boolean/null Whether the URL's port is listening + +display object + +Presentation only — every value here renders facts that appear elsewhere in the +item: + + Field Type Description + ────────── ────── ──────────────────────────────────────────────────────────── + state string The table's collapsed default-branch state (see state + values); absent when none applies + symbols string Raw status symbols without colors (e.g., "!?↓") + statusline string Pre-formatted status with colors and links + columns object Rendered custom column values keyed by header; empty cells + omitted repo object -Top-level repo describes the local checkout's repository as derived from the -primary remote. ci.repo describes the repository targeted by the PR/MR URL in -ci.url (for fork PRs, this is the upstream target). Existing repo_url and -ci.repo_url fields remain available and carry the same URL as repo.url / -ci.repo.url. - - Field Type Description - ──────── ────── ────────────────────────────────────────────────────────────── - url string Repository web URL - provider string "github", "gitlab", "gitea", "azure-devops", or "unknown" - host string Repository web host - owner string Owner, organization, or namespace path - name string Repository name - project string Azure DevOps project name; absent for other providers - remote string Local remote name used for top-level repo metadata; absent - from ci.repo - -main_state values +repo.forge describes the local checkout's repository as derived from the primary + remote. pr.repo describes the repository targeted by the PR/MR (for fork PRs, +the upstream target). + + Field Type Description + ──────── ────── ───────────────────────────────────────────────────────────── + url string Repository web URL + provider string "github", "gitlab", "gitea", "azure-devops", or "unknown" + host string Repository web host + owner string Owner, organization, or namespace path + name string Repository name + project string Azure DevOps project name; absent for other providers + remote string Local remote name; present on repo.forge, absent from pr.repo + +state values The single highest-priority state describing the branch's relation to the default branch; absent when none applies (a normal up-to-date branch). Each @@ -646,34 +604,119 @@ value is one Default-branch symbol — see Default branch for the symbol and the full meaning of each value ("is_main", "orphan", "empty", "integrated", "would_conflict", "same_commit", "diverged", "ahead", "behind"). -integration_reason values +integration reasons -Set only when main_state == "integrated" (the ⊂ symbol), recording which check -matched. Checks run cheapest-first and the first match wins. JSON-only — every -reason renders as the same ⊂: +default_branch.integration.reason records which check matched. Checks run +cheapest-first and the first match wins. JSON-only — every reason renders as the + same ⊂: Value Meaning ────────────── ─────────────────────────────────────────────────────────────── + "same_commit" Branch HEAD is the default branch's commit "ancestor" Branch HEAD is an ancestor of the default branch, which has moved past it - "no-added-chan The three-dot diff (main...branch) is empty — no file changes + "no_added_chan The three-dot diff (main...branch) is empty — no file changes ges" beyond the merge-base - "trees-match" Different history, but the branch's tree is identical to the + "trees_match" Different history, but the branch's tree is identical to the default branch's - "merge-adds-no The branch has changes, but merging them leaves the default + "merge_adds_no The branch has changes, but merging them leaves the default thing" branch's tree unchanged (e.g. a squash merge where the target advanced on other files) - "patch-id-matc The branch's squashed diff matches a single commit on the + "patch_id_matc The branch's squashed diff matches a single commit on the h" default branch (e.g. a GitHub/GitLab squash merge) -ci.status and ci.review_state values +review states + +pr.review is one of "changes_requested", "pending", "draft", "approved", absent +when the forge reports no review signal. The CI status section is the single +source: its table maps the colored values, and the notes below it cover "draft" +and "approved". The vocabulary matches Claude Code's statusline pr.review_state +field. + +  # Current worktree path (for scripts) +  wt list --format=json | jq -r '.items[] | select(.worktree.current) |  +  .worktree.path' +   +  # Branches with uncommitted changes +  wt list --format=json | jq '.items[] | select(.worktree.changes.modified)' +   +  # Worktrees with merge conflicts +  wt list --format=json | jq '.items[] | select(.worktree.changes.conflicted)' +   +  # Branches ahead of main (needs merging) +  wt list --format=json | jq '.items[] | select(.default_branch.ahead > 0) |  +  .branch' +   +  # Integrated branches (safe to remove) +  wt list --format=json | jq '.items[] | select(.display.state == "integrated"  +  or .display.state == "empty") | .branch' +   +  # Branches without worktrees +  wt list --format=json --branches | jq '.items[] | select(.worktree == null) |  +  .branch' +   +  # Worktrees ahead of upstream (needs pushing) +  wt list --format=json | jq '.items[] | select(.upstream.ahead > 0) | {branch,  +  ahead: .upstream.ahead}' +   +  # Stale CI (local changes not reflected in CI) +  wt list --format=json --full | jq '.items[] | select(.checks.stale) | .branch' + +A JSON Schema for the envelope is published at +worktrunk.dev/schema/list-v2.json. +It describes what wt writes, so a field the absence rule can omit is +optional there rather than required-and-null. + +Schema 1 -The CI status section above is the single source for both fields: the table maps - each colored value, and the notes below it cover "draft" and "approved". -ci.status is one of "passed", "running", "failed", "conflicts", "no-ci", -"error"; ci.review_state is one of "changes_requested", "pending", "draft", -"approved", absent when the forge reports no review signal. The vocabulary -matches Claude Code's statusline pr.review_state field. +The original bare-array format — one object per row, no envelope — and the +default while [list] json-schema is unset. It is on its way out: wt config +update adopts = 2, a future release flips the default, and a later one +removes schema 1. Its fields all have a schema-2 home: + + Schema 1 Schema 2 + ──────────────────────────────────────── ───────────────────────────────────── + branch branch + path worktree.path + kind the row's shape — a worktree object + means "worktree", no worktree object + means "branch" + commit.sha, .short_sha, .message head.sha, .short_sha, .subject + commit.timestamp (Unix) head.committed_at (RFC 3339 UTC) + working_tree.staged, .modified, worktree.changes.* + .untracked, .renamed, .deleted, .diff + operation_state "conflicts" worktree.changes.conflicted + operation_state "rebase", "merge", … worktree.operation + main_state display.state + integration_reason default_branch.integration.reason + (snake_case, and it reports + "same_commit" rather than folding it + into main_state) + main.ahead, .behind, .diff default_branch.ahead, .behind, .diff + remote.name, .branch, .ahead, .behind upstream.remote, .branch, .ahead, + .behind + worktree.state worktree.locked, .prunable, + .duplicate_branch, .branch_mismatch — + independent, so they co-occur + worktree.reason worktree.locked.reason, + worktree.prunable.reason + worktree.detached worktree.detached + is_main, is_current, is_previous worktree.main, .current, .previous + ci.status checks.status, plus the shapes + described under checks object + ci.source, ci.stale checks.source, checks.stale + ci.number, ci.url, ci.review_state pr.number, pr.url, pr.review + ci.repo, ci.repo_url pr.repo, pr.repo.url + repo, repo_url the envelope's repo.forge, + repo.forge.url + url, url_active dev_server.url, dev_server.listening + summary, vars summary, vars + statusline, symbols, columns display.statusline, display.symbols, + display.columns + +The envelope's repo.default_branch and collected have no schema-1 equivalent, +and schema 2 separates "nothing to report" from "not determined" — see How "no +value" reads. Missing a field that would be generally useful? Open an issue at https://github.com/max-sixty/worktrunk. From 051516dd2873fe94f302620c85f4634dec40d3c1 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 1 Sep 2026 16:37:39 -0700 Subject: [PATCH 03/17] list: fix three doc claims and expose the branch marker in JSON (#58) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The CI-cache line pointed at `wt config state`, a group with no default action; it now 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 now says it emits the current schema — a one-item array under 1, the envelope object under 2. - The same line records why that surface stays silent when `[list] json-schema` is unset while plain `wt list --format=json` prints a deprecation notice: a prompt consumer can't act on a warning drawn over its own line, and the nag reaches the same user on their next interactive run. Keeping statusline silent is the existing behaviour (`suppress_warnings` on the surface); this documents it rather than changing it. - The Status-symbols section documented every subcolumn except the branch marker. A "Marker" subsection covers it, and both JSON schemas gain an additive `marker` field (schema 1 `Option` after `symbols`; schema 2 `Tri` beside `vars`, absent when unset and null while unresolved), so the value is readable without parsing it back out of `symbols`. `docs/public/schema/list-v2.json` regenerated. Co-Authored-By: Claude Fable 5.1 --- docs/public/schema/list-v2.json | 4 + docs/src/content/docs/list.md | 17 +- .../skills/worktrunk/reference/list.md | 17 +- skills/worktrunk/reference/list.md | 17 +- src/cli/list.rs | 2 +- src/cli/mod.rs | 15 +- src/commands/list/json_output.rs | 7 + src/commands/list/json_v2.rs | 14 + tests/integration_tests/list.rs | 19 + ...tegration_tests__help__help_list_long.snap | 15 +- ...tion_tests__help__help_list_narrow_80.snap | 16 +- ...__list_json_schema_2_with_user_marker.snap | 346 ++++++++++++++++++ ...sts__list__list_json_with_user_marker.snap | 19 +- ...gration_tests__list_layout__width_120.snap | 3 +- ...egration_tests__list_layout__width_60.snap | 16 +- 15 files changed, 504 insertions(+), 23 deletions(-) create mode 100644 tests/snapshots/integration__integration_tests__list__list_json_schema_2_with_user_marker.snap diff --git a/docs/public/schema/list-v2.json b/docs/public/schema/list-v2.json index e709006947..3f338febb5 100644 --- a/docs/public/schema/list-v2.json +++ b/docs/public/schema/list-v2.json @@ -425,6 +425,10 @@ ], "description": "HEAD commit; null for unborn branches." }, + "marker": { + "$ref": "#/$defs/Nullable_string", + "description": "Branch marker stored via `wt config state marker`; absent when none\nis set, null while the lookup is unresolved. The same string is the\nlast glyph in `display.symbols`." + }, "pr": { "$ref": "#/$defs/Nullable_JsonPr", "description": "Open PR/MR; absent when none exists (or CI wasn't collected), null\nwhen the forge fetch failed." diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index 68e0f10643..5670d0508d 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -129,7 +129,7 @@ The two remaining review states have no indicator of their own: `"draft"` only d Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — `pr.review` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state cache` to view or clear. ### LLM summaries @@ -207,6 +207,16 @@ Relation to the tracking branch, derived from the `upstream.ahead` / `upstream.b | `⇣` | `behind` > 0 | Behind remote | | `⇅` | `ahead` > 0, `behind` > 0 | Diverged from remote | +### Marker + +The last subcolumn carries the branch's own marker — whatever +[`wt config state marker set`](/config/#wt-config-state-marker) stored, usually +an emoji, which is why the subcolumn is two cells wide. Nothing in `wt` writes +it; it is there for agents and scripts to say what a branch is for, and it +reads back as the item's `marker` field. `wt config state marker set 🤖` on a +branch whose default-branch state is `_` renders `_ 🤖` in the column and +`"_🤖"` in `display.symbols`. + ### Placeholder symbols These appear across all columns while the table is loading: @@ -294,6 +304,7 @@ Item fields: | `checks` | object | CI pipeline (see [checks object](#checks-object)); collected with `--full` | | `dev_server` | object | `{url, listening}` from the project's `list.url` template; absent when not configured | | `summary` | string | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | +| `marker` | string | Branch marker from [`wt config state marker`](/config/#wt-config-state-marker); absent when none is set | | `vars` | object | Per-branch variables from [`wt config state vars`](/config/#wt-config-state-vars) | | `display` | object | Rendered strings (see [display object](#display-object)) | @@ -497,7 +508,7 @@ removes schema 1. Its fields all have a schema-2 home: | `ci.repo`, `ci.repo_url` | `pr.repo`, `pr.repo.url` | | `repo`, `repo_url` | the envelope's `repo.forge`, `repo.forge.url` | | `url`, `url_active` | `dev_server.url`, `dev_server.listening` | -| `summary`, `vars` | `summary`, `vars` | +| `summary`, `vars`, `marker` | `summary`, `vars`, `marker` | | `statusline`, `symbols`, `columns` | `display.statusline`, `display.symbols`, `display.columns` | The envelope's `repo.default_branch` and `collected` have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see [How "no value" reads](#schema-2). @@ -575,7 +586,7 @@ The line carries the same cells as the worktree's row in `wt list`. A stale CI s ### Output formats - `table` (default): `branch status HEAD± main↕ main…± Remote⇅ CI URL` -- `json`: A one-entry array in the `wt list --format=json` schema +- `json`: the current [`wt list --format=json`](/list/#json-output) schema — a one-item array under schema 1, the envelope object under schema 2. A prompt consumer can't act on a warning printed over its own line, so this surface stays silent: an unset `[list] json-schema` resolves to schema 1 here without the deprecation notice plain `wt list --format=json` prints. - `claude-code`: the `table` cells, preceded by `dir` and followed by `model context pace` A cell with nothing to show is left out rather than blanked, so most lines are shorter than that; `claude-code` also drops `branch` where `dir` already ends in `.`. A line that still overruns the terminal drops whole cells, least important first, starting with the dev server URL. diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 013d0229ce..306d95f1cf 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -116,7 +116,7 @@ The two remaining review states have no indicator of their own: `"draft"` only d Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — `pr.review` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state cache` to view or clear. ### LLM summaries @@ -192,6 +192,16 @@ Relation to the tracking branch, derived from the `upstream.ahead` / `upstream.b | `⇣` | `behind` > 0 | Behind remote | | `⇅` | `ahead` > 0, `behind` > 0 | Diverged from remote | +### Marker + +The last subcolumn carries the branch's own marker — whatever +[`wt config state marker set`](https://worktrunk.dev/config/#wt-config-state-marker) stored, usually +an emoji, which is why the subcolumn is two cells wide. Nothing in `wt` writes +it; it is there for agents and scripts to say what a branch is for, and it +reads back as the item's `marker` field. `wt config state marker set 🤖` on a +branch whose default-branch state is `_` renders `_ 🤖` in the column and +`"_🤖"` in `display.symbols`. + ### Placeholder symbols These appear across all columns while the table is loading: @@ -279,6 +289,7 @@ Item fields: | `checks` | object | CI pipeline (see [checks object](#checks-object)); collected with `--full` | | `dev_server` | object | `{url, listening}` from the project's `list.url` template; absent when not configured | | `summary` | string | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | +| `marker` | string | Branch marker from [`wt config state marker`](https://worktrunk.dev/config/#wt-config-state-marker); absent when none is set | | `vars` | object | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | | `display` | object | Rendered strings (see [display object](#display-object)) | @@ -482,7 +493,7 @@ removes schema 1. Its fields all have a schema-2 home: | `ci.repo`, `ci.repo_url` | `pr.repo`, `pr.repo.url` | | `repo`, `repo_url` | the envelope's `repo.forge`, `repo.forge.url` | | `url`, `url_active` | `dev_server.url`, `dev_server.listening` | -| `summary`, `vars` | `summary`, `vars` | +| `summary`, `vars`, `marker` | `summary`, `vars`, `marker` | | `statusline`, `symbols`, `columns` | `display.statusline`, `display.symbols`, `display.columns` | The envelope's `repo.default_branch` and `collected` have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see [How "no value" reads](#schema-2). @@ -556,7 +567,7 @@ The line carries the same cells as the worktree's row in `wt list`. A stale CI s ### Output formats - `table` (default): `branch status HEAD± main↕ main…± Remote⇅ CI URL` -- `json`: A one-entry array in the `wt list --format=json` schema +- `json`: the current [`wt list --format=json`](https://worktrunk.dev/list/#json-output) schema — a one-item array under schema 1, the envelope object under schema 2. A prompt consumer can't act on a warning printed over its own line, so this surface stays silent: an unset `[list] json-schema` resolves to schema 1 here without the deprecation notice plain `wt list --format=json` prints. - `claude-code`: the `table` cells, preceded by `dir` and followed by `model context pace` A cell with nothing to show is left out rather than blanked, so most lines are shorter than that; `claude-code` also drops `branch` where `dir` already ends in `.`. A line that still overruns the terminal drops whole cells, least important first, starting with the dev server URL. diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 013d0229ce..306d95f1cf 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -116,7 +116,7 @@ The two remaining review states have no indicator of their own: `"draft"` only d Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — `pr.review` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state cache` to view or clear. ### LLM summaries @@ -192,6 +192,16 @@ Relation to the tracking branch, derived from the `upstream.ahead` / `upstream.b | `⇣` | `behind` > 0 | Behind remote | | `⇅` | `ahead` > 0, `behind` > 0 | Diverged from remote | +### Marker + +The last subcolumn carries the branch's own marker — whatever +[`wt config state marker set`](https://worktrunk.dev/config/#wt-config-state-marker) stored, usually +an emoji, which is why the subcolumn is two cells wide. Nothing in `wt` writes +it; it is there for agents and scripts to say what a branch is for, and it +reads back as the item's `marker` field. `wt config state marker set 🤖` on a +branch whose default-branch state is `_` renders `_ 🤖` in the column and +`"_🤖"` in `display.symbols`. + ### Placeholder symbols These appear across all columns while the table is loading: @@ -279,6 +289,7 @@ Item fields: | `checks` | object | CI pipeline (see [checks object](#checks-object)); collected with `--full` | | `dev_server` | object | `{url, listening}` from the project's `list.url` template; absent when not configured | | `summary` | string | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | +| `marker` | string | Branch marker from [`wt config state marker`](https://worktrunk.dev/config/#wt-config-state-marker); absent when none is set | | `vars` | object | Per-branch variables from [`wt config state vars`](https://worktrunk.dev/config/#wt-config-state-vars) | | `display` | object | Rendered strings (see [display object](#display-object)) | @@ -482,7 +493,7 @@ removes schema 1. Its fields all have a schema-2 home: | `ci.repo`, `ci.repo_url` | `pr.repo`, `pr.repo.url` | | `repo`, `repo_url` | the envelope's `repo.forge`, `repo.forge.url` | | `url`, `url_active` | `dev_server.url`, `dev_server.listening` | -| `summary`, `vars` | `summary`, `vars` | +| `summary`, `vars`, `marker` | `summary`, `vars`, `marker` | | `statusline`, `symbols`, `columns` | `display.statusline`, `display.symbols`, `display.columns` | The envelope's `repo.default_branch` and `collected` have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see [How "no value" reads](#schema-2). @@ -556,7 +567,7 @@ The line carries the same cells as the worktree's row in `wt list`. A stale CI s ### Output formats - `table` (default): `branch status HEAD± main↕ main…± Remote⇅ CI URL` -- `json`: A one-entry array in the `wt list --format=json` schema +- `json`: the current [`wt list --format=json`](https://worktrunk.dev/list/#json-output) schema — a one-item array under schema 1, the envelope object under schema 2. A prompt consumer can't act on a warning printed over its own line, so this surface stays silent: an unset `[list] json-schema` resolves to schema 1 here without the deprecation notice plain `wt list --format=json` prints. - `claude-code`: the `table` cells, preceded by `dir` and followed by `model context pace` A cell with nothing to show is left out rather than blanked, so most lines are shorter than that; `claude-code` also drops `branch` where `dir` already ends in `.`. A line that still overruns the terminal drops whole cells, least important first, starting with the dev server URL. diff --git a/src/cli/list.rs b/src/cli/list.rs index 695028777a..6c55bfd2e0 100644 --- a/src/cli/list.rs +++ b/src/cli/list.rs @@ -12,7 +12,7 @@ pub enum ListSubcommand { ## Output formats - `table` (default): `branch status HEAD± main↕ main…± Remote⇅ CI URL` -- `json`: A one-entry array in the `wt list --format=json` schema +- `json`: the current [`wt list --format=json`](/list/#json-output) schema — a one-item array under schema 1, the envelope object under schema 2. A prompt consumer can't act on a warning printed over its own line, so this surface stays silent: an unset `[list] json-schema` resolves to schema 1 here without the deprecation notice plain `wt list --format=json` prints. - `claude-code`: the `table` cells, preceded by `dir` and followed by `model context pace` A cell with nothing to show is left out rather than blanked, so most lines are shorter than that; `claude-code` also drops `branch` where `dir` already ends in `.`. A line that still overruns the terminal drops whole cells, least important first, starting with the dev server URL. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 97e6a1b682..9f35f4d9db 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -897,7 +897,7 @@ The two remaining review states have no indicator of their own: `"draft"` only d Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — `pr.review` is then `"approved"` or absent, respectively. GitLab MR data carries only `"pending"` and `"draft"` — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state` to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR (`"draft"`) or when unpushed local changes make the status stale (`checks.stale`). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with `--remotes` — get CI status detection. Results are cached for 30-60 seconds; use `wt config state cache` to view or clear. ### LLM summaries @@ -973,6 +973,16 @@ Relation to the tracking branch, derived from the `upstream.ahead` / `upstream.b | `⇣` | `behind` > 0 | Behind remote | | `⇅` | `ahead` > 0, `behind` > 0 | Diverged from remote | +### Marker + +The last subcolumn carries the branch's own marker — whatever +[`wt config state marker set`](/config/#wt-config-state-marker) stored, usually +an emoji, which is why the subcolumn is two cells wide. Nothing in `wt` writes +it; it is there for agents and scripts to say what a branch is for, and it +reads back as the item's `marker` field. `wt config state marker set 🤖` on a +branch whose default-branch state is `_` renders `_ 🤖` in the column and +`"_🤖"` in `display.symbols`. + ### Placeholder symbols These appear across all columns while the table is loading: @@ -1060,6 +1070,7 @@ Item fields: | `checks` | object | CI pipeline (see [checks object](#checks-object)); collected with `--full` | | `dev_server` | object | `{url, listening}` from the project's `list.url` template; absent when not configured | | `summary` | string | LLM branch summary; needs `--full`, `[list] summary = true`, and a `[commit.generation]` command | +| `marker` | string | Branch marker from [`wt config state marker`](/config/#wt-config-state-marker); absent when none is set | | `vars` | object | Per-branch variables from [`wt config state vars`](/config/#wt-config-state-vars) | | `display` | object | Rendered strings (see [display object](#display-object)) | @@ -1263,7 +1274,7 @@ removes schema 1. Its fields all have a schema-2 home: | `ci.repo`, `ci.repo_url` | `pr.repo`, `pr.repo.url` | | `repo`, `repo_url` | the envelope's `repo.forge`, `repo.forge.url` | | `url`, `url_active` | `dev_server.url`, `dev_server.listening` | -| `summary`, `vars` | `summary`, `vars` | +| `summary`, `vars`, `marker` | `summary`, `vars`, `marker` | | `statusline`, `symbols`, `columns` | `display.statusline`, `display.symbols`, `display.columns` | The envelope's `repo.default_branch` and `collected` have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see [How "no value" reads](#schema-2). diff --git a/src/commands/list/json_output.rs b/src/commands/list/json_output.rs index 2cf58a2619..860c8168b1 100644 --- a/src/commands/list/json_output.rs +++ b/src/commands/list/json_output.rs @@ -115,6 +115,11 @@ pub struct JsonItem { #[serde(skip_serializing_if = "Option::is_none")] pub symbols: Option, + /// Branch marker stored via `wt config state marker`; absent when none is + /// set. The same string is the last glyph in `symbols`. + #[serde(skip_serializing_if = "Option::is_none")] + pub marker: Option, + /// Custom variables stored via `wt config state vars` #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub vars: BTreeMap, @@ -387,6 +392,7 @@ impl JsonItem { // Statusline and symbols (raw, without ANSI codes) let statusline = item.statusline.clone(); let symbols = Some(format_raw_symbols(&item.status_symbols)).filter(|s| !s.is_empty()); + let marker = item.user_marker.clone().flatten(); // Per-branch vars data (pre-fetched, moved out to avoid cloning) let vars = super::json_v2::take_vars(item.branch(), all_vars); @@ -420,6 +426,7 @@ impl JsonItem { summary, statusline, symbols, + marker, vars, columns, } diff --git a/src/commands/list/json_v2.rs b/src/commands/list/json_v2.rs index 1c06e11791..acefa654ab 100644 --- a/src/commands/list/json_v2.rs +++ b/src/commands/list/json_v2.rs @@ -190,6 +190,12 @@ pub struct JsonItemV2 { #[serde(skip_serializing_if = "Tri::is_absent")] pub summary: Tri, + /// Branch marker stored via `wt config state marker`; absent when none + /// is set, null while the lookup is unresolved. The same string is the + /// last glyph in `display.symbols`. + #[serde(skip_serializing_if = "Tri::is_absent")] + pub marker: Tri, + /// Custom variables stored via `wt config state vars`. #[serde(skip_serializing_if = "BTreeMap::is_empty")] pub vars: BTreeMap, @@ -683,6 +689,13 @@ impl JsonItemV2 { } }; + // Marker: gate 5 unresolved → null; resolved with none set → absent. + let marker = match &item.user_marker { + None => Tri::Unknown, + Some(None) => Tri::Absent, + Some(Some(value)) => Tri::Known(value.clone()), + }; + let vars = take_vars(item.branch(), all_vars); let columns = columns_map(custom_columns, &item.custom_values); @@ -707,6 +720,7 @@ impl JsonItemV2 { checks, dev_server, summary, + marker, vars, display, } diff --git a/tests/integration_tests/list.rs b/tests/integration_tests/list.rs index f43b2401d9..4f876beaf8 100644 --- a/tests/integration_tests/list.rs +++ b/tests/integration_tests/list.rs @@ -1183,6 +1183,25 @@ fn test_list_json_with_user_marker(mut repo: TestRepo) { }); } +/// Schema 2 reports the branch marker as its own `marker` field, and folds +/// it into `display.symbols` the way the table folds it into the Status +/// column. A branch with no marker set omits the field entirely. +#[rstest] +fn test_list_json_schema_2_with_user_marker(mut repo: TestRepo) { + repo.write_test_config("[list]\njson-schema = 2\n"); + repo.commit_with_age("Initial commit", DAY); + + repo.add_worktree("with-status"); + repo.set_marker("with-status", "🔧"); + repo.add_worktree("without-status"); + + assert_cmd_snapshot!({ + let mut cmd = list_snapshots::command(&repo, repo.root_path()); + cmd.arg("--format=json"); + cmd + }); +} + #[rstest] fn test_list_json_with_git_operation(mut repo: TestRepo) { // Test JSON output includes git_operation field when worktree is in rebase state diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 27ec01811c..4fa776a70c 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -198,7 +198,7 @@ The two remaining review states have no indicator of their own: "draft" Color precedence resolves the fold: changes-requested (magenta) outranks running checks — waiting can't clear it — while an outstanding required review (cyan) only recolors an otherwise green or quiet branch. Cool colors mean waiting, warm colors mean act. An approved PR, or one with no review signal at all (no required reviewers and no reviews), keeps its plain pipeline color — pr.review is then "approved" or absent, respectively. GitLab MR data carries only "pending" and "draft" — no approved or changes-requested signal. -CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR ("draft") or when unpushed local changes make the status stale (checks.stale). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with --remotes — get CI status detection. Results are cached for 30-60 seconds; use wt config state to view or clear. +CI cells are clickable links to the PR or pipeline page, and appear dimmed for a draft PR/MR ("draft") or when unpushed local changes make the status stale (checks.stale). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with --remotes — get CI status detection. Results are cached for 30-60 seconds; use wt config state cache to view or clear. LLM summaries @@ -272,6 +272,16 @@ Relation to the tracking branch, derived from the upstream.ahead / u ⇣ behind > 0 Behind remote ⇅ ahead > 0, behind > 0 Diverged from remote +Marker + +The last subcolumn carries the branch's own marker — whatever +wt config state marker set stored, usually +an emoji, which is why the subcolumn is two cells wide. Nothing in wt writes +it; it is there for agents and scripts to say what a branch is for, and it +reads back as the item's marker field. wt config state marker set 🤖 on a +branch whose default-branch state is _ renders _ 🤖 in the column and +"_🤖" in display.symbols. + Placeholder symbols These appear across all columns while the table is loading: @@ -357,6 +367,7 @@ Item fields: checks object CI pipeline (see checks object); collected with --full dev_server object {url, listening} from the project's list.url template; absent when not configured summary string LLM branch summary; needs --full, [list] summary = true, and a [commit.generation] command + marker string Branch marker from wt config state marker; absent when none is set vars object Per-branch variables from wt config state vars display object Rendered strings (see display object) @@ -558,7 +569,7 @@ removes schema 1. Its fields all have a schema-2 home: ci.repo, ci.repo_url pr.repo, pr.repo.url repo, repo_url the envelope's repo.forge, repo.forge.url url, url_active dev_server.url, dev_server.listening - summary, vars summary, vars + summary, vars, marker summary, vars, marker statusline, symbols, columns display.statusline, display.symbols, display.columns The envelope's repo.default_branch and collected have no schema-1 equivalent, and schema 2 separates "nothing to report" from "not determined" — see How "no value" reads. diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 3ea5eea3e2..24a70e93e3 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -239,7 +239,7 @@ CI cells are clickable links to the PR or pipeline page, and appear dimmed for a (checks.stale). PRs/MRs are checked first, then branch workflows/pipelines for branches with an upstream. Local-only branches show blank; remote-only branches — visible with --remotes — get CI status detection. Results are cached for 30-60 - seconds; use wt config state to view or clear. + seconds; use wt config state cache to view or clear. LLM summaries @@ -348,6 +348,16 @@ Relation to the tracking branch, derived from the upstream.ahead / ⇣ behind > 0 Behind remote ⇅ ahead > 0, behind > 0 Diverged from remote +Marker + +The last subcolumn carries the branch's own marker — whatever +wt config state marker set stored, usually +an emoji, which is why the subcolumn is two cells wide. Nothing in wt writes +it; it is there for agents and scripts to say what a branch is for, and it +reads back as the item's marker field. wt config state marker set 🤖 on a +branch whose default-branch state is _ renders _ 🤖 in the column and +"_🤖" in display.symbols. + Placeholder symbols These appear across all columns while the table is loading: @@ -450,6 +460,8 @@ Item fields: ver when not configured summary string LLM branch summary; needs --full, [list] summary = true, and a [commit.generation] command + marker string Branch marker from wt config state marker; absent when none is + set vars object Per-branch variables from wt config state vars display object Rendered strings (see display object) @@ -710,7 +722,7 @@ removes schema 1. Its fields all have a schema-2 home: repo, repo_url the envelope's repo.forge, repo.forge.url url, url_active dev_server.url, dev_server.listening - summary, vars summary, vars + summary, vars, marker summary, vars, marker statusline, symbols, columns display.statusline, display.symbols, display.columns diff --git a/tests/snapshots/integration__integration_tests__list__list_json_schema_2_with_user_marker.snap b/tests/snapshots/integration__integration_tests__list__list_json_schema_2_with_user_marker.snap new file mode 100644 index 0000000000..cd0c6dd96f --- /dev/null +++ b/tests/snapshots/integration__integration_tests__list__list_json_schema_2_with_user_marker.snap @@ -0,0 +1,346 @@ +--- +source: tests/integration_tests/list.rs +info: + program: wt + args: + - list + - "--format=json" + env: + APPDATA: "[TEST_CONFIG_HOME]" + CLAUDE_CONFIG_DIR: "[TEST_CLAUDE_CONFIG]" + CLICOLOR_FORCE: "1" + COLUMNS: "500" + GIT_ALLOW_PROTOCOL: file + GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User + GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" + GIT_TERMINAL_PROMPT: "0" + HOME: "[TEST_HOME]" + LANG: C + LC_ALL: C + LLVM_PROFILE_FILE: "[LLVM_PROFILE_FILE]" + OPENCODE_CONFIG_DIR: "[TEST_OPENCODE_CONFIG]" + PATH: "[PATH]" + TERM: alacritty + USERPROFILE: "[TEST_HOME]" + WORKTRUNK_APPROVALS_PATH: "[TEST_APPROVALS]" + WORKTRUNK_CONFIG_PATH: "[TEST_CONFIG]" + WORKTRUNK_SYSTEM_CONFIG_PATH: "[TEST_SYSTEM_CONFIG]" + WORKTRUNK_TEST_BASH_INSTALLED: "0" + WORKTRUNK_TEST_CLAUDE_INSTALLED: "0" + WORKTRUNK_TEST_CODEX_INSTALLED: "0" + WORKTRUNK_TEST_DELAYED_STREAM_MS: "-1" + WORKTRUNK_TEST_EPOCH: "1735776000" + WORKTRUNK_TEST_FISH_INSTALLED: "0" + WORKTRUNK_TEST_GEMINI_INSTALLED: "0" + WORKTRUNK_TEST_MOCK_CONFIG_DIR: "[TEST_MOCK_CONFIG]" + WORKTRUNK_TEST_NUSHELL_ENV: "0" + WORKTRUNK_TEST_OPENCODE_INSTALLED: "0" + WORKTRUNK_TEST_PARENT_SHELL: "" + WORKTRUNK_TEST_POWERSHELL_ENV: "0" + WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" + WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" + WORKTRUNK_TEST_ZSH_INSTALLED: "0" + XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" +--- +success: true +exit_code: 0 +----- stdout ----- +{ + "schema": 2, + "repo": { + "default_branch": "main" + }, + "collected": { + "ci": false, + "summary": false + }, + "items": [ + { + "branch": "main", + "head": { + "sha": "33323bc15e5a41f2dff1a997b9cd2345d6b74871", + "short_sha": "33323bc", + "subject": "Initial commit", + "committed_at": "2025-01-01T00:00:00Z" + }, + "worktree": { + "path": "_REPO_", + "main": true, + "current": true, + "previous": false, + "detached": false, + "branch_mismatch": false, + "duplicate_branch": false, + "changes": { + "staged": false, + "modified": false, + "untracked": false, + "renamed": false, + "deleted": false, + "conflicted": false, + "diff": { + "added": 0, + "deleted": 0 + } + } + }, + "upstream": { + "remote": "origin", + "branch": "main", + "ahead": 1, + "behind": 0 + }, + "display": { + "state": "is_main", + "symbols": "^⇡", + "statusline": "main \u001b[2m^\u001b[22m\u001b[2m⇡\u001b[22m \u001b[32m⇡1\u001b[0m" + } + }, + { + "branch": "feature-a", + "head": { + "sha": "1b87d4731ea707905d15a726e193531c20affa14", + "short_sha": "1b87d47", + "subject": "Add feature-a file", + "committed_at": "2025-01-01T08:00:00Z" + }, + "worktree": { + "path": "_REPO_.feature-a", + "main": false, + "current": false, + "previous": false, + "detached": false, + "branch_mismatch": false, + "duplicate_branch": false, + "changes": { + "staged": false, + "modified": false, + "untracked": false, + "renamed": false, + "deleted": false, + "conflicted": false, + "diff": { + "added": 0, + "deleted": 0 + } + } + }, + "default_branch": { + "ahead": 1, + "behind": 1, + "diff": { + "added": 1, + "deleted": 0 + }, + "orphan": false, + "merge_conflicts": false + }, + "display": { + "state": "diverged", + "symbols": "↕", + "statusline": "feature-a \u001b[2m↕\u001b[22m \u001b[32m↑1\u001b[0m \u001b[2m\u001b[31m↓1\u001b[0m ^\u001b[32m+1\u001b[0m" + } + }, + { + "branch": "feature-b", + "head": { + "sha": "f62940fcec424585adf98625e722fdf990810614", + "short_sha": "f62940f", + "subject": "Add feature-b file", + "committed_at": "2025-01-01T08:00:00Z" + }, + "worktree": { + "path": "_REPO_.feature-b", + "main": false, + "current": false, + "previous": false, + "detached": false, + "branch_mismatch": false, + "duplicate_branch": false, + "changes": { + "staged": false, + "modified": false, + "untracked": false, + "renamed": false, + "deleted": false, + "conflicted": false, + "diff": { + "added": 0, + "deleted": 0 + } + } + }, + "default_branch": { + "ahead": 1, + "behind": 1, + "diff": { + "added": 1, + "deleted": 0 + }, + "orphan": false, + "merge_conflicts": false + }, + "display": { + "state": "diverged", + "symbols": "↕", + "statusline": "feature-b \u001b[2m↕\u001b[22m \u001b[32m↑1\u001b[0m \u001b[2m\u001b[31m↓1\u001b[0m ^\u001b[32m+1\u001b[0m" + } + }, + { + "branch": "feature-c", + "head": { + "sha": "345c7c93ad7c3d8f5b08380898d78e024019599c", + "short_sha": "345c7c9", + "subject": "Add feature-c file", + "committed_at": "2025-01-01T08:00:00Z" + }, + "worktree": { + "path": "_REPO_.feature-c", + "main": false, + "current": false, + "previous": false, + "detached": false, + "branch_mismatch": false, + "duplicate_branch": false, + "changes": { + "staged": false, + "modified": false, + "untracked": false, + "renamed": false, + "deleted": false, + "conflicted": false, + "diff": { + "added": 0, + "deleted": 0 + } + } + }, + "default_branch": { + "ahead": 1, + "behind": 1, + "diff": { + "added": 1, + "deleted": 0 + }, + "orphan": false, + "merge_conflicts": false + }, + "display": { + "state": "diverged", + "symbols": "↕", + "statusline": "feature-c \u001b[2m↕\u001b[22m \u001b[32m↑1\u001b[0m \u001b[2m\u001b[31m↓1\u001b[0m ^\u001b[32m+1\u001b[0m" + } + }, + { + "branch": "with-status", + "head": { + "sha": "33323bc15e5a41f2dff1a997b9cd2345d6b74871", + "short_sha": "33323bc", + "subject": "Initial commit", + "committed_at": "2025-01-01T00:00:00Z" + }, + "worktree": { + "path": "_REPO_.with-status", + "main": false, + "current": false, + "previous": false, + "detached": false, + "branch_mismatch": false, + "duplicate_branch": false, + "changes": { + "staged": false, + "modified": false, + "untracked": false, + "renamed": false, + "deleted": false, + "conflicted": false, + "diff": { + "added": 0, + "deleted": 0 + } + } + }, + "default_branch": { + "ahead": 0, + "behind": 0, + "diff": { + "added": 0, + "deleted": 0 + }, + "orphan": false, + "integration": { + "reason": "same_commit" + }, + "merge_conflicts": false + }, + "marker": "🔧", + "display": { + "state": "empty", + "symbols": "_🔧", + "statusline": "with-status \u001b[2m_\u001b[22m🔧" + } + }, + { + "branch": "without-status", + "head": { + "sha": "33323bc15e5a41f2dff1a997b9cd2345d6b74871", + "short_sha": "33323bc", + "subject": "Initial commit", + "committed_at": "2025-01-01T00:00:00Z" + }, + "worktree": { + "path": "_REPO_.without-status", + "main": false, + "current": false, + "previous": false, + "detached": false, + "branch_mismatch": false, + "duplicate_branch": false, + "changes": { + "staged": false, + "modified": false, + "untracked": false, + "renamed": false, + "deleted": false, + "conflicted": false, + "diff": { + "added": 0, + "deleted": 0 + } + } + }, + "default_branch": { + "ahead": 0, + "behind": 0, + "diff": { + "added": 0, + "deleted": 0 + }, + "orphan": false, + "integration": { + "reason": "same_commit" + }, + "merge_conflicts": false + }, + "display": { + "state": "empty", + "symbols": "_", + "statusline": "without-status \u001b[2m_\u001b[22m" + } + } + ] +} + +----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_json_with_user_marker.snap b/tests/snapshots/integration__integration_tests__list__list_json_with_user_marker.snap index 828fefd8ad..77835ff446 100644 --- a/tests/snapshots/integration__integration_tests__list__list_json_with_user_marker.snap +++ b/tests/snapshots/integration__integration_tests__list__list_json_with_user_marker.snap @@ -10,10 +10,20 @@ info: CLAUDE_CONFIG_DIR: "[TEST_CLAUDE_CONFIG]" CLICOLOR_FORCE: "1" COLUMNS: "500" + GIT_ALLOW_PROTOCOL: file GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" - GIT_CONFIG_GLOBAL: "[TEST_GIT_CONFIG]" - GIT_CONFIG_SYSTEM: /dev/null + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" GIT_TERMINAL_PROMPT: "0" HOME: "[TEST_HOME]" LANG: C @@ -36,8 +46,10 @@ info: WORKTRUNK_TEST_MOCK_CONFIG_DIR: "[TEST_MOCK_CONFIG]" WORKTRUNK_TEST_NUSHELL_ENV: "0" WORKTRUNK_TEST_OPENCODE_INSTALLED: "0" + WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -237,7 +249,8 @@ exit_code: 0 "is_current": false, "is_previous": false, "statusline": "with-status \u001b[2m_\u001b[22m🔧", - "symbols": "_🔧" + "symbols": "_🔧", + "marker": "🔧" }, { "branch": "without-status", diff --git a/tests/snapshots/integration__integration_tests__list_layout__width_120.snap b/tests/snapshots/integration__integration_tests__list_layout__width_120.snap index b910bdeee1..694c046121 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__width_120.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__width_120.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -64,4 +65,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 5 worktrees, 1 with changes, 3 ahead, 1 column hidden +○ Showing 5 worktrees, 1 with changes, 3 ahead, hidden: Path diff --git a/tests/snapshots/integration__integration_tests__list_layout__width_60.snap b/tests/snapshots/integration__integration_tests__list_layout__width_60.snap index 043ea1ac06..a71e18dce5 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__width_60.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__width_60.snap @@ -11,9 +11,18 @@ info: COLUMNS: "60" GIT_ALLOW_PROTOCOL: file GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" - GIT_CONFIG_GLOBAL: "[TEST_GIT_CONFIG]" - GIT_CONFIG_SYSTEM: /dev/null + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" GIT_TERMINAL_PROMPT: "0" HOME: "[TEST_HOME]" LANG: C @@ -39,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,4 +65,4 @@ exit_code: 0 ----- stderr ----- -○ Showing 5 worktrees, 1 with changes, 3 ahead, 7 columns hidden +○ Showing 5 worktrees, 1 with changes, 3 ahead, hidden: main↕, main…±, Remote⇅, Path, Commit, Age, Message From 97e420e7a72369551ee2306b1ed284b28d708632 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 1 Sep 2026 16:44:35 -0700 Subject: [PATCH 04/17] list: cap the Branch column at 32 characters (#89) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Branch column had no width cap, so one 58-character branch sized it for every row: at 60 columns the table degenerated into a branch list with nine columns hidden, and at 200 it still lost Message. The column now sizes to min(longest, 32) and elides past that with `…`, the way Message already does; `--format=json` still carries the whole name. The skeleton row truncates the same way — it previously padded without truncating, which overflowed into the next column once Branch could be narrower than its content. New `one_long_branch_does_not_size_the_table` snapshots the result at 60, 100, and 200 columns. Co-Authored-By: Claude Fable 5.1 --- docs/src/content/docs/list.md | 2 +- .../skills/worktrunk/reference/list.md | 2 +- skills/worktrunk/reference/list.md | 2 +- src/cli/mod.rs | 2 +- src/commands/list/layout.rs | 11 ++- src/commands/list/render.rs | 5 ++ tests/integration_tests/list_layout.rs | 13 ++++ ...tegration_tests__help__help_list_long.snap | 32 ++++----- ...tion_tests__help__help_list_narrow_80.snap | 5 +- ...s__list_layout__long_branch_width_100.snap | 68 +++++++++++++++++++ ...s__list_layout__long_branch_width_200.snap | 68 +++++++++++++++++++ ...ts__list_layout__long_branch_width_60.snap | 68 +++++++++++++++++++ ...gration_tests__list_layout__width_120.snap | 12 ++-- ...egration_tests__list_layout__width_60.snap | 12 ++-- 14 files changed, 267 insertions(+), 35 deletions(-) create mode 100644 tests/snapshots/integration__integration_tests__list_layout__long_branch_width_100.snap create mode 100644 tests/snapshots/integration__integration_tests__list_layout__long_branch_width_200.snap create mode 100644 tests/snapshots/integration__integration_tests__list_layout__long_branch_width_60.snap diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index 5670d0508d..a51cb0def9 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -76,7 +76,7 @@ $ wt list --format=json | Column | Shows | |--------|-------| -| Branch | Branch name; a detached worktree has none, so it shows its short hash in dim yellow | +| Branch | Branch name, elided with `…` past 32 characters so one long name can't size the column for every row (`--format=json` keeps it whole); a detached worktree has none, so it shows its short hash in dim yellow | | Status | Compact symbols (see below) | | HEAD± | Uncommitted changes, including untracked files: +added -deleted lines | | main↕ | Commits ahead/behind default branch | diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 306d95f1cf..80dcabb492 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -63,7 +63,7 @@ $ wt list --format=json | Column | Shows | |--------|-------| -| Branch | Branch name; a detached worktree has none, so it shows its short hash in dim yellow | +| Branch | Branch name, elided with `…` past 32 characters so one long name can't size the column for every row (`--format=json` keeps it whole); a detached worktree has none, so it shows its short hash in dim yellow | | Status | Compact symbols (see below) | | HEAD± | Uncommitted changes, including untracked files: +added -deleted lines | | main↕ | Commits ahead/behind default branch | diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 306d95f1cf..80dcabb492 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -63,7 +63,7 @@ $ wt list --format=json | Column | Shows | |--------|-------| -| Branch | Branch name; a detached worktree has none, so it shows its short hash in dim yellow | +| Branch | Branch name, elided with `…` past 32 characters so one long name can't size the column for every row (`--format=json` keeps it whole); a detached worktree has none, so it shows its short hash in dim yellow | | Status | Compact symbols (see below) | | HEAD± | Uncommitted changes, including untracked files: +added -deleted lines | | main↕ | Commits ahead/behind default branch | diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 9f35f4d9db..eecc4161b8 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -844,7 +844,7 @@ $ wt list --format=json | Column | Shows | |--------|-------| -| Branch | Branch name; a detached worktree has none, so it shows its short hash in dim yellow | +| Branch | Branch name, elided with `…` past 32 characters so one long name can't size the column for every row (`--format=json` keeps it whole); a detached worktree has none, so it shows its short hash in dim yellow | | Status | Compact symbols (see below) | | HEAD± | Uncommitted changes, including untracked files: +added -deleted lines | | main↕ | Commits ahead/behind default branch | diff --git a/src/commands/list/layout.rs b/src/commands/list/layout.rs index 6eaf80b702..ace8869c35 100644 --- a/src/commands/list/layout.rs +++ b/src/commands/list/layout.rs @@ -307,6 +307,14 @@ pub struct LayoutMetadata { const EMPTY_PENALTY: u8 = 10; +/// Widest the Branch column grows to fit its longest name. +/// +/// Without a cap one 58-character branch sizes the column for every row, and +/// at a narrow width the table degenerates into a branch list with everything +/// else dropped. Past this the name is elided with `…`, the way Message +/// already is; `--format=json` keeps the full name either way. +const MAX_BRANCH: usize = 32; + #[derive(Clone, Copy, Debug)] pub struct DiffDisplayConfig { pub variant: DiffVariant, @@ -1217,7 +1225,8 @@ pub fn calculate_layout_with_width( .into_iter() .chain(detached_width) .max() - .unwrap_or(0); + .unwrap_or(0) + .min(MAX_BRANCH); let max_branch = fit_header(ColumnKind::Branch.header(), max_branch); let path_data_width = items diff --git a/src/commands/list/render.rs b/src/commands/list/render.rs index 2b4670bc0c..d84214622b 100644 --- a/src/commands/list/render.rs +++ b/src/commands/list/render.rs @@ -325,7 +325,12 @@ impl LayoutConfig { } else { cell.push_raw(branch.to_string()); } + // Elide past the column's cap, matching the settled row — + // `render_text_cell` truncates there, and a skeleton that + // didn't would overflow into the next column. + let mut cell = cell.truncate_to_width(col.width); cell.pad_to(col.width); + return cell; } ColumnKind::Path => { // Show actual path (no dim - start normal, gray out later if removable) diff --git a/tests/integration_tests/list_layout.rs b/tests/integration_tests/list_layout.rs index 0ef43b7e09..4e56b13333 100644 --- a/tests/integration_tests/list_layout.rs +++ b/tests/integration_tests/list_layout.rs @@ -41,3 +41,16 @@ fn representative_widths_render_useful_tables(mut repo: TestRepo) { snapshot_list(&repo, &format!("width_{width}"), width); } } + +/// One very long branch must not size the Branch column for every row: past +/// the cap the name is elided, so the columns that answer "what is going on +/// here" still fit. Without it, 60 columns degenerates into a branch list and +/// 200 still loses Message. +#[rstest] +fn one_long_branch_does_not_size_the_table(mut repo: TestRepo) { + repo.add_worktree("a-very-long-branch-name-that-is-fifty-eight-chars-long-ok"); + + for width in [60, 100, 200] { + snapshot_list(&repo, &format!("long_branch_width_{width}"), width); + } +} diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 4fa776a70c..1ccf64417e 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -141,22 +141,22 @@ Output as JSON for scripting: Columns - Column Shows - ──────── ──────────────────────────────────────────────────────────────────────────────────── - Branch Branch name; a detached worktree has none, so it shows its short hash in dim yellow - Status Compact symbols (see below) - HEAD± Uncommitted changes, including untracked files: +added -deleted lines - main↕ Commits ahead/behind default branch - main…± Line diffs since the merge-base (three-dot) with the default branch - Summary LLM-generated branch summary; requires --full, summary = true, and commit.generation - Remote⇅ Commits ahead/behind tracking branch - CI PR/MR number colored by pipeline status; --full only - Path Worktree directory - URL Dev server URL from project config; dimmed if port is not listening - (custom) User-defined custom columns from [list.custom-columns] user config [experimental] - Commit Short hash, abbreviated per core.abbrev - Age Time since last commit - Message Last commit message (truncated) + Column Shows + ──────── ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── + Branch Branch name, elided with … past 32 characters so one long name can't size the column for every row (--format=json keeps it whole); a detached worktree has none, so it shows its short hash in dim yellow + Status Compact symbols (see below) + HEAD± Uncommitted changes, including untracked files: +added -deleted lines + main↕ Commits ahead/behind default branch + main…± Line diffs since the merge-base (three-dot) with the default branch + Summary LLM-generated branch summary; requires --full, summary = true, and commit.generation + Remote⇅ Commits ahead/behind tracking branch + CI PR/MR number colored by pipeline status; --full only + Path Worktree directory + URL Dev server URL from project config; dimmed if port is not listening + (custom) User-defined custom columns from [list.custom-columns] user config [experimental] + Commit Short hash, abbreviated per core.abbrev + Age Time since last commit + Message Last commit message (truncated) The main header label is used regardless of the default branch's actual name. diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 24a70e93e3..9924d3991f 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -154,8 +154,9 @@ Output as JSON for scripting: Column Shows ──────── ───────────────────────────────────────────────────────────────────── - Branch Branch name; a detached worktree has none, so it shows its short hash - in dim yellow + Branch Branch name, elided with … past 32 characters so one long name can't + size the column for every row (--format=json keeps it whole); a + detached worktree has none, so it shows its short hash in dim yellow Status Compact symbols (see below) HEAD± Uncommitted changes, including untracked files: +added -deleted lines main↕ Commits ahead/behind default branch diff --git a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_100.snap b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_100.snap new file mode 100644 index 0000000000..f740a04792 --- /dev/null +++ b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_100.snap @@ -0,0 +1,68 @@ +--- +source: tests/integration_tests/list_layout.rs +info: + program: wt + args: + - list + env: + APPDATA: "[TEST_CONFIG_HOME]" + CLAUDE_CONFIG_DIR: "[TEST_CLAUDE_CONFIG]" + CLICOLOR_FORCE: "1" + COLUMNS: "100" + GIT_ALLOW_PROTOCOL: file + GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User + GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" + GIT_TERMINAL_PROMPT: "0" + HOME: "[TEST_HOME]" + LANG: C + LC_ALL: C + LLVM_PROFILE_FILE: "[LLVM_PROFILE_FILE]" + OPENCODE_CONFIG_DIR: "[TEST_OPENCODE_CONFIG]" + PATH: "[PATH]" + TERM: alacritty + USERPROFILE: "[TEST_HOME]" + WORKTRUNK_APPROVALS_PATH: "[TEST_APPROVALS]" + WORKTRUNK_CONFIG_PATH: "[TEST_CONFIG]" + WORKTRUNK_SYSTEM_CONFIG_PATH: "[TEST_SYSTEM_CONFIG]" + WORKTRUNK_TEST_BASH_INSTALLED: "0" + WORKTRUNK_TEST_CLAUDE_INSTALLED: "0" + WORKTRUNK_TEST_CODEX_INSTALLED: "0" + WORKTRUNK_TEST_DELAYED_STREAM_MS: "-1" + WORKTRUNK_TEST_EPOCH: "1735776000" + WORKTRUNK_TEST_FISH_INSTALLED: "0" + WORKTRUNK_TEST_GEMINI_INSTALLED: "0" + WORKTRUNK_TEST_MOCK_CONFIG_DIR: "[TEST_MOCK_CONFIG]" + WORKTRUNK_TEST_NUSHELL_ENV: "0" + WORKTRUNK_TEST_OPENCODE_INSTALLED: "0" + WORKTRUNK_TEST_PARENT_SHELL: "" + WORKTRUNK_TEST_POWERSHELL_ENV: "0" + WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" + WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" + WORKTRUNK_TEST_ZSH_INSTALLED: "0" + XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" +--- +success: true +exit_code: 0 +----- stdout ----- + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age +@ main ^| | 05a4a45 16h ++ a-very-long-branch-name-that-is… _ 05a4a45 16h ++ feature-a ↑ ↑1 +1 1b87d47 16h ++ feature-b ↑ ↑1 +1 f62940f 16h ++ feature-c ↑ ↑1 +1 345c7c9 16h + +----- stderr ----- + +○ Showing 5 worktrees, 3 ahead, hidden: Path, Message diff --git a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_200.snap b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_200.snap new file mode 100644 index 0000000000..9b06bf05e9 --- /dev/null +++ b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_200.snap @@ -0,0 +1,68 @@ +--- +source: tests/integration_tests/list_layout.rs +info: + program: wt + args: + - list + env: + APPDATA: "[TEST_CONFIG_HOME]" + CLAUDE_CONFIG_DIR: "[TEST_CLAUDE_CONFIG]" + CLICOLOR_FORCE: "1" + COLUMNS: "200" + GIT_ALLOW_PROTOCOL: file + GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User + GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" + GIT_TERMINAL_PROMPT: "0" + HOME: "[TEST_HOME]" + LANG: C + LC_ALL: C + LLVM_PROFILE_FILE: "[LLVM_PROFILE_FILE]" + OPENCODE_CONFIG_DIR: "[TEST_OPENCODE_CONFIG]" + PATH: "[PATH]" + TERM: alacritty + USERPROFILE: "[TEST_HOME]" + WORKTRUNK_APPROVALS_PATH: "[TEST_APPROVALS]" + WORKTRUNK_CONFIG_PATH: "[TEST_CONFIG]" + WORKTRUNK_SYSTEM_CONFIG_PATH: "[TEST_SYSTEM_CONFIG]" + WORKTRUNK_TEST_BASH_INSTALLED: "0" + WORKTRUNK_TEST_CLAUDE_INSTALLED: "0" + WORKTRUNK_TEST_CODEX_INSTALLED: "0" + WORKTRUNK_TEST_DELAYED_STREAM_MS: "-1" + WORKTRUNK_TEST_EPOCH: "1735776000" + WORKTRUNK_TEST_FISH_INSTALLED: "0" + WORKTRUNK_TEST_GEMINI_INSTALLED: "0" + WORKTRUNK_TEST_MOCK_CONFIG_DIR: "[TEST_MOCK_CONFIG]" + WORKTRUNK_TEST_NUSHELL_ENV: "0" + WORKTRUNK_TEST_OPENCODE_INSTALLED: "0" + WORKTRUNK_TEST_PARENT_SHELL: "" + WORKTRUNK_TEST_POWERSHELL_ENV: "0" + WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" + WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" + WORKTRUNK_TEST_ZSH_INSTALLED: "0" + XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" +--- +success: true +exit_code: 0 +----- stdout ----- + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ a-very-long-branch-name-that-is… _ ../repo.a-very-long-branch-name-that-is-fifty-eight-chars-long-ok 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + +----- stderr ----- + +○ Showing 5 worktrees, 3 ahead diff --git a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_60.snap b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_60.snap new file mode 100644 index 0000000000..b4a0128e14 --- /dev/null +++ b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_60.snap @@ -0,0 +1,68 @@ +--- +source: tests/integration_tests/list_layout.rs +info: + program: wt + args: + - list + env: + APPDATA: "[TEST_CONFIG_HOME]" + CLAUDE_CONFIG_DIR: "[TEST_CLAUDE_CONFIG]" + CLICOLOR_FORCE: "1" + COLUMNS: "60" + GIT_ALLOW_PROTOCOL: file + GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User + GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" + GIT_TERMINAL_PROMPT: "0" + HOME: "[TEST_HOME]" + LANG: C + LC_ALL: C + LLVM_PROFILE_FILE: "[LLVM_PROFILE_FILE]" + OPENCODE_CONFIG_DIR: "[TEST_OPENCODE_CONFIG]" + PATH: "[PATH]" + TERM: alacritty + USERPROFILE: "[TEST_HOME]" + WORKTRUNK_APPROVALS_PATH: "[TEST_APPROVALS]" + WORKTRUNK_CONFIG_PATH: "[TEST_CONFIG]" + WORKTRUNK_SYSTEM_CONFIG_PATH: "[TEST_SYSTEM_CONFIG]" + WORKTRUNK_TEST_BASH_INSTALLED: "0" + WORKTRUNK_TEST_CLAUDE_INSTALLED: "0" + WORKTRUNK_TEST_CODEX_INSTALLED: "0" + WORKTRUNK_TEST_DELAYED_STREAM_MS: "-1" + WORKTRUNK_TEST_EPOCH: "1735776000" + WORKTRUNK_TEST_FISH_INSTALLED: "0" + WORKTRUNK_TEST_GEMINI_INSTALLED: "0" + WORKTRUNK_TEST_MOCK_CONFIG_DIR: "[TEST_MOCK_CONFIG]" + WORKTRUNK_TEST_NUSHELL_ENV: "0" + WORKTRUNK_TEST_OPENCODE_INSTALLED: "0" + WORKTRUNK_TEST_PARENT_SHELL: "" + WORKTRUNK_TEST_POWERSHELL_ENV: "0" + WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" + WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" + WORKTRUNK_TEST_ZSH_INSTALLED: "0" + XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" +--- +success: true +exit_code: 0 +----- stdout ----- + Branch Status HEAD± +@ main ^| ++ a-very-long-branch-name-that-is… _ ++ feature-a ↑ ++ feature-b ↑ ++ feature-c ↑ + +----- stderr ----- + +○ Showing 5 worktrees, 3 ahead, hidden: main↕, main…±, Remote⇅, Path, Commit, Age, Message diff --git a/tests/snapshots/integration__integration_tests__list_layout__width_120.snap b/tests/snapshots/integration__integration_tests__list_layout__width_120.snap index 694c046121..1cb9a9523b 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__width_120.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__width_120.snap @@ -56,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ main ^| | 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 1b87d47 16h Add feature-a… -+ feature-b ↑ ↑1 +1 f62940f 16h Add feature-b… -+ feature-c ↑ ↑1 +1 345c7c9 16h Add feature-c… -+ feature/implement-oauth2-social-login ? – +1 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ main ^| | 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 345c7c9 16h Add feature-c file ++ feature/implement-oauth2-social… ? – +1 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_layout__width_60.snap b/tests/snapshots/integration__integration_tests__list_layout__width_60.snap index a71e18dce5..13b8cfb697 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__width_60.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__width_60.snap @@ -56,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± -@ main ^| -+ feature-a ↑ -+ feature-b ↑ -+ feature-c ↑ -+ feature/implement-oauth2-social-login ? – +1 + Branch Status HEAD± +@ main ^| ++ feature-a ↑ ++ feature-b ↑ ++ feature-c ↑ ++ feature/implement-oauth2-social… ? – +1 ----- stderr ----- From 5a573bf5ad61d348930396e70c88ec8abda1903c Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 1 Sep 2026 18:42:18 -0700 Subject: [PATCH 05/17] list: drop an all-empty column before any populated one (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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. Two things let that happen: the empty penalty (+10) was smaller than the largest base priority, so an empty column could still outrank a populated one, and the allocation loop kept going after a column failed to fit, letting a narrow blank column slip into the gap a wider populated one had just failed to fill. The penalty now exceeds every base priority, and the loop records the first populated drop and skips every empty column after it. `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. `RepoFacts` collects what the layout needs and the items can't answer at skeleton time — the remote, the URL template, the cached PR number — in place of three positional arguments. The help text claimed "a column that renders empty for every row is dropped", which was only ever true of custom columns; it now says what each kind does. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BGS51k25qzfeJEJ8dfxEEY --- docs/src/content/docs/list.md | 4 +- .../skills/worktrunk/reference/list.md | 4 +- skills/worktrunk/reference/list.md | 4 +- src/cli/mod.rs | 4 +- src/commands/list/collect/mod.rs | 17 +- src/commands/list/layout.rs | 191 ++++++++++++------ src/commands/picker/mod.rs | 14 +- tests/integration_tests/list_layout.rs | 12 ++ ...tegration_tests__help__help_list_long.snap | 5 +- ...tion_tests__help__help_list_narrow_80.snap | 11 +- ...sts__list_layout__no_remote_width_100.snap | 68 +++++++ 11 files changed, 250 insertions(+), 84 deletions(-) create mode 100644 tests/snapshots/integration__integration_tests__list_layout__no_remote_width_100.snap diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index a51cb0def9..097c835d57 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -93,7 +93,7 @@ $ wt list --format=json The `main` header label is used regardless of the default branch's actual name. -The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. `main↕` and `main…±` measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local `main` trails `origin/main`, a branch reads as ahead of the real mainline, not of a stale local checkout. The `↑`/`↓`/`↕` Status symbols derive from these counts, so they track the upstream tip too. @@ -146,7 +146,7 @@ Each `[list.custom-columns]` entry in user config adds a column: the key is the template = "{{ vars.ticket }}" ``` -A column that renders empty for every row is dropped from the table. Templates, widths, and drop priority: [custom columns config](/config/#custom-columns). +A custom column that renders empty for every row is dropped from the table outright, rather than merely ranked last as an empty built-in is. Templates, widths, and drop priority: [custom columns config](/config/#custom-columns). ## Status symbols diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 80dcabb492..b707ee0168 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -80,7 +80,7 @@ $ wt list --format=json The `main` header label is used regardless of the default branch's actual name. -The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. `main↕` and `main…±` measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local `main` trails `origin/main`, a branch reads as ahead of the real mainline, not of a stale local checkout. The `↑`/`↓`/`↕` Status symbols derive from these counts, so they track the upstream tip too. @@ -131,7 +131,7 @@ Each `[list.custom-columns]` entry in user config adds a column: the key is the template = "{{ vars.ticket }}" ``` -A column that renders empty for every row is dropped from the table. Templates, widths, and drop priority: [custom columns config](https://worktrunk.dev/config/#custom-columns). +A custom column that renders empty for every row is dropped from the table outright, rather than merely ranked last as an empty built-in is. Templates, widths, and drop priority: [custom columns config](https://worktrunk.dev/config/#custom-columns). ## Status symbols diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 80dcabb492..b707ee0168 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -80,7 +80,7 @@ $ wt list --format=json The `main` header label is used regardless of the default branch's actual name. -The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. `main↕` and `main…±` measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local `main` trails `origin/main`, a branch reads as ahead of the real mainline, not of a stale local checkout. The `↑`/`↓`/`↕` Status symbols derive from these counts, so they track the upstream tip too. @@ -131,7 +131,7 @@ Each `[list.custom-columns]` entry in user config adds a column: the key is the template = "{{ vars.ticket }}" ``` -A column that renders empty for every row is dropped from the table. Templates, widths, and drop priority: [custom columns config](https://worktrunk.dev/config/#custom-columns). +A custom column that renders empty for every row is dropped from the table outright, rather than merely ranked last as an empty built-in is. Templates, widths, and drop priority: [custom columns config](https://worktrunk.dev/config/#custom-columns). ## Status symbols diff --git a/src/cli/mod.rs b/src/cli/mod.rs index eecc4161b8..6f9b4ba3aa 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -861,7 +861,7 @@ $ wt list --format=json The `main` header label is used regardless of the default branch's actual name. -The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. `main↕` and `main…±` measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local `main` trails `origin/main`, a branch reads as ahead of the real mainline, not of a stale local checkout. The `↑`/`↓`/`↕` Status symbols derive from these counts, so they track the upstream tip too. @@ -912,7 +912,7 @@ Each `[list.custom-columns]` entry in user config adds a column: the key is the template = "{{ vars.ticket }}" ``` -A column that renders empty for every row is dropped from the table. Templates, widths, and drop priority: [custom columns config](/config/#custom-columns). +A custom column that renders empty for every row is dropped from the table outright, rather than merely ranked last as an empty built-in is. Templates, widths, and drop priority: [custom columns config](/config/#custom-columns). ## Status symbols diff --git a/src/commands/list/collect/mod.rs b/src/commands/list/collect/mod.rs index 68ffda0d0c..6fed0a742f 100644 --- a/src/commands/list/collect/mod.rs +++ b/src/commands/list/collect/mod.rs @@ -1311,12 +1311,18 @@ pub fn collect( &tasks, destination, &main_worktree.path, - url_template.as_deref(), - max_pr_number, super::layout::ColumnSelection { custom: &custom_columns, selected: (!selected_columns.is_empty()).then_some(selected_columns.as_slice()), }, + super::layout::RepoFacts { + // O(1) off the bulk config map, no fork. With no remote no branch + // can track one, so `Remote⇅` drops instead of holding a blank + // column open ahead of Path, Commit, Age and Message. + has_remote: repo.primary_remote().is_ok(), + url_template: url_template.as_deref(), + max_pr_number, + }, ); // Single-line invariant: with no detectable width, an unlimited width @@ -2550,12 +2556,15 @@ remove the file manually to continue."; link_style: LinkStyle::Expanded, }, Path::new("/tmp"), - None, - None, super::super::layout::ColumnSelection { custom: &[], selected: None, }, + super::super::layout::RepoFacts { + has_remote: true, + url_template: None, + max_pr_number: None, + }, ); let placeholder = super::super::render::PLACEHOLDER; diff --git a/src/commands/list/layout.rs b/src/commands/list/layout.rs index ace8869c35..ead2cc7a07 100644 --- a/src/commands/list/layout.rs +++ b/src/commands/list/layout.rs @@ -62,17 +62,24 @@ //! - 5-12: Context (CI, branch diff, path, upstream, URL, summary, commit, time) //! - 13: Message (nice-to-have, space-hungry) //! -//! **Empty penalty**: +10 if column has no data (only header) -//! - Empty working_diff: 3 + 10 = priority 13 -//! - Empty ahead/behind: 4 + 10 = priority 14 +//! **Empty penalty**: +14 if column has no data (only header) +//! - Empty working_diff: 3 + 14 = priority 17 +//! - Empty ahead/behind: 4 + 14 = priority 18 //! - etc. //! //! This creates two effective priority tiers: //! - **Tier 1 (priorities 0-13)**: Columns with actual data -//! - **Tier 2 (priorities 13-23)**: Empty columns (visual consistency) +//! - **Tier 2 (priorities 14-27)**: Empty columns (visual consistency) //! -//! The empty penalty is large (+10) but not infinite, so empty columns maintain their relative -//! ordering (empty working_diff still ranks higher than empty ci_status) for visual consistency. +//! The penalty exceeds the largest base priority, so the tiers can't interleave: an +//! all-empty column never outranks a populated one. It is not infinite, so empty columns +//! keep their relative ordering (empty working_diff still ranks higher than empty +//! ci_status) for visual consistency. +//! +//! Priority order alone doesn't finish the job, because the allocation loop keeps going +//! after a column fails to fit: a narrow blank column would slip into the gap a wider +//! populated one just failed to fill. So the loop also records whether any populated +//! column was dropped, and skips every empty column once that happens. //! //! ## Why This Design? //! @@ -83,8 +90,8 @@ //! 2. Show nice-to-have data (message, commit hash) when space allows //! 3. Maintain visual consistency - empty columns in predictable positions at wide widths //! -//! **Key decision**: Message sits at the boundary (priority 13). Empty columns (priority 13+) -//! rank below message, so: +//! **Key decision**: Message sits at the bottom of tier 1 (priority 13). Empty columns +//! (priority 14+) rank below it, so: //! - Narrow terminals: Data columns + message (hide empty columns) //! - Wide terminals: Data columns + message + empty columns (visual consistency) //! @@ -94,14 +101,16 @@ //! computes layout before data arrives. Currently we assume most columns have data (optimistic), //! which means empty penalties don't apply in progressive mode. //! -//! Exceptions that we can compute instantly from items: +//! Exceptions we can settle before any task reports: //! - `path`: true only if some worktree's path carries information the branch //! column doesn't — `branch_worktree_mismatch` or `duplicate_branch` (computed //! from items) -//! - `branch_diff`/`ci_status`: false if their required task is skipped +//! - `branch_diff`/`ci_status`/`url`: false if their required task is skipped +//! - `upstream`: false when the repo has no remote ([`RepoFacts::has_remote`]), +//! because then no branch can track one //! -//! Other columns (status, working_diff, ahead_behind, upstream) require expensive git operations, -//! so we assume they have data until proven otherwise. +//! The rest (status, working_diff, ahead_behind) require expensive git operations, so we +//! assume they have data until proven otherwise. //! //! ## Special Cases //! @@ -284,6 +293,28 @@ pub struct ColumnWidths { pub custom: Vec, } +/// Facts about the repository that the layout needs and the items can't +/// answer at skeleton time, when no task has reported yet. +#[derive(Clone, Copy, Debug)] +pub struct RepoFacts<'a> { + /// The repository has at least one remote configured, so a branch can + /// have an upstream. With none, `Remote⇅` is blank on every row, and + /// saying so here is what makes it drop before a populated column + /// instead of surviving one. Read from the bulk config map (O(1), no + /// fork) — see [`Repository::primary_remote`]. + /// + /// [`Repository::primary_remote`]: worktrunk::git::Repository::primary_remote + pub has_remote: bool, + + /// The project's `list.url` template, which sizes the URL column; `None` + /// when none is configured, and then the column renders nothing. + pub url_template: Option<&'a str>, + + /// Largest PR/MR number any previous fetch cached, which sizes the CI + /// column before this run's fetch reports. + pub max_pr_number: Option, +} + /// Tracks which columns have actual data (vs just headers) #[derive(Clone, Copy, Debug)] pub struct ColumnDataFlags { @@ -305,7 +336,13 @@ pub struct LayoutMetadata { pub status_position_mask: super::model::PositionMask, } -const EMPTY_PENALTY: u8 = 10; +/// Added to a column's base priority when it has nothing to show on any row. +/// +/// Larger than the largest base priority, so an all-empty column ranks below +/// every populated one — Message (13) survives a blank `Remote⇅`, not the +/// other way round. Empty columns keep their relative order among themselves, +/// so a wide terminal still lays them out predictably. +const EMPTY_PENALTY: u8 = 14; /// Widest the Branch column grows to fit its longest name. /// @@ -759,8 +796,8 @@ fn build_estimated_widths( tasks: &HashSet, path_is_informative: bool, url_width: usize, - max_pr_number: Option, custom_widths: Vec, + facts: RepoFacts<'_>, ) -> LayoutMetadata { // Fixed widths for slow columns (require expensive git operations) // Values exceeding these widths use compact notation (K suffix) @@ -785,22 +822,23 @@ fn build_estimated_widths( // the ratcheted cache sizes the next invocation correctly. let ci_estimate = fit_header( ColumnKind::CiStatus.header(), - super::ci_status::pr_ref_width(max_pr_number.unwrap_or(9999)), + super::ci_status::pr_ref_width(facts.max_pr_number.unwrap_or(9999)), ); // Assume columns will have data (better to show and hide than to not show). // This is a limitation of progressive mode - we can't know which columns have data // before the data arrives, so empty penalties don't apply properly. // - // Exceptions that we can compute instantly from items: + // Exceptions we can settle before any task reports: // - path: true only if a worktree is off-template or shares its branch - // - branch_diff/ci_status: false if their task isn't in the run plan + // - branch_diff/ci_status/url: false if their task isn't in the run plan + // - upstream: false when the repo has no remote, so no branch can track one let data_flags = ColumnDataFlags { status: true, working_diff: true, ahead_behind: true, branch_diff: tasks.contains(&TaskKind::BranchDiff), - upstream: true, + upstream: facts.has_remote, url: tasks.contains(&TaskKind::UrlStatus), ci_status: tasks.contains(&TaskKind::CiStatus), path: path_is_informative, @@ -974,9 +1012,21 @@ fn allocate_columns_with_priority( true }; + // Whether a column that has something to show was left out. Priority order + // alone doesn't settle "an all-empty column never outranks a populated + // one": the loop keeps going after a column doesn't fit, so a narrow blank + // `Remote⇅` would otherwise slip into the gap a wider Message just failed + // to fill. Every empty candidate sorts after every populated one + // (EMPTY_PENALTY exceeds the largest base priority), so one flag covers it. + let mut dropped_populated = false; + // Allocate columns in priority order for candidate in candidates { let spec = candidate.spec; + let is_empty_column = !spec.kind.has_data(&metadata.data_flags); + if is_empty_column && dropped_populated { + continue; + } // Flexible columns: allocate at minimum, expand post-loop if matches!(spec.kind, ColumnKind::Summary | ColumnKind::Message) { @@ -985,6 +1035,7 @@ fn allocate_columns_with_priority( _ => MIN_MESSAGE, }; let spacing_cost = if needs_spacing(&pending) { spacing } else { 0 }; + let mut allocated = false; if remaining > spacing_cost { let available = remaining - spacing_cost; if available >= min_width { @@ -995,8 +1046,10 @@ fn allocate_columns_with_priority( width: min_width, format: ColumnFormat::Text, }); + allocated = true; } } + dropped_populated |= !allocated && !is_empty_column; continue; } @@ -1022,6 +1075,8 @@ fn allocate_columns_with_priority( width: allocated, format, }); + } else if !is_empty_column { + dropped_populated = true; } } @@ -1197,9 +1252,8 @@ pub fn calculate_layout_with_width( tasks: &HashSet, destination: Destination, main_worktree_path: &Path, - url_template: Option<&str>, - max_pr_number: Option, columns: ColumnSelection, + facts: RepoFacts<'_>, ) -> LayoutConfig { let link_style = destination.link_style; let custom_columns = columns.custom; @@ -1246,7 +1300,7 @@ pub fn calculate_layout_with_width( .any(|data| data.branch_worktree_mismatch || data.duplicate_branch); // Estimate URL width from template (heuristic, no expansion needed) - let url_width = estimate_url_width(url_template, link_style); + let url_width = estimate_url_width(facts.url_template, link_style); // Custom column widths are measured, not estimated: values were expanded // before layout. A column empty on every row stays 0 and is excluded. @@ -1274,8 +1328,8 @@ pub fn calculate_layout_with_width( tasks, path_is_informative, url_width, - max_pr_number, custom_widths, + facts, ); // Sized from the abbreviated SHAs themselves — `%h` is `core.abbrev` wide @@ -1306,6 +1360,17 @@ mod tests { use worktrunk::git::LineDiff; use worktrunk::styling::terminal_width; + /// The default repository context: a remote exists, no dev-server URL + /// template, and no cached PR/MR number. Tests that care about one of + /// these override it (`RepoFacts { has_remote: false, ..test_facts() }`). + fn test_facts() -> RepoFacts<'static> { + RepoFacts { + has_remote: true, + url_template: None, + max_pr_number: None, + } + } + #[test] fn test_fit_header() { // Data wider than header - return data width @@ -1557,7 +1622,8 @@ mod tests { // Full run plan means all tasks are computed (equivalent to --full) // path_is_informative=true to test the path flag is passed through // url_width=0 since we're not testing URL column here - let metadata = build_estimated_widths(20, &full_run_tasks(), true, 0, None, Vec::new()); + let metadata = + build_estimated_widths(20, &full_run_tasks(), true, 0, Vec::new(), test_facts()); let widths = metadata.widths; // Line diffs (Signs variant: +/-) allocate 3 digits for 100-999 range @@ -1627,12 +1693,31 @@ mod tests { #[test] fn test_ci_column_width_from_max_pr_number() { // Cached largest number sizes the column: "#12345" → 6 - let metadata = - build_estimated_widths(20, &full_run_tasks(), false, 0, Some(12345), Vec::new()); + let metadata = build_estimated_widths( + 20, + &full_run_tasks(), + false, + 0, + Vec::new(), + RepoFacts { + max_pr_number: Some(12345), + ..test_facts() + }, + ); assert_eq!(metadata.widths.ci_status, 6); // Never below header width ("CI" → 2) - let metadata = build_estimated_widths(20, &full_run_tasks(), false, 0, Some(1), Vec::new()); + let metadata = build_estimated_widths( + 20, + &full_run_tasks(), + false, + 0, + Vec::new(), + RepoFacts { + max_pr_number: Some(1), + ..test_facts() + }, + ); assert_eq!(metadata.widths.ci_status, 2); } @@ -1683,12 +1768,11 @@ mod tests { link_style: LinkStyle::Expanded, }, &main_worktree_path, - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ); assert!( @@ -1777,12 +1861,11 @@ mod tests { link_style: LinkStyle::Expanded, }, &main_worktree_path, - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ); assert!( @@ -1881,12 +1964,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ) } @@ -1927,12 +2009,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &[], selected: Some(&selected), }, + test_facts(), ); let kinds: Vec = layout.columns.iter().map(|c| c.kind).collect(); @@ -1989,12 +2070,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &custom, selected: Some(&selected), }, + test_facts(), ); let kinds: Vec = layout.columns.iter().map(|c| c.kind).collect(); @@ -2035,12 +2115,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &custom, selected: None, }, + test_facts(), ); assert!( find_column(&layout, ColumnKind::Custom(0)).is_some(), @@ -2073,12 +2152,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ); let branch = find_column(&layout, ColumnKind::Branch).expect("Branch allocated"); @@ -2111,12 +2189,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &[], selected: Some(&selected), }, + test_facts(), ); assert!( find_column(&unplanned, ColumnKind::CiStatus).is_none(), @@ -2134,12 +2211,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &[], selected: Some(&selected), }, + test_facts(), ); assert!( find_column(&planned, ColumnKind::CiStatus).is_some(), @@ -2162,12 +2238,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &[], selected: Some(&selected), }, + test_facts(), ); assert!( find_column(&layout, ColumnKind::Branch).is_some(), @@ -2406,12 +2481,11 @@ mod tests { link_style: LinkStyle::Expanded, }, main_path, - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ); assert!( find_column(&layout_wide, ColumnKind::Summary).is_some(), @@ -2433,12 +2507,11 @@ mod tests { link_style: LinkStyle::Expanded, }, main_path, - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ); let summary_170 = find_column(&layout_170, ColumnKind::Summary) .expect("Summary should be present at 170") @@ -2565,12 +2638,11 @@ mod tests { link_style: LinkStyle::Expanded, }, main_path, - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ); let mut lines = Vec::new(); @@ -2606,12 +2678,11 @@ mod tests { link_style: LinkStyle::Expanded, }, main_path, - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ); let branch = find_column(&layout, ColumnKind::Branch); assert!( @@ -2633,12 +2704,11 @@ mod tests { link_style: LinkStyle::Expanded, }, main_path, - None, - None, ColumnSelection { custom: &[], selected: None, }, + test_facts(), ); let branch = find_column(&layout, ColumnKind::Branch).unwrap(); assert!( @@ -2700,12 +2770,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &columns, selected: None, }, + test_facts(), ); // Value wider than max_width clamps to it @@ -2745,12 +2814,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &columns, selected: None, }, + test_facts(), ); // Values are final before layout, so an all-empty column is excluded @@ -2774,12 +2842,11 @@ mod tests { link_style: LinkStyle::Expanded, }, Path::new("/test"), - None, - None, ColumnSelection { custom: &columns, selected: None, }, + test_facts(), ); // Priority 9 loses to the core columns when space runs out, and the diff --git a/src/commands/picker/mod.rs b/src/commands/picker/mod.rs index 0be1bca6ff..070e4557dc 100644 --- a/src/commands/picker/mod.rs +++ b/src/commands/picker/mod.rs @@ -3247,12 +3247,15 @@ pub mod tests { link_style: crate::commands::list::layout::LinkStyle::Expanded, }, Path::new("/test"), - None, - None, crate::commands::list::layout::ColumnSelection { custom: &[], selected: None, }, + crate::commands::list::layout::RepoFacts { + has_remote: true, + url_template: None, + max_pr_number: None, + }, )); (row, token, rendered, morphed) } @@ -4227,12 +4230,15 @@ pub mod tests { link_style: crate::commands::list::layout::LinkStyle::Expanded, }, Path::new("/test"), - None, - None, crate::commands::list::layout::ColumnSelection { custom: &[], selected: None, }, + crate::commands::list::layout::RepoFacts { + has_remote: true, + url_template: None, + max_pr_number: None, + }, ); let (line, local) = diff --git a/tests/integration_tests/list_layout.rs b/tests/integration_tests/list_layout.rs index 4e56b13333..cda3f2eaca 100644 --- a/tests/integration_tests/list_layout.rs +++ b/tests/integration_tests/list_layout.rs @@ -42,6 +42,18 @@ fn representative_widths_render_useful_tables(mut repo: TestRepo) { } } +/// With no remote configured, `Remote⇅` is blank on every row, so it ranks +/// below every populated column and drops first. At this width it used to +/// hold its blank seven columns open while Message — which has something to +/// say on every row — was dropped for want of them. +#[rstest] +fn empty_remote_column_drops_before_populated_ones(mut repo: TestRepo) { + repo.run_git(&["remote", "remove", "origin"]); + repo.add_worktree("feature-with-a-longer-name"); + + snapshot_list(&repo, "no_remote_width_100", 100); +} + /// One very long branch must not size the Branch column for every row: past /// the cap the name is elided, so the columns that answer "what is going on /// here" still fit. Without it, 60 columns degenerates into a branch list and diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 1ccf64417e..694760f13b 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -160,7 +160,8 @@ Output as JSON for scripting: The main header label is used regardless of the default branch's actual name. -The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (hidden: Commit, Age, Message). A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [list] columns; --format=json carries every field at any width. +The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (hidden: Commit, Age, Message). A column with nothing to show on any row — Remote⇅ in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [list] +columns; --format=json carries every field at any width. main↕ and main…± measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local main trails origin/main, a branch reads as ahead of the real mainline, not of a stale local checkout. The ↑/↓/↕ Status symbols derive from these counts, so they track the upstream tip too. @@ -211,7 +212,7 @@ Each [list.custom-columns] entry in user config adds a column: the key i   [list.custom-columns.Ticket]   template = "{{ vars.ticket }}" -A column that renders empty for every row is dropped from the table. Templates, widths, and drop priority: custom columns config. +A custom column that renders empty for every row is dropped from the table outright, rather than merely ranked last as an empty built-in is. Templates, widths, and drop priority: custom columns config. Status symbols diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 9924d3991f..5a67f086cf 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -178,9 +178,11 @@ The main header label is used regardless of the default branch's actual The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (hidden: -Commit, Age, Message). A wider terminal brings them back. To pin a set rather -than leave it to the width, name the columns in [list] columns; --format=json -carries every field at any width. +Commit, Age, Message). A column with nothing to show on any row — Remote⇅ in a +repo with no remote — ranks below every populated column, so it goes first. A +wider terminal brings them back. To pin a set rather than leave it to the width, + name the columns in [list] columns; --format=json carries every field at any +width. main↕ and main…± measure against the default branch's upstream tip when the local copy lags it — so in a fork whose local main trails origin/main, a branch @@ -260,7 +262,8 @@ you set yourself, or the git-native description) — useful for tracking   [list.custom-columns.Ticket]   template = "{{ vars.ticket }}" -A column that renders empty for every row is dropped from the table. Templates, +A custom column that renders empty for every row is dropped from the table +outright, rather than merely ranked last as an empty built-in is. Templates, widths, and drop priority: custom columns config. Status symbols diff --git a/tests/snapshots/integration__integration_tests__list_layout__no_remote_width_100.snap b/tests/snapshots/integration__integration_tests__list_layout__no_remote_width_100.snap new file mode 100644 index 0000000000..273ec36415 --- /dev/null +++ b/tests/snapshots/integration__integration_tests__list_layout__no_remote_width_100.snap @@ -0,0 +1,68 @@ +--- +source: tests/integration_tests/list_layout.rs +info: + program: wt + args: + - list + env: + APPDATA: "[TEST_CONFIG_HOME]" + CLAUDE_CONFIG_DIR: "[TEST_CLAUDE_CONFIG]" + CLICOLOR_FORCE: "1" + COLUMNS: "100" + GIT_ALLOW_PROTOCOL: file + GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User + GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" + GIT_TERMINAL_PROMPT: "0" + HOME: "[TEST_HOME]" + LANG: C + LC_ALL: C + LLVM_PROFILE_FILE: "[LLVM_PROFILE_FILE]" + OPENCODE_CONFIG_DIR: "[TEST_OPENCODE_CONFIG]" + PATH: "[PATH]" + TERM: alacritty + USERPROFILE: "[TEST_HOME]" + WORKTRUNK_APPROVALS_PATH: "[TEST_APPROVALS]" + WORKTRUNK_CONFIG_PATH: "[TEST_CONFIG]" + WORKTRUNK_SYSTEM_CONFIG_PATH: "[TEST_SYSTEM_CONFIG]" + WORKTRUNK_TEST_BASH_INSTALLED: "0" + WORKTRUNK_TEST_CLAUDE_INSTALLED: "0" + WORKTRUNK_TEST_CODEX_INSTALLED: "0" + WORKTRUNK_TEST_DELAYED_STREAM_MS: "-1" + WORKTRUNK_TEST_EPOCH: "1735776000" + WORKTRUNK_TEST_FISH_INSTALLED: "0" + WORKTRUNK_TEST_GEMINI_INSTALLED: "0" + WORKTRUNK_TEST_MOCK_CONFIG_DIR: "[TEST_MOCK_CONFIG]" + WORKTRUNK_TEST_NUSHELL_ENV: "0" + WORKTRUNK_TEST_OPENCODE_INSTALLED: "0" + WORKTRUNK_TEST_PARENT_SHELL: "" + WORKTRUNK_TEST_POWERSHELL_ENV: "0" + WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" + WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" + WORKTRUNK_TEST_ZSH_INSTALLED: "0" + XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" +--- +success: true +exit_code: 0 +----- stdout ----- + Branch Status HEAD± main↕ main…± Commit Age Message +@ main ^ 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 1b87d47 16h Add feature-a… ++ feature-b ↑ ↑1 +1 f62940f 16h Add feature-b… ++ feature-c ↑ ↑1 +1 345c7c9 16h Add feature-c… ++ feature-with-a-longer-name _ 05a4a45 16h Initial commit + +----- stderr ----- + +○ Showing 5 worktrees, 3 ahead, hidden: Remote⇅, Path From 194258dfbe31da58ba8bac3b705c404b173a98fb Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 1 Sep 2026 20:57:33 -0700 Subject: [PATCH 06/17] list: head the default-branch columns with the default branch's name (#91) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `main↕` and `main…±` said `main` in a repo whose default branch is `trunk` — documented in `--help`, invisible in the terminal, and against the project's own "default branch, not main branch" rule. Both columns now carry the repo's own name where it fits: they are sized by their data (`↑99 ↓99`, `+999 -999`), so a name within that budget costs the table nothing and a `trunk` repo lays out exactly as a `main` one does. A longer name would widen two columns on every row to spell out a word the reader takes in once, so it gives way to a generic `base` — as does a default branch `wt` can't resolve, where `base` is the honest label for what the numbers are measured against. One decision serves both columns, taken from the tighter of the two budgets, so they can never name different things; `RepoFacts` carries the branch name in, `LayoutMetadata` the resolved label out, so the header, the width floor and the hidden-column footer all read the same value. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BGS51k25qzfeJEJ8dfxEEY --- dev/config.example.toml | 4 +- docs/src/content/docs/config.md | 4 +- docs/src/content/docs/list.md | 2 +- .../skills/worktrunk/reference/config.md | 4 +- .../skills/worktrunk/reference/list.md | 2 +- skills/worktrunk/reference/config.md | 4 +- skills/worktrunk/reference/list.md | 2 +- src/cli/mod.rs | 6 +- src/commands/list/collect/mod.rs | 2 + src/commands/list/columns.rs | 84 +++++++++++- src/commands/list/layout.rs | 120 +++++++++++++++--- src/commands/picker/mod.rs | 2 + ...ation_tests__help__help_config_create.snap | 4 +- ...gration_tests__help__help_config_long.snap | 4 +- ...tegration_tests__help__help_list_long.snap | 2 +- ...tion_tests__help__help_list_narrow_80.snap | 4 +- ...nches_with_nonexistent_default_branch.snap | 3 +- ..._full_with_nonexistent_default_branch.snap | 3 +- ..._list_with_nonexistent_default_branch.snap | 3 +- 19 files changed, 214 insertions(+), 45 deletions(-) diff --git a/dev/config.example.toml b/dev/config.example.toml index 95c3064570..41cc1ad22c 100644 --- a/dev/config.example.toml +++ b/dev/config.example.toml @@ -119,8 +119,8 @@ # - `branch` — The branch name # - `status` — Git status symbols, plus any user-defined status # - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -# - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) -# - `branch-diff` — Line changes against the default branch (header `main…±`) +# - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) +# - `branch-diff` — Line changes against the default branch (header `main…±`, likewise) # - `summary` — An LLM-generated summary of the branch # - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) # - `ci` — CI status of the head commit diff --git a/docs/src/content/docs/config.md b/docs/src/content/docs/config.md index 36ba9e4087..4d27a5924a 100644 --- a/docs/src/content/docs/config.md +++ b/docs/src/content/docs/config.md @@ -212,8 +212,8 @@ Valid built-in names: - `branch` — The branch name - `status` — Git status symbols, plus any user-defined status - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) -- `branch-diff` — Line changes against the default branch (header `main…±`) +- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) +- `branch-diff` — Line changes against the default branch (header `main…±`, likewise) - `summary` — An LLM-generated summary of the branch - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) - `ci` — CI status of the head commit diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index 097c835d57..d9263be0c5 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -91,7 +91,7 @@ $ wt list --format=json | Age | Time since last commit | | Message | Last commit message (truncated) | -The `main` header label is used regardless of the default branch's actual name. +The `main↕` and `main…±` headers name the default branch: its own name where it fits the width those columns already need (`trunk↕`, `master…±`), and `base` where it doesn't, so a long name never widens every row. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. diff --git a/plugins/worktrunk/skills/worktrunk/reference/config.md b/plugins/worktrunk/skills/worktrunk/reference/config.md index a8c275dcfc..b915206ff4 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/config.md +++ b/plugins/worktrunk/skills/worktrunk/reference/config.md @@ -206,8 +206,8 @@ Valid built-in names: - `branch` — The branch name - `status` — Git status symbols, plus any user-defined status - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) -- `branch-diff` — Line changes against the default branch (header `main…±`) +- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) +- `branch-diff` — Line changes against the default branch (header `main…±`, likewise) - `summary` — An LLM-generated summary of the branch - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) - `ci` — CI status of the head commit diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index b707ee0168..8561cf2f5e 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -78,7 +78,7 @@ $ wt list --format=json | Age | Time since last commit | | Message | Last commit message (truncated) | -The `main` header label is used regardless of the default branch's actual name. +The `main↕` and `main…±` headers name the default branch: its own name where it fits the width those columns already need (`trunk↕`, `master…±`), and `base` where it doesn't, so a long name never widens every row. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. diff --git a/skills/worktrunk/reference/config.md b/skills/worktrunk/reference/config.md index a8c275dcfc..b915206ff4 100644 --- a/skills/worktrunk/reference/config.md +++ b/skills/worktrunk/reference/config.md @@ -206,8 +206,8 @@ Valid built-in names: - `branch` — The branch name - `status` — Git status symbols, plus any user-defined status - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) -- `branch-diff` — Line changes against the default branch (header `main…±`) +- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) +- `branch-diff` — Line changes against the default branch (header `main…±`, likewise) - `summary` — An LLM-generated summary of the branch - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) - `ci` — CI status of the head commit diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index b707ee0168..8561cf2f5e 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -78,7 +78,7 @@ $ wt list --format=json | Age | Time since last commit | | Message | Last commit message (truncated) | -The `main` header label is used regardless of the default branch's actual name. +The `main↕` and `main…±` headers name the default branch: its own name where it fits the width those columns already need (`trunk↕`, `master…±`), and `base` where it doesn't, so a long name never widens every row. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 6f9b4ba3aa..c693b9d078 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -859,7 +859,7 @@ $ wt list --format=json | Age | Time since last commit | | Message | Last commit message (truncated) | -The `main` header label is used regardless of the default branch's actual name. +The `main↕` and `main…±` headers name the default branch: its own name where it fits the width those columns already need (`trunk↕`, `master…±`), and `base` where it doesn't, so a long name never widens every row. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. @@ -2162,8 +2162,8 @@ Valid built-in names: - `branch` — The branch name - `status` — Git status symbols, plus any user-defined status - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) -- `branch-diff` — Line changes against the default branch (header `main…±`) +- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) +- `branch-diff` — Line changes against the default branch (header `main…±`, likewise) - `summary` — An LLM-generated summary of the branch - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) - `ci` — CI status of the head commit diff --git a/src/commands/list/collect/mod.rs b/src/commands/list/collect/mod.rs index 6fed0a742f..0c4a95d9a8 100644 --- a/src/commands/list/collect/mod.rs +++ b/src/commands/list/collect/mod.rs @@ -1322,6 +1322,7 @@ pub fn collect( has_remote: repo.primary_remote().is_ok(), url_template: url_template.as_deref(), max_pr_number, + default_branch: default_branch.as_deref(), }, ); @@ -2564,6 +2565,7 @@ remove the file manually to continue."; has_remote: true, url_template: None, max_pr_number: None, + default_branch: None, }, ); let placeholder = super::super::render::PLACEHOLDER; diff --git a/src/commands/list/columns.rs b/src/commands/list/columns.rs index 93e5f093fc..caea64b339 100644 --- a/src/commands/list/columns.rs +++ b/src/commands/list/columns.rs @@ -1,3 +1,7 @@ +use std::borrow::Cow; + +use unicode_width::UnicodeWidthStr; + use super::collect::TaskKind; /// Logical identifier for each column rendered by `wt list`. @@ -24,15 +28,48 @@ pub enum ColumnKind { Custom(u8), } +/// Label the two default-branch columns fall back to when the branch's own +/// name is too long to head them (see [`base_label`]). +pub const GENERIC_BASE_LABEL: &str = "base"; + +/// Widest default-branch name that can head `AheadBehind` and `BranchDiff`. +/// +/// Both columns are sized by their data, not their header: `↑99 ↓99` is 7 +/// cells and `+999 -999` is 9. Subtracting each header's suffix (`↕` is 1 +/// cell, `…±` is 2) leaves budgets of 6 and 7, and the tighter one governs so +/// the two columns can't disagree about which label to use. A name within it +/// costs the table nothing — `test_base_label_never_widens_its_columns` pins +/// that. +const MAX_BASE_LABEL: usize = 6; + +/// The label the two default-branch columns head with. +/// +/// The repo's own default-branch name where it fits — `trunk↕`, `master…±` — +/// so the header names what the numbers are measured against instead of +/// asserting `main` in a repo that has no such branch. A longer name would +/// widen two columns on every row to spell out a word the reader takes in +/// once, so it gives way to `base`. Both columns take this one decision, so +/// they always name the same thing. +pub fn base_label(default_branch: Option<&str>) -> &str { + default_branch + .filter(|name| name.width() <= MAX_BASE_LABEL) + .unwrap_or(GENERIC_BASE_LABEL) +} + impl ColumnKind { - pub const fn header(self) -> &'static str { - match self { + /// This column's header text. + /// + /// `base` is the default-branch label from [`base_label`]; only + /// `AheadBehind` and `BranchDiff` — the columns measured against the + /// default branch — read it. + pub fn header(self, base: &str) -> Cow<'static, str> { + Cow::Borrowed(match self { ColumnKind::Gutter => "", ColumnKind::Branch => "Branch", ColumnKind::Status => "Status", ColumnKind::WorkingDiff => "HEAD±", - ColumnKind::AheadBehind => "main↕", - ColumnKind::BranchDiff => "main…±", + ColumnKind::AheadBehind => return Cow::Owned(format!("{base}↕")), + ColumnKind::BranchDiff => return Cow::Owned(format!("{base}…±")), ColumnKind::Path => "Path", ColumnKind::Upstream => "Remote⇅", ColumnKind::Url => "URL", @@ -44,7 +81,7 @@ impl ColumnKind { // Header is the resolved column name; layout substitutes it when // building `ColumnLayout`. ColumnKind::Custom(_) => "", - } + }) } /// Get the base priority for this column (lower = more important). @@ -477,7 +514,7 @@ mod tests { for kind in COLUMN_SPECS.iter().map(|spec| spec.kind) { if kind != ColumnKind::Gutter { assert!( - !kind.header().is_empty(), + !kind.header(GENERIC_BASE_LABEL).is_empty(), "{:?} should have a non-empty header", kind ); @@ -485,6 +522,41 @@ mod tests { } } + #[test] + fn test_base_label_names_the_default_branch_when_it_fits() { + assert_eq!(base_label(Some("main")), "main"); + assert_eq!(base_label(Some("trunk")), "trunk"); + assert_eq!(base_label(Some("master")), "master"); + // Past the budget the generic label stands in, as it does when the + // default branch can't be resolved at all. + assert_eq!(base_label(Some("develop")), GENERIC_BASE_LABEL); + assert_eq!(base_label(None), GENERIC_BASE_LABEL); + + assert_eq!(ColumnKind::AheadBehind.header("trunk"), "trunk↕"); + assert_eq!(ColumnKind::BranchDiff.header("trunk"), "trunk…±"); + // Every other column ignores the label. + assert_eq!(ColumnKind::Upstream.header("trunk"), "Remote⇅"); + } + + /// A label within [`MAX_BASE_LABEL`] costs the table nothing: both + /// default-branch columns are already as wide as their data (`↑99 ↓99`, + /// `+999 -999`), so the header fits inside the width they'd have anyway. + /// This is what lets the real branch name in without widening every row. + #[test] + fn test_base_label_never_widens_its_columns() { + for name in ["main", "master", "trunk", "dev", "Δmain"] { + let base = base_label(Some(name)); + assert!( + ColumnKind::AheadBehind.header(base).width() <= 7, + "{name}: ahead/behind header outgrew its data width" + ); + assert!( + ColumnKind::BranchDiff.header(base).width() <= 9, + "{name}: branch-diff header outgrew its data width" + ); + } + } + #[test] fn test_all_column_kinds_have_priority() { // Every ColumnKind variant must be in COLUMN_SPECS so priority() works correctly. diff --git a/src/commands/list/layout.rs b/src/commands/list/layout.rs index ead2cc7a07..fa6d792f61 100644 --- a/src/commands/list/layout.rs +++ b/src/commands/list/layout.rs @@ -313,6 +313,12 @@ pub struct RepoFacts<'a> { /// Largest PR/MR number any previous fetch cached, which sizes the CI /// column before this run's fetch reports. pub max_pr_number: Option, + + /// The default branch's name, which heads the two columns measured + /// against it. `None` when it can't be resolved; either way the label + /// itself comes from [`base_label`](super::columns::base_label), which + /// falls back to `base`. + pub default_branch: Option<&'a str>, } /// Tracks which columns have actual data (vs just headers) @@ -334,6 +340,9 @@ pub struct LayoutMetadata { pub widths: ColumnWidths, pub data_flags: ColumnDataFlags, pub status_position_mask: super::model::PositionMask, + /// What the two default-branch columns head with, resolved once here so + /// every header, width floor and footer entry names the same thing. + pub base_label: String, } /// Added to a column's base priority when it has nothing to show on any row. @@ -806,14 +815,15 @@ fn build_estimated_widths( // `PositionMask::FULL`, so the column has to be at least that wide or the // cell overflows it. Read the width off the mask rather than restating its // arithmetic — the two can't drift. + let base = super::columns::base_label(facts.default_branch); let status_fixed = fit_header( - ColumnKind::Status.header(), + &ColumnKind::Status.header(base), super::model::PositionMask::FULL.total_width(), ); - let working_diff_fixed = fit_header(ColumnKind::WorkingDiff.header(), 9); // "+999 -999" - let ahead_behind_fixed = fit_header(ColumnKind::AheadBehind.header(), 7); // "↑99 ↓99" - let branch_diff_fixed = fit_header(ColumnKind::BranchDiff.header(), 9); // "+999 -999" - let upstream_fixed = fit_header(ColumnKind::Upstream.header(), 7); // "↑99 ↓99" + let working_diff_fixed = fit_header(&ColumnKind::WorkingDiff.header(base), 9); // "+999 -999" + let ahead_behind_fixed = fit_header(&ColumnKind::AheadBehind.header(base), 7); // "↑99 ↓99" + let branch_diff_fixed = fit_header(&ColumnKind::BranchDiff.header(base), 9); // "+999 -999" + let upstream_fixed = fit_header(&ColumnKind::Upstream.header(base), 7); // "↑99 ↓99" let age_estimate = 4; // "11mo" (short format) // CI column: PR/MR reference ("#3035"), sized from the cached largest // number seen; "#9999" before the first fetch populates the cache. A @@ -821,7 +831,7 @@ fn build_estimated_widths( // (`PrStatus::format_cell`) — the layout never resizes mid-render — and // the ratcheted cache sizes the next invocation correctly. let ci_estimate = fit_header( - ColumnKind::CiStatus.header(), + &ColumnKind::CiStatus.header(base), super::ci_status::pr_ref_width(facts.max_pr_number.unwrap_or(9999)), ); @@ -847,7 +857,7 @@ fn build_estimated_widths( // URL width estimated from template + longest branch (or fallback) // When url_width is 0 (no template), don't allocate any space for URL column let url_estimate = if url_width > 0 { - fit_header(ColumnKind::Url.header(), url_width) + fit_header(&ColumnKind::Url.header(base), url_width) } else { 0 }; @@ -888,6 +898,7 @@ fn build_estimated_widths( widths, data_flags, status_position_mask: super::model::PositionMask::FULL, + base_label: base.to_string(), } } @@ -1063,7 +1074,7 @@ fn allocate_columns_with_priority( let is_first = !needs_spacing(&pending); let min_width = if spec.shrinkable { - Some(spec.kind.header().width().max(1)) + Some(spec.kind.header(&metadata.base_label).width().max(1)) } else { None }; @@ -1181,7 +1192,7 @@ fn allocate_columns_with_priority( ColumnKind::Custom(i) => { std::borrow::Cow::Owned(custom_columns[i as usize].name.clone()) } - kind => std::borrow::Cow::Borrowed(kind.header()), + kind => kind.header(&metadata.base_label), }; columns.push(ColumnLayout { kind: col.spec.kind, @@ -1208,7 +1219,7 @@ fn allocate_columns_with_priority( .into_iter() .map(|kind| match kind { ColumnKind::Custom(i) => custom_columns[i as usize].name.clone(), - kind => kind.header().to_string(), + kind => kind.header(&metadata.base_label).into_owned(), }) .collect(); @@ -1281,7 +1292,8 @@ pub fn calculate_layout_with_width( .max() .unwrap_or(0) .min(MAX_BRANCH); - let max_branch = fit_header(ColumnKind::Branch.header(), max_branch); + let base = super::columns::base_label(facts.default_branch); + let max_branch = fit_header(&ColumnKind::Branch.header(base), max_branch); let path_data_width = items .iter() @@ -1289,7 +1301,7 @@ pub fn calculate_layout_with_width( .map(|path| shorten_path(path, main_worktree_path).width()) .max() .unwrap_or(0); - let max_path_width = fit_header(ColumnKind::Path.header(), path_data_width); + let max_path_width = fit_header(&ColumnKind::Path.header(base), path_data_width); // The Path column is redundant with Branch unless a path says something the // branch name doesn't: the worktree sits off-template, or two worktrees share @@ -1341,7 +1353,7 @@ pub fn calculate_layout_with_width( .map(|item| item.short_sha.width()) .max() .unwrap_or(0); - let commit_width = fit_header(ColumnKind::Commit.header(), commit_data_width); + let commit_width = fit_header(&ColumnKind::Commit.header(base), commit_data_width); allocate_columns_with_priority( &metadata, @@ -1360,14 +1372,16 @@ mod tests { use worktrunk::git::LineDiff; use worktrunk::styling::terminal_width; - /// The default repository context: a remote exists, no dev-server URL - /// template, and no cached PR/MR number. Tests that care about one of - /// these override it (`RepoFacts { has_remote: false, ..test_facts() }`). + /// The default repository context: a remote exists, the default branch is + /// `main`, and there's no dev-server URL template or cached PR/MR number. + /// Tests that care about one of these override it + /// (`RepoFacts { has_remote: false, ..test_facts() }`). fn test_facts() -> RepoFacts<'static> { RepoFacts { has_remote: true, url_template: None, max_pr_number: None, + default_branch: Some("main"), } } @@ -2169,6 +2183,80 @@ mod tests { ); } + /// The two default-branch columns head with the repo's own branch name, + /// and the layout is the piece that has to agree with itself about it: + /// the rendered header, the width floor and the hidden-column footer all + /// read the one label. A name that fits costs no width, so a `trunk` repo + /// lays out exactly as a `main` one does. + #[test] + fn test_headers_name_the_default_branch() { + let items = vec![make_test_item("feature-branch")]; + let layout_at = |default_branch, width| { + calculate_layout_with_width( + &items, + &non_full_run_tasks(), + Destination { + width, + link_style: LinkStyle::Expanded, + }, + Path::new("/test"), + ColumnSelection { + custom: &[], + selected: None, + }, + RepoFacts { + default_branch, + ..test_facts() + }, + ) + }; + + let trunk = layout_at(Some("trunk"), 200); + let ahead_behind = + find_column(&trunk, ColumnKind::AheadBehind).expect("AheadBehind allocated"); + assert_eq!(ahead_behind.header, "trunk↕"); + assert_eq!( + find_column(&trunk, ColumnKind::BranchDiff) + .expect("BranchDiff allocated") + .header, + "trunk…±" + ); + + let main = layout_at(Some("main"), 200); + assert_eq!( + ahead_behind.width, + find_column(&main, ColumnKind::AheadBehind).unwrap().width, + "a name within the budget doesn't widen the column" + ); + + // A name too long to head the columns gives way to the generic label, + // and an unresolved default branch lands on the same one. + assert_eq!( + find_column( + &layout_at(Some("development"), 200), + ColumnKind::AheadBehind + ) + .unwrap() + .header, + "base↕" + ); + assert_eq!( + find_column(&layout_at(None, 200), ColumnKind::AheadBehind) + .unwrap() + .header, + "base↕" + ); + + // The footer names a dropped column by the same label the header + // would have carried. + let narrow = layout_at(Some("trunk"), 40); + assert!( + narrow.hidden_columns.iter().any(|name| name == "trunk…±"), + "the footer names the hidden column as the header would: {:?}", + narrow.hidden_columns + ); + } + #[test] fn test_layout_renders_column_iff_task_planned() { // The layout stage honors the run plan literally: a column renders iff one diff --git a/src/commands/picker/mod.rs b/src/commands/picker/mod.rs index 070e4557dc..2f8fef04d0 100644 --- a/src/commands/picker/mod.rs +++ b/src/commands/picker/mod.rs @@ -3255,6 +3255,7 @@ pub mod tests { has_remote: true, url_template: None, max_pr_number: None, + default_branch: None, }, )); (row, token, rendered, morphed) @@ -4238,6 +4239,7 @@ pub mod tests { has_remote: true, url_template: None, max_pr_number: None, + default_branch: None, }, ); diff --git a/tests/snapshots/integration__integration_tests__help__help_config_create.snap b/tests/snapshots/integration__integration_tests__help__help_config_create.snap index f8d3df2b81..f101e046c3 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_create.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_create.snap @@ -195,8 +195,8 @@ Creates ~/.config/worktrunk/config.toml with the following content:   # - `branch` — The branch name   # - `status` — Git status symbols, plus any user-defined status   # - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -  # - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) -  # - `branch-diff` — Line changes against the default branch (header `main…±`) +  # - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) +  # - `branch-diff` — Line changes against the default branch (header `main…±`, likewise)   # - `summary` — An LLM-generated summary of the branch   # - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`)   # - `ci` — CI status of the head commit diff --git a/tests/snapshots/integration__integration_tests__help__help_config_long.snap b/tests/snapshots/integration__integration_tests__help__help_config_long.snap index febf0e5d62..c170d0e0a0 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_long.snap @@ -243,8 +243,8 @@ Valid built-in names: - branch — The branch name - status — Git status symbols, plus any user-defined status - working-diff — Uncommitted line changes against HEAD, including untracked files (header HEAD±) -- ahead-behind — Commits ahead of and behind the default branch (header main↕) -- branch-diff — Line changes against the default branch (header main…±) +- ahead-behind — Commits ahead of and behind the default branch (header main↕, or whatever the default branch is called) +- branch-diff — Line changes against the default branch (header main…±, likewise) - summary — An LLM-generated summary of the branch - upstream — Commits ahead of and behind the upstream tracking branch (header Remote⇅) - ci — CI status of the head commit diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 694760f13b..8e52dd4915 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -158,7 +158,7 @@ Output as JSON for scripting: Age Time since last commit Message Last commit message (truncated) -The main header label is used regardless of the default branch's actual name. +The main↕ and main…± headers name the default branch: its own name where it fits the width those columns already need (trunk↕, master…±), and base where it doesn't, so a long name never widens every row. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (hidden: Commit, Age, Message). A column with nothing to show on any row — Remote⇅ in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [list] columns; --format=json carries every field at any width. diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 5a67f086cf..9ffe67c6cb 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -173,7 +173,9 @@ Output as JSON for scripting: Age Time since last commit Message Last commit message (truncated) -The main header label is used regardless of the default branch's actual name. +The main↕ and main…± headers name the default branch: its own name where it fits + the width those columns already need (trunk↕, master…±), and base where it +doesn't, so a long name never widens every row. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs diff --git a/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap index c2ab8cfe79..7dfa122b65 100644 --- a/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,7 +57,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message + Branch Status HEAD± base↕ base…± Remote⇅ Path Commit Age Message @ main ⚑^| | . 05a4a45 16h Initial commit + feature-a ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ../repo.feature-b f62940f 16h Add feature-b file diff --git a/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap index ef8a398c7f..13b6f1d39f 100644 --- a/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,7 +57,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message + Branch Status HEAD± base↕ base…± Remote⇅ CI Path Commit Age Message @ main ⚑^| | . 05a4a45 16h Initial commit + feature-a ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ../repo.feature-b f62940f 16h Add feature-b file diff --git a/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap index de43c13881..330e717dbf 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,7 +56,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message + Branch Status HEAD± base↕ base…± Remote⇅ Path Commit Age Message @ main ⚑^| | . 05a4a45 16h Initial commit + feature-a ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ../repo.feature-b f62940f 16h Add feature-b file From 7b1a95d40b951c0fb1ea5781f49df5969dbc186e Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 1 Sep 2026 21:00:29 -0700 Subject: [PATCH 07/17] list: give detached and prunable worktrees a row that says so (#92) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A detached worktree rendered as a bare hash under Branch with no marker of its own — `⚑` 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 `⊘` instead, and stops claiming to be off-template, so `⚑` again means only what it says. The Path column also counts a detached row as informative: a hash names no directory, so without the path nothing ties the row to one. A prunable worktree showed four `·` loading glyphs that never resolved. Its directory is gone, so `work_items_for_worktree` spawns no task for it and no cell one would fill is ever coming; the glyphs promised data for the life of the row. Those cells now render blank, leaving the `⊟` in Status as the row's whole story. `worktree.detached` was in `--format=json` all along; the table now uses it. Schema 1's `worktree.state` stays silent for a detached row rather than naming a state its sibling `detached` field already carries. Not addressed: under `--branches`, the branch a detached worktree was created from still lists as a second row. Git keeps no record of that association once HEAD detaches, so the two rows are both true — the branch genuinely has no worktree — and the Path cell is now the only honest link between them. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BGS51k25qzfeJEJ8dfxEEY --- docs/src/content/docs/list.md | 7 +- .../skills/worktrunk/reference/list.md | 7 +- skills/worktrunk/reference/list.md | 7 +- src/cli/mod.rs | 7 +- src/commands/list/collect/mod.rs | 7 +- src/commands/list/json_output.rs | 5 +- src/commands/list/layout.rs | 14 ++-- src/commands/list/model/item.rs | 70 +++++++++++++++---- src/commands/list/model/state.rs | 11 ++- src/commands/list/render.rs | 4 +- tests/integration_tests/list_layout.rs | 16 +++++ ...tegration_tests__help__help_list_long.snap | 7 +- ...tion_tests__help__help_list_narrow_80.snap | 35 +++++----- ...ation_tests__list__list_detached_head.snap | 3 +- ..._list__list_detached_head_in_worktree.snap | 3 +- ...ips_operations_for_prunable_worktrees.snap | 3 +- ...ts__list__list_task_dag_detached_head.snap | 3 +- ...yout__detached_and_prunable_width_120.snap | 69 ++++++++++++++++++ 18 files changed, 217 insertions(+), 61 deletions(-) create mode 100644 tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index d9263be0c5..23564207f6 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -166,16 +166,17 @@ Independent flags from `git status`; several can show at once (e.g. `+!?`). Each ### Worktree -An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (`✘ > ↻ > ⊟ > ⊞ > ⚑ > /`): +An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (`✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > /`): | Symbol | JSON | Meaning | |--------|------|---------| | `✘` | `worktree.changes.conflicted` | Merge conflicts | | `↻` | `worktree.operation` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | -| `⊟` | `worktree.prunable` | Prunable (worktree directory missing) | +| `⊟` | `worktree.prunable` | Prunable (worktree directory missing); nothing can be read from a directory that isn't there, so the row's data cells stay blank | | `⊞` | `worktree.locked` | Locked worktree | +| `⊘` | `worktree.detached` | Detached HEAD — the worktree is on a commit, not a branch, which is why its Branch cell holds a short hash | | `⚑` | `worktree.duplicate_branch` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | -| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | +| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies | | `/` | no `worktree` object | Branch without a worktree | ### Default branch diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 8561cf2f5e..87fa4c9cb1 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -151,16 +151,17 @@ Independent flags from `git status`; several can show at once (e.g. `+!?`). Each ### Worktree -An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (`✘ > ↻ > ⊟ > ⊞ > ⚑ > /`): +An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (`✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > /`): | Symbol | JSON | Meaning | |--------|------|---------| | `✘` | `worktree.changes.conflicted` | Merge conflicts | | `↻` | `worktree.operation` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | -| `⊟` | `worktree.prunable` | Prunable (worktree directory missing) | +| `⊟` | `worktree.prunable` | Prunable (worktree directory missing); nothing can be read from a directory that isn't there, so the row's data cells stay blank | | `⊞` | `worktree.locked` | Locked worktree | +| `⊘` | `worktree.detached` | Detached HEAD — the worktree is on a commit, not a branch, which is why its Branch cell holds a short hash | | `⚑` | `worktree.duplicate_branch` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | -| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | +| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies | | `/` | no `worktree` object | Branch without a worktree | ### Default branch diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 8561cf2f5e..87fa4c9cb1 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -151,16 +151,17 @@ Independent flags from `git status`; several can show at once (e.g. `+!?`). Each ### Worktree -An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (`✘ > ↻ > ⊟ > ⊞ > ⚑ > /`): +An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (`✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > /`): | Symbol | JSON | Meaning | |--------|------|---------| | `✘` | `worktree.changes.conflicted` | Merge conflicts | | `↻` | `worktree.operation` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | -| `⊟` | `worktree.prunable` | Prunable (worktree directory missing) | +| `⊟` | `worktree.prunable` | Prunable (worktree directory missing); nothing can be read from a directory that isn't there, so the row's data cells stay blank | | `⊞` | `worktree.locked` | Locked worktree | +| `⊘` | `worktree.detached` | Detached HEAD — the worktree is on a commit, not a branch, which is why its Branch cell holds a short hash | | `⚑` | `worktree.duplicate_branch` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | -| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | +| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies | | `/` | no `worktree` object | Branch without a worktree | ### Default branch diff --git a/src/cli/mod.rs b/src/cli/mod.rs index c693b9d078..2076fb035d 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -932,16 +932,17 @@ Independent flags from `git status`; several can show at once (e.g. `+!?`). Each ### Worktree -An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (`✘ > ↻ > ⊟ > ⊞ > ⚑ > /`): +An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (`✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > /`): | Symbol | JSON | Meaning | |--------|------|---------| | `✘` | `worktree.changes.conflicted` | Merge conflicts | | `↻` | `worktree.operation` `"rebase"`, `"merge"`, `"cherry_pick"`, `"revert"`, `"bisect"` | A git operation is in progress; `git status` names it | -| `⊟` | `worktree.prunable` | Prunable (worktree directory missing) | +| `⊟` | `worktree.prunable` | Prunable (worktree directory missing); nothing can be read from a directory that isn't there, so the row's data cells stay blank | | `⊞` | `worktree.locked` | Locked worktree | +| `⊘` | `worktree.detached` | Detached HEAD — the worktree is on a commit, not a branch, which is why its Branch cell holds a short hash | | `⚑` | `worktree.duplicate_branch` | Branch checked out in more than one worktree, so `wt` resolves it to whichever git lists first; every worktree on the branch is flagged | -| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home | +| `⚑` | `worktree.branch_mismatch` | Worktree isn't at the path its branch implies | | `/` | no `worktree` object | Branch without a worktree | ### Default branch diff --git a/src/commands/list/collect/mod.rs b/src/commands/list/collect/mod.rs index 0c4a95d9a8..66011af329 100644 --- a/src/commands/list/collect/mod.rs +++ b/src/commands/list/collect/mod.rs @@ -1117,9 +1117,12 @@ pub fn collect( // is_previous set to false initially - computed after skeleton let is_previous = false; - // Check if worktree is at its expected path based on config template + // Check if worktree is at its expected path based on config + // template. A detached worktree has no branch to imply a path, so + // it isn't off-template — it has its own `⊘`, and flagging it here + // too would spend the `⚑` on a state the row already reports. let branch_worktree_mismatch = - !is_worktree_at_expected_path(wt, repo, repo.user_config()); + wt.branch.is_some() && !is_worktree_at_expected_path(wt, repo, repo.user_config()); let mut worktree_data = WorktreeData::from_worktree(wt, is_main, is_current, is_previous); diff --git a/src/commands/list/json_output.rs b/src/commands/list/json_output.rs index 860c8168b1..2b3904a333 100644 --- a/src/commands/list/json_output.rs +++ b/src/commands/list/json_output.rs @@ -458,7 +458,10 @@ fn worktree_state_to_json( // (metadata family) hasn't been populated yet; fall through to the // direct-field fallback below. match status_symbols.worktree_state { - None | Some(WorktreeState::None) => {} + // `Detached` has no `state` string of its own: the sibling `detached` + // field already carries it, and naming it twice would be the only + // state this object reports in two places. + None | Some(WorktreeState::None | WorktreeState::Detached) => {} Some(WorktreeState::Branch) => return (Some("no_worktree"), None), Some(WorktreeState::BranchWorktreeMismatch) => { return (Some("branch_worktree_mismatch"), None); diff --git a/src/commands/list/layout.rs b/src/commands/list/layout.rs index fa6d792f61..e87007a3c6 100644 --- a/src/commands/list/layout.rs +++ b/src/commands/list/layout.rs @@ -1304,12 +1304,14 @@ pub fn calculate_layout_with_width( let max_path_width = fit_header(&ColumnKind::Path.header(base), path_data_width); // The Path column is redundant with Branch unless a path says something the - // branch name doesn't: the worktree sits off-template, or two worktrees share - // the branch and the path is the only thing telling their rows apart. - let path_is_informative = items - .iter() - .filter_map(|item| item.worktree_data()) - .any(|data| data.branch_worktree_mismatch || data.duplicate_branch); + // branch name doesn't: the worktree sits off-template, two worktrees share + // the branch and the path is the only thing telling their rows apart, or a + // detached row's Branch cell is a hash, which names no directory at all. + let path_is_informative = items.iter().any(|item| { + item.worktree_data().is_some_and(|data| { + data.branch_worktree_mismatch || data.duplicate_branch || item.branch().is_none() + }) + }); // Estimate URL width from template (heuristic, no expansion needed) let url_width = estimate_url_width(facts.url_template, link_style); diff --git a/src/commands/list/model/item.rs b/src/commands/list/model/item.rs index a2ded2128a..2f17a8b8eb 100644 --- a/src/commands/list/model/item.rs +++ b/src/commands/list/model/item.rs @@ -22,17 +22,25 @@ use crate::commands::list::layout::{LinkStyle, format_url_cell}; /// /// Used by `refresh_status_symbols` to resolve the worktree-state position /// (Gate 2) from metadata alone. The decision priority is: -/// `prunable` > `locked` > `duplicate_branch` > `branch_worktree_mismatch` > -/// `None` — the yellow actionable states outrank the informational (dim -/// yellow) `⚑`. The last two both render `⚑`, so their order decides only -/// which cause the JSON `worktree.state` names; a duplicate wins because a -/// force-added worktree lands off-template as a side effect of being -/// force-added, not as the fact worth reporting. -fn metadata_worktree_state(data: &WorktreeData) -> WorktreeState { +/// `prunable` > `locked` > detached > `duplicate_branch` > +/// `branch_worktree_mismatch` > `None` — the yellow actionable states outrank +/// the informational (dim yellow) `⊘` and `⚑`. The last two both render `⚑`, +/// so their order decides only which cause the JSON `worktree.state` names; a +/// duplicate wins because a force-added worktree lands off-template as a side +/// effect of being force-added, not as the fact worth reporting. +/// +/// `has_branch` is the row's own answer, not `data.detached`: git reports a +/// worktree mid-rebase as detached while worktree parsing still recovers its +/// branch name, and such a row is on a branch as far as every other cell is +/// concerned. A row with no branch is exactly the one whose Branch cell shows +/// a hash. +fn metadata_worktree_state(data: &WorktreeData, has_branch: bool) -> WorktreeState { if data.is_prunable() { WorktreeState::Prunable } else if data.locked.is_some() { WorktreeState::Locked + } else if !has_branch { + WorktreeState::Detached } else if data.duplicate_branch { WorktreeState::DuplicateBranch } else if data.branch_worktree_mismatch { @@ -486,6 +494,22 @@ impl ListItem { matches!(&self.kind, ItemKind::Worktree(data) if data.is_main) } + /// The glyph this row's unresolved cells render, given the render's + /// current one. + /// + /// A prunable worktree's directory is gone, so `work_items_for_worktree` + /// spawns nothing for it and no cell a task would fill will ever arrive. + /// The loading dot would promise data that isn't coming — for the life of + /// the row, not for a tick — so those cells stay blank and the `⊟` in + /// Status is the row's whole story. + pub(crate) fn placeholder<'a>(&self, rendering: &'a str) -> &'a str { + if self.worktree_data().is_some_and(|data| data.is_prunable()) { + crate::commands::list::render::PLACEHOLDER_BLANK + } else { + rendering + } + } + pub fn head(&self) -> &str { &self.branch_ref.commit_sha } @@ -683,8 +707,9 @@ impl ListItem { // line, `status_symbols.worktree_state` is always `Some`. // (Prunable worktrees are pre-seeded at spawn time and have // `worktree_state = Some(Prunable)` by the time this runs.) + let has_branch = self.branch().is_some(); let metadata_state = match &self.kind { - ItemKind::Worktree(data) => metadata_worktree_state(data), + ItemKind::Worktree(data) => metadata_worktree_state(data, has_branch), ItemKind::Branch(_) => WorktreeState::Branch, }; if self.status_symbols.worktree_state.is_none() { @@ -935,17 +960,19 @@ mod tests { use super::*; /// The yellow actionable states outrank the informational (dim yellow) - /// `⚑`, so a demoted flag can never mask `⊟` or `⊞`. A force-added + /// `⊘` and `⚑`, so a demoted flag can never mask `⊟` or `⊞`. A force-added /// duplicate lands off-template too, so the two `⚑` states routinely /// co-occur and their order picks the cause the JSON reports. #[test] fn test_metadata_worktree_state_priority() { + const ON_BRANCH: bool = true; + let mismatched = WorktreeData { branch_worktree_mismatch: true, ..Default::default() }; assert_eq!( - metadata_worktree_state(&mismatched), + metadata_worktree_state(&mismatched, ON_BRANCH), WorktreeState::BranchWorktreeMismatch ); @@ -954,7 +981,7 @@ mod tests { ..mismatched.clone() }; assert_eq!( - metadata_worktree_state(&duplicate), + metadata_worktree_state(&duplicate, ON_BRANCH), WorktreeState::DuplicateBranch ); @@ -962,13 +989,30 @@ mod tests { prunable: Some("gone".to_string()), ..duplicate.clone() }; - assert_eq!(metadata_worktree_state(&prunable), WorktreeState::Prunable); + assert_eq!( + metadata_worktree_state(&prunable, ON_BRANCH), + WorktreeState::Prunable + ); let locked = WorktreeData { locked: Some("pinned".to_string()), ..duplicate.clone() }; - assert_eq!(metadata_worktree_state(&locked), WorktreeState::Locked); + assert_eq!( + metadata_worktree_state(&locked, ON_BRANCH), + WorktreeState::Locked + ); + + // Off a branch, `⊘` outranks both `⚑` states — nothing else in the + // row says the Branch cell is a hash — but still yields to `⊟`/`⊞`. + assert_eq!( + metadata_worktree_state(&duplicate, false), + WorktreeState::Detached + ); + assert_eq!( + metadata_worktree_state(&locked, false), + WorktreeState::Locked + ); } #[test] diff --git a/src/commands/list/model/state.rs b/src/commands/list/model/state.rs index 14cffabbb1..2327919900 100644 --- a/src/commands/list/model/state.rs +++ b/src/commands/list/model/state.rs @@ -74,8 +74,8 @@ impl Divergence { /// - For worktrees: whether the path matches the template, or has issues /// - For branches (without worktree): shows / to distinguish from worktrees /// -/// Priority order for worktrees: Prunable > Locked > DuplicateBranch > -/// BranchWorktreeMismatch +/// Priority order for worktrees: Prunable > Locked > Detached > +/// DuplicateBranch > BranchWorktreeMismatch /// /// `DuplicateBranch` and `BranchWorktreeMismatch` share the `⚑` glyph: both /// say this worktree's place in the branch ⇔ worktree map is irregular, and @@ -83,6 +83,10 @@ impl Divergence { /// duplicate, an off-template Path cell the mismatch. The variants stay /// separate because the JSON `worktree.state` names the cause, where there /// is no glyph budget to spend. +/// +/// `Detached` gets a glyph of its own because nothing else in the row says +/// so: the Branch cell holds a short hash, which reads as a branch named +/// like one until the column tells you otherwise. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, strum::IntoStaticStr)] pub enum WorktreeState { #[strum(serialize = "")] @@ -93,6 +97,8 @@ pub enum WorktreeState { BranchWorktreeMismatch, /// The branch is checked out in more than one worktree DuplicateBranch, + /// Detached HEAD: the worktree is on a commit, not a branch + Detached, /// Prunable (worktree directory missing) Prunable, /// Locked (protected from removal) @@ -106,6 +112,7 @@ impl std::fmt::Display for WorktreeState { match self { Self::None => Ok(()), Self::BranchWorktreeMismatch | Self::DuplicateBranch => write!(f, "⚑"), + Self::Detached => write!(f, "⊘"), Self::Prunable => write!(f, "⊟"), Self::Locked => write!(f, "⊞"), Self::Branch => write!(f, "/"), diff --git a/src/commands/list/render.rs b/src/commands/list/render.rs index d84214622b..ada3815885 100644 --- a/src/commands/list/render.rs +++ b/src/commands/list/render.rs @@ -278,7 +278,7 @@ impl LayoutConfig { /// to [`PLACEHOLDER`] on the reveal tick. Non-progressive callers pass /// [`PLACEHOLDER`] directly. pub fn render_list_item_line(&self, item: &ListItem, placeholder: &str) -> StyledLine { - self.render_line(|column| column.render_cell(item, self, placeholder)) + self.render_line(|column| column.render_cell(item, self, item.placeholder(placeholder))) } /// Render a skeleton row showing known data (branch, path) with placeholders for other columns. @@ -293,7 +293,7 @@ impl LayoutConfig { .unwrap_or_default(); let dim = Style::new().dimmed(); - let spinner = placeholder; + let spinner = item.placeholder(placeholder); self.render_line(|col| { let mut cell = StyledLine::new(); diff --git a/tests/integration_tests/list_layout.rs b/tests/integration_tests/list_layout.rs index cda3f2eaca..e901530e95 100644 --- a/tests/integration_tests/list_layout.rs +++ b/tests/integration_tests/list_layout.rs @@ -54,6 +54,22 @@ fn empty_remote_column_drops_before_populated_ones(mut repo: TestRepo) { snapshot_list(&repo, "no_remote_width_100", 100); } +/// Two rows that aren't on a branch in the ordinary way. A detached worktree +/// puts a hash in the Branch column, so `⊘` and the Path cell are what say +/// which directory it is; a prunable one has no directory to read, so its +/// data cells stay blank rather than showing a loading dot that can never +/// resolve. +#[rstest] +fn detached_and_prunable_rows_say_what_they_are(mut repo: TestRepo) { + let detached = repo.add_worktree("feature-detached"); + repo.run_git_in(&detached, &["checkout", "--detach"]); + + let gone = repo.add_worktree("feature-gone"); + std::fs::remove_dir_all(&gone).unwrap(); + + snapshot_list(&repo, "detached_and_prunable_width_120", 120); +} + /// One very long branch must not size the Branch column for every row: past /// the cap the name is elided, so the columns that answer "what is going on /// here" still fit. Without it, 60 columns degenerates into a branch list and diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 8e52dd4915..99fa614331 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -232,16 +232,17 @@ Independent flags from git status; several can show at once (e.g. +! Worktree -An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (✘ > ↻ > ⊟ > ⊞ > ⚑ > /): +An in-progress git operation, a worktree-location attribute, or a branch with no worktree. One symbol shows, highest priority first (✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > /): Symbol JSON Meaning ────── ─────────────────────────────────────────────────────────────────────── ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ✘ worktree.changes.conflicted Merge conflicts ↻ worktree.operation "rebase", "merge", "cherry_pick", "revert", "bisect" A git operation is in progress; git status names it - ⊟ worktree.prunable Prunable (worktree directory missing) + ⊟ worktree.prunable Prunable (worktree directory missing); nothing can be read from a directory that isn't there, so the row's data cells stay blank ⊞ worktree.locked Locked worktree + ⊘ worktree.detached Detached HEAD — the worktree is on a commit, not a branch, which is why its Branch cell holds a short hash ⚑ worktree.duplicate_branch Branch checked out in more than one worktree, so wt resolves it to whichever git lists first; every worktree on the branch is flagged - ⚑ worktree.branch_mismatch Worktree isn't at the path its branch implies — including a detached one, which has no branch to imply a path and so is never at home + ⚑ worktree.branch_mismatch Worktree isn't at the path its branch implies / no worktree object Branch without a worktree Default branch diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 9ffe67c6cb..25ba141462 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -294,26 +294,29 @@ symbol in the column. Worktree An in-progress git operation, a worktree-location attribute, or a branch with no - worktree. One symbol shows, highest priority first (✘ > ↻ > ⊟ > ⊞ > ⚑ > /): + worktree. One symbol shows, highest priority first (✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > /): - Symb JSON Meaning + Symb JSON Meaning ol - ──── ────────────────────────── ────────────────────────────────────────────── - ✘ worktree.changes.conflicte Merge conflicts - d - ↻ worktree.operation A git operation is in progress; git status - "rebase", "merge", names it + ──── ───────────────────────── ─────────────────────────────────────────────── + ✘ worktree.changes.conflict Merge conflicts + ed + ↻ worktree.operation A git operation is in progress; git status + "rebase", "merge", names it "cherry_pick", "revert", "bisect" - ⊟ worktree.prunable Prunable (worktree directory missing) - ⊞ worktree.locked Locked worktree - ⚑ worktree.duplicate_branch Branch checked out in more than one worktree, - so wt resolves it to whichever git lists - first; every worktree on the branch is flagged - ⚑ worktree.branch_mismatch Worktree isn't at the path its branch implies - — including a detached one, which has no - branch to imply a path and so is never at home - / no worktree object Branch without a worktree + ⊟ worktree.prunable Prunable (worktree directory missing); nothing + can be read from a directory that isn't there, + so the row's data cells stay blank + ⊞ worktree.locked Locked worktree + ⊘ worktree.detached Detached HEAD — the worktree is on a commit, + not a branch, which is why its Branch cell + holds a short hash + ⚑ worktree.duplicate_branch Branch checked out in more than one worktree, + so wt resolves it to whichever git lists first; + every worktree on the branch is flagged + ⚑ worktree.branch_mismatch Worktree isn't at the path its branch implies + / no worktree object Branch without a worktree Default branch diff --git a/tests/snapshots/integration__integration_tests__list__list_detached_head.snap b/tests/snapshots/integration__integration_tests__list__list_detached_head.snap index c084342c44..f61df04094 100644 --- a/tests/snapshots/integration__integration_tests__list__list_detached_head.snap +++ b/tests/snapshots/integration__integration_tests__list__list_detached_head.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,7 +57,7 @@ success: true exit_code: 0 ----- stdout ----- Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ 05a4a45 ⚑^ . 05a4a45 16h Initial commit +@ 05a4a45 ⊘^ . 05a4a45 16h Initial commit + feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file + feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file diff --git a/tests/snapshots/integration__integration_tests__list__list_detached_head_in_worktree.snap b/tests/snapshots/integration__integration_tests__list__list_detached_head_in_worktree.snap index d6f8df3136..4aab9cb626 100644 --- a/tests/snapshots/integration__integration_tests__list__list_detached_head_in_worktree.snap +++ b/tests/snapshots/integration__integration_tests__list__list_detached_head_in_worktree.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,7 +58,7 @@ exit_code: 0 ----- stdout ----- Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message @ main ^| | . 05a4a45 16h Initial commit -+ 05a4a45 ⚑_ ../repo.feature 05a4a45 16h Initial commit ++ 05a4a45 ⊘_ ../repo.feature 05a4a45 16h Initial commit + feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file + feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file diff --git a/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap b/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap index a7e02c92f9..f1600b8a2a 100644 --- a/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap +++ b/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,7 +58,7 @@ exit_code: 0 ----- stdout ----- Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message @ main ^| | . 05a4a45 16h Initial commit -+ feature ⊟ · · · · ../repo.feature 05a4a45 · · ++ feature ⊟         ../repo.feature 05a4a45     + feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file + feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file diff --git a/tests/snapshots/integration__integration_tests__list__list_task_dag_detached_head.snap b/tests/snapshots/integration__integration_tests__list__list_task_dag_detached_head.snap index 0094a301e8..3c2a9428dd 100644 --- a/tests/snapshots/integration__integration_tests__list__list_task_dag_detached_head.snap +++ b/tests/snapshots/integration__integration_tests__list__list_task_dag_detached_head.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,7 +58,7 @@ success: true exit_code: 0 ----- stdout ----- Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ 05a4a45 ⚑^ . 05a4a45 16h Initial commit +@ 05a4a45 ⊘^ . 05a4a45 16h Initial commit + feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file + feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file diff --git a/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap b/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap new file mode 100644 index 0000000000..3cf69e3c3f --- /dev/null +++ b/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap @@ -0,0 +1,69 @@ +--- +source: tests/integration_tests/list_layout.rs +info: + program: wt + args: + - list + env: + APPDATA: "[TEST_CONFIG_HOME]" + CLAUDE_CONFIG_DIR: "[TEST_CLAUDE_CONFIG]" + CLICOLOR_FORCE: "1" + COLUMNS: "120" + GIT_ALLOW_PROTOCOL: file + GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z" + GIT_AUTHOR_EMAIL: test@example.com + GIT_AUTHOR_NAME: Test User + GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z" + GIT_COMMITTER_EMAIL: test@example.com + GIT_COMMITTER_NAME: Test User + GIT_CONFIG_COUNT: "2" + GIT_CONFIG_GLOBAL: /nonexistent/wt/gitconfig + GIT_CONFIG_KEY_0: user.useConfigOnly + GIT_CONFIG_KEY_1: rerere.enabled + GIT_CONFIG_SYSTEM: /nonexistent/wt/gitconfig + GIT_CONFIG_VALUE_0: "true" + GIT_CONFIG_VALUE_1: "false" + GIT_TERMINAL_PROMPT: "0" + HOME: "[TEST_HOME]" + LANG: C + LC_ALL: C + LLVM_PROFILE_FILE: "[LLVM_PROFILE_FILE]" + OPENCODE_CONFIG_DIR: "[TEST_OPENCODE_CONFIG]" + PATH: "[PATH]" + TERM: alacritty + USERPROFILE: "[TEST_HOME]" + WORKTRUNK_APPROVALS_PATH: "[TEST_APPROVALS]" + WORKTRUNK_CONFIG_PATH: "[TEST_CONFIG]" + WORKTRUNK_SYSTEM_CONFIG_PATH: "[TEST_SYSTEM_CONFIG]" + WORKTRUNK_TEST_BASH_INSTALLED: "0" + WORKTRUNK_TEST_CLAUDE_INSTALLED: "0" + WORKTRUNK_TEST_CODEX_INSTALLED: "0" + WORKTRUNK_TEST_DELAYED_STREAM_MS: "-1" + WORKTRUNK_TEST_EPOCH: "1735776000" + WORKTRUNK_TEST_FISH_INSTALLED: "0" + WORKTRUNK_TEST_GEMINI_INSTALLED: "0" + WORKTRUNK_TEST_MOCK_CONFIG_DIR: "[TEST_MOCK_CONFIG]" + WORKTRUNK_TEST_NUSHELL_ENV: "0" + WORKTRUNK_TEST_OPENCODE_INSTALLED: "0" + WORKTRUNK_TEST_PARENT_SHELL: "" + WORKTRUNK_TEST_POWERSHELL_ENV: "0" + WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" + WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" + WORKTRUNK_TEST_ZSH_INSTALLED: "0" + XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" +--- +success: true +exit_code: 0 +----- stdout ----- + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial comm… ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-… ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-… ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-… ++ 05a4a45 ⊘_ ../repo.feature-detached 05a4a45 16h Initial comm… ++ feature-gone ⊟         ../repo.feature-gone 05a4a45     + +----- stderr ----- + +○ Showing 6 worktrees, 3 ahead From a97eb0dbb7df10ed3c7faabb0021800917adb060 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 1 Sep 2026 21:01:53 -0700 Subject: [PATCH 08/17] list: fix four table-alignment defects (#93) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Numeric headers sat over the deletions half of their field. A diff column is two right-aligned halves — `+999` then `-999` — so a right-aligned `HEAD±` stood over the second one and a lone `+1` landed to its left, out from under the header naming it. The header now centres, straddling the separator. Age was left-aligned, so `now` and `4m` didn't line up on the unit. It is a single right-aligned value, and its header goes right with it. Data rows carried up to 14 characters of trailing padding the header line never had — padding places a cell, and past the last one it places nothing, but a reader who selects the row gets it anyway. The footer ran to 67 characters in a 40-column terminal the table itself fit; the hidden-column list grows with every column a narrow terminal drops, so it is longest exactly where there is least room. It now wraps at the terminal width, continuations indented under the text. `Path` still renders `.` for the primary worktree — that is the same `.` `git` and the shell use for the repo root, and left alone deliberately. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01BGS51k25qzfeJEJ8dfxEEY --- .claude/skills/writing-user-outputs/SKILL.md | 14 +- README.md | 6 +- docs/src/content/docs/claude-code.md | 10 +- docs/src/content/docs/config.md | 10 +- docs/src/content/docs/list.md | 14 +- docs/src/content/docs/tips-patterns.md | 2 +- docs/src/content/docs/worktrunk.md | 6 +- docs/src/generated/terminal-styles.json | 142 +++++++++--------- .../skills/worktrunk/reference/README.md | 6 +- .../skills/worktrunk/reference/claude-code.md | 10 +- .../skills/worktrunk/reference/config.md | 10 +- .../skills/worktrunk/reference/list.md | 14 +- .../worktrunk/reference/tips-patterns.md | 2 +- .../skills/worktrunk/reference/worktrunk.md | 6 +- skills/worktrunk/reference/claude-code.md | 10 +- skills/worktrunk/reference/config.md | 10 +- skills/worktrunk/reference/list.md | 14 +- skills/worktrunk/reference/tips-patterns.md | 2 +- skills/worktrunk/reference/worktrunk.md | 6 +- src/cli/mod.rs | 14 +- src/commands/list/collect/mod.rs | 1 + src/commands/list/mod.rs | 41 +++-- src/commands/list/render.rs | 84 ++++++++--- ...ests__snapshot_path_yields_to_summary.snap | 10 +- src/styling/line.rs | 20 +++ ...are_repo_commands_from_bare_directory.snap | 5 +- ...are_repo_identifies_primary_correctly.snap | 9 +- ...y__bare_repo_list_shows_no_bare_entry.snap | 5 +- ..._repository__bare_repo_list_worktrees.snap | 7 +- ...itory__nested_bare_repo_list_snapshot.snap | 7 +- ...gration_tests__ci_status__azure_no_ci.snap | 13 +- ...sts__ci_status__azure_pipeline_passed.snap | 13 +- ...tatus__azure_pipeline_retriable_error.snap | 13 +- ..._tests__ci_status__azure_pr_conflicts.snap | 13 +- ...status__azure_pr_list_retriable_error.snap | 13 +- ...ion_tests__ci_status__azure_pr_queued.snap | 13 +- ...ests__ci_status__azure_stale_pipeline.snap | 13 +- ...sts__ci_status__filters_by_repo_owner.snap | 13 +- ...n_tests__ci_status__gitea_blanked_500.snap | 13 +- ...tests__ci_status__gitea_commit_status.snap | 13 +- ...tatus__gitea_commit_status_error_body.snap | 13 +- ...__gitea_commit_status_retriable_error.snap | 13 +- ...ation_tests__ci_status__gitea_fork_pr.snap | 13 +- ...gration_tests__ci_status__gitea_no_ci.snap | 13 +- ...ion_tests__ci_status__gitea_not_found.snap | 13 +- ..._tests__ci_status__gitea_pr_conflicts.snap | 13 +- ...tests__ci_status__gitea_pr_error_body.snap | 13 +- ...itea_remote_branch_uses_branch_remote.snap | 13 +- ...sts__ci_status__gitea_retriable_error.snap | 13 +- ...i_status__github_pr_changes_requested.snap | 13 +- ...tests__ci_status__github_pr_conflicts.snap | 13 +- ...ests__ci_status__gitlab_ci_rate_limit.snap | 13 +- ...s__gitlab_empty_mr_list_no_project_id.snap | 13 +- ..._status__gitlab_filters_by_project_id.snap | 13 +- ...tests__ci_status__gitlab_mr_conflicts.snap | 13 +- ...ts__ci_status__gitlab_mr_view_failure.snap | 13 +- ...us__gitlab_multiple_mrs_no_project_id.snap | 13 +- ...s__ci_status__gitlab_pipeline_success.snap | 13 +- ...tatus__gitlab_single_mr_no_project_id.snap | 13 +- ...ion_tests__ci_status__gitlab_stale_mr.snap | 13 +- ..._full_with_configured_platform_github.snap | 13 +- ...__list_full_with_gitea_forge_platform.snap | 13 +- ..._status__list_full_with_gitlab_remote.snap | 13 +- ...full_with_invalid_configured_platform.snap | 13 +- ...ntegration_tests__ci_status__stale_pr.snap | 13 +- ...ests__ci_status__url_based_pushremote.snap | 13 +- ...eneration_project_level_shows_warning.snap | 11 +- ...mmit_generation_section_shows_warning.snap | 11 +- ...cated_template_variables_show_warning.snap | 11 +- ...plate_variables_verbose_shows_content.snap | 11 +- ...t_config_path_not_found_shows_warning.snap | 11 +- ...tegration_tests__help__help_list_long.snap | 14 +- ...tion_tests__help__help_list_narrow_80.snap | 14 +- ...sts__list__commit_conflicts_with_full.snap | 13 +- ...__list__commit_conflicts_without_full.snap | 13 +- ...ation_tests__list__list_bisect_symbol.snap | 13 +- ...s__list__list_branch_only_with_status.snap | 13 +- ...nches_with_nonexistent_default_branch.snap | 10 +- ...ation_tests__list__list_detached_head.snap | 10 +- ..._list__list_detached_head_in_worktree.snap | 12 +- ...on_tests__list__list_duplicate_branch.snap | 15 +- ...egration_tests__list__list_empty_repo.snap | 5 +- ..._full_with_nonexistent_default_branch.snap | 10 +- ...sts__list__list_handles_orphan_branch.snap | 13 +- ...sts__list__list_large_diffs_alignment.snap | 17 ++- ...on_tests__list__list_locked_no_reason.snap | 13 +- ...ion_tests__list__list_locked_worktree.snap | 13 +- ...tests__list__list_long_commit_message.snap | 11 +- ...list_many_worktrees_with_varied_stats.snap | 19 +-- ...st__list_maximum_working_tree_symbols.snap | 13 +- ..._tests__list__list_multiple_worktrees.snap | 11 +- ...ist_nested_worktree_current_indicator.snap | 13 +- ...tests__list__list_no_progressive_flag.snap | 13 +- ...tion_tests__list__list_ordering_rules.snap | 19 +-- ...__list__list_previous_worktree_gutter.snap | 13 +- ...ist__list_primary_on_different_branch.snap | 11 +- ..._list__list_progressive_with_branches.snap | 15 +- ...list__list_shows_warning_on_git_error.snap | 13 +- ...ips_operations_for_prunable_worktrees.snap | 12 +- ...list_status_column_padding_with_emoji.snap | 17 ++- ...ts__list__list_task_dag_detached_head.snap | 10 +- ...__list__list_task_dag_full_with_diffs.snap | 11 +- ...s__list__list_task_dag_many_worktrees.snap | 31 ++-- ...t__list_task_dag_with_locked_worktree.snap | 15 +- ...ts__list__list_task_dag_with_upstream.snap | 15 +- ...ts__list__list_unicode_commit_message.snap | 13 +- ...t_user_marker_with_special_characters.snap | 15 +- ...warns_when_commit_details_batch_fails.snap | 13 +- ..._when_default_branch_missing_worktree.snap | 11 +- ..._tests__list__list_with_branches_flag.snap | 19 +-- ..._list_with_branches_flag_no_available.snap | 11 +- ...list_with_branches_flag_only_branches.snap | 17 ++- ...gration_tests__list__list_with_c_flag.snap | 11 +- ..._list_with_nonexistent_default_branch.snap | 10 +- ..._list__list_with_remotes_and_branches.snap | 19 +-- ...sts__list__list_with_remotes_and_full.snap | 13 +- ...with_remotes_filters_tracked_branches.snap | 13 +- ...ith_remotes_filters_tracked_worktrees.snap | 15 +- ...n_tests__list__list_with_remotes_flag.snap | 15 +- ...on_tests__list__list_with_user_marker.snap | 11 +- ...n_tests__list__maximum_status_symbols.snap | 13 +- ...st__maximum_status_with_git_operation.snap | 13 +- ...egration_tests__list__quickstart_list.snap | 6 +- ...tion_tests__list__readme_example_list.snap | 10 +- ...s__list__readme_example_list_branches.snap | 2 +- ...tests__list__readme_example_list_full.snap | 2 +- ...sts__list__readme_example_list_marker.snap | 10 +- ...ts__list__task_dag_ordering_stability.snap | 19 +-- ...tests__list__tips_dev_server_workflow.snap | 2 +- ...n_tests__list__with_upstream_tracking.snap | 23 +-- ...ist__working_tree_conflicts_with_full.snap | 13 +- ...__working_tree_conflicts_without_full.snap | 13 +- ..._config__list_config_branches_enabled.snap | 13 +- ...t_config_branches_flag_overrides_file.snap | 13 +- ..._config_cli_override_after_subcommand.snap | 13 +- ...__list_config_cli_override_beats_file.snap | 13 +- ...ride_deprecated_key_silently_migrated.snap | 13 +- ...li_override_malformed_warns_on_stderr.snap | 13 +- ...olumns_env_override_not_yet_supported.snap | 11 +- ..._deprecated_type_mismatch_drops_layer.snap | 11 +- ...rride_bad_value_preserves_file_config.snap | 13 +- ...nv_override_bad_value_warns_on_stderr.snap | 11 +- ...ig_env_override_preserves_file_fields.snap | 13 +- ...onfig_env_override_validation_failure.snap | 11 +- ...config__list_config_full_and_branches.snap | 13 +- ...list_config__list_config_full_enabled.snap | 11 +- ...nfig_malformed_config_warns_on_stderr.snap | 11 +- ...med_non_section_field_warns_on_stderr.snap | 11 +- ...ormed_system_config_non_section_field.snap | 11 +- ...lformed_system_config_warns_on_stderr.snap | 11 +- ...nfig_validation_error_warns_on_stderr.snap | 11 +- ...ustom_column_empty_everywhere_dropped.snap | 11 +- ...sts__list_config__list_custom_columns.snap | 11 +- ...on_tests__list_config__list_no_config.snap | 11 +- ..._list_config__list_project_url_column.snap | 11 +- ...yout__detached_and_prunable_width_120.snap | 14 +- ...tion_tests__list_layout__diverse_rows.snap | 13 +- ...s__list_layout__long_branch_width_100.snap | 12 +- ...s__list_layout__long_branch_width_200.snap | 12 +- ...ts__list_layout__long_branch_width_60.snap | 15 +- ...sts__list_layout__no_remote_width_100.snap | 12 +- ...gration_tests__list_layout__width_120.snap | 12 +- ...egration_tests__list_layout__width_30.snap | 6 +- ...egration_tests__list_layout__width_60.snap | 15 +- ...t_message_with_directive_not_executed.snap | 13 +- ...pe_like_branch_names_displayed_safely.snap | 13 +- ...omote__promote_shows_mismatch_in_list.snap | 13 +- ...sts__switch__list_with_relative_paths.snap | 13 +- ...cker__switch_picker_abort_escape_list.snap | 2 +- ...icker__switch_picker_preview_log_list.snap | 2 +- 170 files changed, 1239 insertions(+), 1011 deletions(-) diff --git a/.claude/skills/writing-user-outputs/SKILL.md b/.claude/skills/writing-user-outputs/SKILL.md index 38d55bfbdc..a56709d614 100644 --- a/.claude/skills/writing-user-outputs/SKILL.md +++ b/.claude/skills/writing-user-outputs/SKILL.md @@ -1095,8 +1095,18 @@ eprintln!("{}", success_message(format!( ## Table Column Alignment -- **Text columns** (Branch, Path): left-aligned -- **Numeric columns** (HEAD±, main↕): right-aligned +- **Text columns** (Branch, Path, Message): left-aligned +- **Single-value numeric columns** (Age): right-aligned, so `now` and `4m` line + up on the unit +- **Diff columns** (HEAD±, main↕): two right-aligned halves either side of a + separator (`+999 -999`) + +A header follows its content, except over a diff column, where it centres: +pushed to either edge it stands over one half and reads as that half's label, +leaving a lone `+1` stranded left of `HEAD±`. + +Rows carry no trailing padding. Padding places a cell; past the last one it +places nothing, and a reader who selects the row gets it anyway. ## Snapshot Testing diff --git a/README.md b/README.md index 459946671c..4751346098 100644 --- a/README.md +++ b/README.md @@ -164,9 +164,9 @@ This creates a new branch and worktree, then switches to it. Do your work, then ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… -^ main ^⇡ ⇡1 0e631ad 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… +^ main ^⇡ ⇡1 0e631ad 1d Initial commit ○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` diff --git a/docs/src/content/docs/claude-code.md b/docs/src/content/docs/claude-code.md index e300c33480..a4e35fbbf2 100644 --- a/docs/src/content/docs/claude-code.md +++ b/docs/src/content/docs/claude-code.md @@ -79,11 +79,11 @@ The Claude Code, Codex, OpenCode, and Gemini plugins track agent sessions with s ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡1 . 33323bc 1d Initial commit -+ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints -+ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component -+ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡1 . 33323bc 1d Initial commit ++ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints ++ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component ++ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit ○ Showing 4 worktrees, 2 with changes, 2 ahead ``` diff --git a/docs/src/content/docs/config.md b/docs/src/content/docs/config.md index 4d27a5924a..d54bdf5cbb 100644 --- a/docs/src/content/docs/config.md +++ b/docs/src/content/docs/config.md @@ -1381,11 +1381,11 @@ Markers appear at the end of the Status column, after git symbols: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ main ^⇡ ⇡1 33323bc 1d Initial commit -+ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp… -+ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… -+ wip-docs ? – +1 33323bc 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ main ^⇡ ⇡1 33323bc 1d Initial commit ++ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp… ++ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… ++ wip-docs ? – +1 33323bc 1d Initial commit ○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path ``` diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index 23564207f6..3124f66d07 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -29,11 +29,11 @@ List all worktrees: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests -^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… -+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… -+ fix-typos _| | 41ee083 4d Merge fix-auth: h… + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests +^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… ++ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… ++ fix-typos _| | 41ee083 4d Merge fix-auth: h… ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path ``` @@ -42,7 +42,7 @@ Include CI status and LLM summaries: ```console $ wt list --full - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 @@ -55,7 +55,7 @@ Include branches that don't have worktrees: ```console $ wt list --branches --full - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 diff --git a/docs/src/content/docs/tips-patterns.md b/docs/src/content/docs/tips-patterns.md index 5c9be6000c..e0bd005a5a 100644 --- a/docs/src/content/docs/tips-patterns.md +++ b/docs/src/content/docs/tips-patterns.md @@ -66,7 +66,7 @@ The URL column in `wt list` shows each worktree's dev server: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ URL Commit + Branch Status HEAD± main↕ main…± Remote⇅ URL Commit @ main ? ^⇅ +5 ⇡1 ⇣1 http://localhost:12107 41ee083 + feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 http://localhost:10703 6814f02 + fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68 diff --git a/docs/src/content/docs/worktrunk.md b/docs/src/content/docs/worktrunk.md index d00a05dc70..7f2623149d 100644 --- a/docs/src/content/docs/worktrunk.md +++ b/docs/src/content/docs/worktrunk.md @@ -182,9 +182,9 @@ This creates a new branch and worktree, then switches to it. Do your work, then ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… -^ main ^⇡ ⇡1 0e631ad 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… +^ main ^⇡ ⇡1 0e631ad 1d Initial commit ○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` diff --git a/docs/src/generated/terminal-styles.json b/docs/src/generated/terminal-styles.json index 2db254331d..b156e7a2f6 100644 --- a/docs/src/generated/terminal-styles.json +++ b/docs/src/generated/terminal-styles.json @@ -24,7 +24,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -34,7 +34,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -44,7 +44,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -54,7 +54,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -74,7 +74,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -84,7 +84,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -166,7 +166,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -176,7 +176,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -218,7 +218,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -228,7 +228,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -257,7 +257,7 @@ } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati…\n^ main ^⇡ ⇡1 0e631ad 1d Initial commit\n\n○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati…\n^ main ^⇡ ⇡1 0e631ad 1d Initial commit\n\n○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path" }, { "lines": [ @@ -284,7 +284,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -294,7 +294,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -304,7 +304,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -314,7 +314,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -334,7 +334,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -344,7 +344,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -457,7 +457,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -467,7 +467,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -520,7 +520,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -530,7 +530,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -613,7 +613,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -623,7 +623,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -675,7 +675,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -685,7 +685,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -714,7 +714,7 @@ } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests\n^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h…\n+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token…\n+ fix-typos _| | 41ee083 4d Merge fix-auth: h…\n\n○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests\n^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h…\n+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token…\n+ fix-typos _| | 41ee083 4d Merge fix-auth: h…\n\n○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path" }, { "lines": [ @@ -741,7 +741,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -751,7 +751,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -761,7 +761,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -771,7 +771,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -791,7 +791,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -801,7 +801,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -843,7 +843,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -853,7 +853,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -905,7 +905,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -915,7 +915,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -987,7 +987,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -997,7 +997,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1049,7 +1049,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1059,7 +1059,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1088,7 +1088,7 @@ } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ main ^⇡ ⇡1 33323bc 1d Initial commit\n+ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp…\n+ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com…\n+ wip-docs ? – +1 33323bc 1d Initial commit\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message\n@ main ^⇡ ⇡1 33323bc 1d Initial commit\n+ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp…\n+ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com…\n+ wip-docs ? – +1 33323bc 1d Initial commit\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path" }, { "lines": [ @@ -1115,7 +1115,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1125,7 +1125,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1135,7 +1135,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1145,7 +1145,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1175,7 +1175,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1185,7 +1185,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1227,7 +1227,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1237,7 +1237,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1289,7 +1289,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1299,7 +1299,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1371,7 +1371,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1381,7 +1381,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1433,7 +1433,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1443,7 +1443,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1472,7 +1472,7 @@ } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message\n@ main ^⇡ ⇡1 . 33323bc 1d Initial commit\n+ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints\n+ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component\n+ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message\n@ main ^⇡ ⇡1 . 33323bc 1d Initial commit\n+ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints\n+ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component\n+ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead" }, { "lines": [ @@ -1499,7 +1499,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1509,7 +1509,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1519,7 +1519,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1529,7 +1529,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1899,7 +1899,7 @@ } ] ], - "plain": " Branch Status HEAD± main↕ main…± Remote⇅ URL Commit\n@ main ? ^⇅ +5 ⇡1 ⇣1 http://localhost:12107 41ee083\n+ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 http://localhost:10703 6814f02\n+ fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68\n+ fix-typos _| | http://localhost:14301 41ee083\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path, Age, Message" + "plain": " Branch Status HEAD± main↕ main…± Remote⇅ URL Commit\n@ main ? ^⇅ +5 ⇡1 ⇣1 http://localhost:12107 41ee083\n+ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 http://localhost:10703 6814f02\n+ fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68\n+ fix-typos _| | http://localhost:14301 41ee083\n\n○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path, Age, Message" }, { "lines": [ @@ -1926,7 +1926,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1936,7 +1936,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1946,7 +1946,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -1956,7 +1956,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -2317,7 +2317,7 @@ } ] ], - "plain": " Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02\n^ main ^⇅ ⇡1 ⇣1 # 41ee083\n+ fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68\n+ fix-typos _| | #410 41ee083\n\n○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message" + "plain": " Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02\n^ main ^⇅ ⇡1 ⇣1 # 41ee083\n+ fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68\n+ fix-typos _| | #410 41ee083\n\n○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path, Age, Message" }, { "lines": [ @@ -2344,7 +2344,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -2354,7 +2354,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -2364,7 +2364,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -2374,7 +2374,7 @@ }, { "classes": [], - "text": " " + "text": " " }, { "classes": [ @@ -2853,7 +2853,7 @@ } ] ], - "plain": " Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02\n^ main ^⇅ ⇡1 ⇣1 # 41ee083\n+ fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68\n+ fix-typos _| | #410 41ee083\n/ exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922\n/ wip /↕ ↑1 ↓1 +33 Start API documentation b40716d\n\n○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message" + "plain": " Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit\n@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02\n^ main ^⇅ ⇡1 ⇣1 # 41ee083\n+ fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68\n+ fix-typos _| | #410 41ee083\n/ exp /↕ ↑2 ↓1 +137 Explore GraphQL schema and resolvers 9637922\n/ wip /↕ ↑1 ↓1 +33 Start API documentation b40716d\n\n○ Showing 4 worktrees, 2 branches, 1 with changes, 4 ahead, hidden: Path, Age, Message" }, { "lines": [ diff --git a/plugins/worktrunk/skills/worktrunk/reference/README.md b/plugins/worktrunk/skills/worktrunk/reference/README.md index 459946671c..4751346098 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/README.md +++ b/plugins/worktrunk/skills/worktrunk/reference/README.md @@ -164,9 +164,9 @@ This creates a new branch and worktree, then switches to it. Do your work, then ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… -^ main ^⇡ ⇡1 0e631ad 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… +^ main ^⇡ ⇡1 0e631ad 1d Initial commit ○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` diff --git a/plugins/worktrunk/skills/worktrunk/reference/claude-code.md b/plugins/worktrunk/skills/worktrunk/reference/claude-code.md index 21a6163d67..f563383024 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/claude-code.md +++ b/plugins/worktrunk/skills/worktrunk/reference/claude-code.md @@ -73,11 +73,11 @@ The Claude Code, Codex, OpenCode, and Gemini plugins track agent sessions with s ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡1 . 33323bc 1d Initial commit -+ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints -+ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component -+ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡1 . 33323bc 1d Initial commit ++ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints ++ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component ++ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit ○ Showing 4 worktrees, 2 with changes, 2 ahead ``` diff --git a/plugins/worktrunk/skills/worktrunk/reference/config.md b/plugins/worktrunk/skills/worktrunk/reference/config.md index b915206ff4..7ce5fdbb43 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/config.md +++ b/plugins/worktrunk/skills/worktrunk/reference/config.md @@ -1373,11 +1373,11 @@ Markers appear at the end of the Status column, after git symbols: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ main ^⇡ ⇡1 33323bc 1d Initial commit -+ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp… -+ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… -+ wip-docs ? – +1 33323bc 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ main ^⇡ ⇡1 33323bc 1d Initial commit ++ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp… ++ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… ++ wip-docs ? – +1 33323bc 1d Initial commit ○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path ``` diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 87fa4c9cb1..8227ad43b9 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -16,11 +16,11 @@ List all worktrees: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests -^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… -+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… -+ fix-typos _| | 41ee083 4d Merge fix-auth: h… + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests +^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… ++ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… ++ fix-typos _| | 41ee083 4d Merge fix-auth: h… ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path ``` @@ -29,7 +29,7 @@ Include CI status and LLM summaries: ```console $ wt list --full - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 @@ -42,7 +42,7 @@ Include branches that don't have worktrees: ```console $ wt list --branches --full - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 diff --git a/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md b/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md index 4500b77422..54af9b20f1 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md +++ b/plugins/worktrunk/skills/worktrunk/reference/tips-patterns.md @@ -60,7 +60,7 @@ The URL column in `wt list` shows each worktree's dev server: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ URL Commit + Branch Status HEAD± main↕ main…± Remote⇅ URL Commit @ main ? ^⇅ +5 ⇡1 ⇣1 http://localhost:12107 41ee083 + feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 http://localhost:10703 6814f02 + fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68 diff --git a/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md b/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md index 43f75eef31..87433e0f3e 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md +++ b/plugins/worktrunk/skills/worktrunk/reference/worktrunk.md @@ -140,9 +140,9 @@ This creates a new branch and worktree, then switches to it. Do your work, then ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… -^ main ^⇡ ⇡1 0e631ad 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… +^ main ^⇡ ⇡1 0e631ad 1d Initial commit ○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` diff --git a/skills/worktrunk/reference/claude-code.md b/skills/worktrunk/reference/claude-code.md index 21a6163d67..f563383024 100644 --- a/skills/worktrunk/reference/claude-code.md +++ b/skills/worktrunk/reference/claude-code.md @@ -73,11 +73,11 @@ The Claude Code, Codex, OpenCode, and Gemini plugins track agent sessions with s ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡1 . 33323bc 1d Initial commit -+ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints -+ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component -+ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡1 . 33323bc 1d Initial commit ++ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints ++ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component ++ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit ○ Showing 4 worktrees, 2 with changes, 2 ahead ``` diff --git a/skills/worktrunk/reference/config.md b/skills/worktrunk/reference/config.md index b915206ff4..7ce5fdbb43 100644 --- a/skills/worktrunk/reference/config.md +++ b/skills/worktrunk/reference/config.md @@ -1373,11 +1373,11 @@ Markers appear at the end of the Status column, after git symbols: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ main ^⇡ ⇡1 33323bc 1d Initial commit -+ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp… -+ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… -+ wip-docs ? – +1 33323bc 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ main ^⇡ ⇡1 33323bc 1d Initial commit ++ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp… ++ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… ++ wip-docs ? – +1 33323bc 1d Initial commit ○ Showing 4 worktrees, 2 with changes, 2 ahead, hidden: Path ``` diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 87fa4c9cb1..8227ad43b9 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -16,11 +16,11 @@ List all worktrees: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests -^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… -+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… -+ fix-typos _| | 41ee083 4d Merge fix-auth: h… + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests +^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… ++ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… ++ fix-typos _| | 41ee083 4d Merge fix-auth: h… ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path ``` @@ -29,7 +29,7 @@ Include CI status and LLM summaries: ```console $ wt list --full - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 @@ -42,7 +42,7 @@ Include branches that don't have worktrees: ```console $ wt list --branches --full - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 diff --git a/skills/worktrunk/reference/tips-patterns.md b/skills/worktrunk/reference/tips-patterns.md index 4500b77422..54af9b20f1 100644 --- a/skills/worktrunk/reference/tips-patterns.md +++ b/skills/worktrunk/reference/tips-patterns.md @@ -60,7 +60,7 @@ The URL column in `wt list` shows each worktree's dev server: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ URL Commit + Branch Status HEAD± main↕ main…± Remote⇅ URL Commit @ main ? ^⇅ +5 ⇡1 ⇣1 http://localhost:12107 41ee083 + feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 http://localhost:10703 6814f02 + fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68 diff --git a/skills/worktrunk/reference/worktrunk.md b/skills/worktrunk/reference/worktrunk.md index 43f75eef31..87433e0f3e 100644 --- a/skills/worktrunk/reference/worktrunk.md +++ b/skills/worktrunk/reference/worktrunk.md @@ -140,9 +140,9 @@ This creates a new branch and worktree, then switches to it. Do your work, then ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… -^ main ^⇡ ⇡1 0e631ad 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… +^ main ^⇡ ⇡1 0e631ad 1d Initial commit ○ Showing 2 worktrees, 1 with changes, 1 ahead, hidden: Path ``` diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 2076fb035d..632c9ddc0c 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -795,11 +795,11 @@ List all worktrees: ```console $ wt list - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests -^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… -+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… -+ fix-typos _| | 41ee083 4d Merge fix-auth: h… + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests +^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… ++ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… ++ fix-typos _| | 41ee083 4d Merge fix-auth: h… ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path ``` @@ -809,7 +809,7 @@ Include CI status and LLM summaries: ```console $ wt list --full - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 @@ -823,7 +823,7 @@ Include branches that don't have worktrees: ```console $ wt list --branches --full - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 diff --git a/src/commands/list/collect/mod.rs b/src/commands/list/collect/mod.rs index 66011af329..e239b7f669 100644 --- a/src/commands/list/collect/mod.rs +++ b/src/commands/list/collect/mod.rs @@ -1966,6 +1966,7 @@ pub fn collect( show_branches || show_remotes, &layout.hidden_columns, timed_out_count, + None, ), }); diff --git a/src/commands/list/mod.rs b/src/commands/list/mod.rs index 117c386664..29a7521893 100644 --- a/src/commands/list/mod.rs +++ b/src/commands/list/mod.rs @@ -150,7 +150,7 @@ use anstyle::Style; use model::{BranchScope, ItemKind, ListData, ListItem}; use progressive::RenderTarget; use worktrunk::git::Repository; -use worktrunk::styling::{INFO_SYMBOL, eprintln}; +use worktrunk::styling::{INFO_SYMBOL, eprintln, terminal_width, wrap_styled_text}; use crate::output::print_json; @@ -378,26 +378,47 @@ impl SummaryMetrics { /// that failed instead get a named entry in the warning that follows the /// table, and that warning carries its own count — repeating it here would /// print the same number twice on adjacent lines. +/// +/// `max_width` bounds the line, following [`format_with_gutter`]'s +/// convention: `None` detects the terminal, and no detectable width wraps +/// nothing. The hidden-column list grows with every column a narrow terminal +/// drops, so the footer is longest exactly where there is least room for it — +/// at 40 columns it ran to 67 for a table that fit. Continuations indent +/// under the text, keeping them subordinate to the `○`. +/// +/// [`format_with_gutter`]: worktrunk::styling::format_with_gutter pub(crate) fn format_summary_message( items: &[ListItem], show_branches: bool, hidden_columns: &[String], timed_out_count: usize, + max_width: Option, ) -> String { + const INDENT: &str = " "; // symbol + space, so continuations align under the text + let metrics = SummaryMetrics::from_items(items); let dim = Style::new().dimmed(); let summary = metrics .summary_parts(show_branches, hidden_columns) .join(", "); - if timed_out_count > 0 { + let body = if timed_out_count > 0 { let plural = if timed_out_count == 1 { "" } else { "s" }; - format!( - "{INFO_SYMBOL} {dim}Showing {summary}; {timed_out_count} task{plural} timed out{dim:#}" - ) + format!("Showing {summary}; {timed_out_count} task{plural} timed out") } else { - format!("{INFO_SYMBOL} {dim}Showing {summary}{dim:#}") - } + format!("Showing {summary}") + }; + + let width = max_width.or_else(terminal_width).unwrap_or(usize::MAX); + let body = wrap_styled_text(&body, width.saturating_sub(INDENT.len())) + .iter() + // The wrapper keeps the space it broke on, which would land a + // continuation one column right of the indent. + .map(|line| line.trim()) + .collect::>() + .join(&format!("\n{INDENT}")); + + format!("{INFO_SYMBOL} {dim}{body}{dim:#}") } #[cfg(test)] @@ -563,10 +584,10 @@ mod tests { // Nothing timed out. Failures alone leave the footer untouched — the // warning after the table names them and carries their count. - assert_snapshot!(format_summary_message(&[], false, &[], 0), @"○ Showing 0 worktrees"); + assert_snapshot!(format_summary_message(&[], false, &[], 0, None), @"○ Showing 0 worktrees"); // Single timeout - assert_snapshot!(format_summary_message(&[], false, &[], 1), @"○ Showing 0 worktrees; 1 task timed out"); + assert_snapshot!(format_summary_message(&[], false, &[], 1, None), @"○ Showing 0 worktrees; 1 task timed out"); // Several timeouts - assert_snapshot!(format_summary_message(&[], false, &[], 3), @"○ Showing 0 worktrees; 3 tasks timed out"); + assert_snapshot!(format_summary_message(&[], false, &[], 3, None), @"○ Showing 0 worktrees; 3 tasks timed out"); } } diff --git a/src/commands/list/render.rs b/src/commands/list/render.rs index ada3815885..156836daec 100644 --- a/src/commands/list/render.rs +++ b/src/commands/list/render.rs @@ -232,6 +232,11 @@ impl LayoutConfig { } } + // Padding after the last cell places nothing. A row whose rightmost + // columns are empty carried up to 14 trailing spaces the header line + // never had, which a reader inherits the moment they select the row. + line.trim_end(); + let final_width = line.width(); tracing::debug!(width = final_width, "Rendered line width: {}", final_width); @@ -248,16 +253,21 @@ impl LayoutConfig { self.render_line(|column| { let mut cell = StyledLine::new(); if !column.header.is_empty() { - // Diff columns have right-aligned values, so right-align headers too - let is_diff_column = matches!(column.format, ColumnFormat::Diff(_)); - - if is_diff_column { - // Right-align header within column width - let header_width = column.header.width(); - if header_width < column.width { - let padding = column.width - header_width; - cell.push_raw(" ".repeat(padding)); - } + let padding = column.width.saturating_sub(column.header.width()); + // A diff column is two right-aligned halves — `+999` then + // `-999` — so a header at either edge stands over one of them + // and reads as its label: pushed right, `HEAD±` sits over the + // deletions and a lone `+1` lands to its left. Centring + // straddles the separator, which is what the header names. + // Age is a single right-aligned value, so its header goes + // right with it; everything else is text, read from the left. + let leading = match column.alignment() { + CellAlignment::Split => padding / 2, + CellAlignment::Right => padding, + CellAlignment::Left => 0, + }; + if leading > 0 { + cell.push_raw(" ".repeat(leading)); } cell.push_styled(column.header.to_string(), style); @@ -392,17 +402,55 @@ impl LayoutConfig { } } +/// Which edge a column's content sits against. +#[derive(Clone, Copy, Debug)] +enum CellAlignment { + /// Text, read from the left: Branch, Path, Message, … + Left, + /// A single value whose digits and unit only line up from the right — + /// Age (`now`, `4m`, `11mo`). + Right, + /// A diff field: two right-aligned halves either side of a separator + /// (`+999 -999`), so neither edge is the field's centre of gravity. + Split, +} + impl ColumnLayout { + fn alignment(&self) -> CellAlignment { + if matches!(self.format, ColumnFormat::Diff(_)) { + CellAlignment::Split + } else if self.kind == ColumnKind::Time { + CellAlignment::Right + } else { + CellAlignment::Left + } + } + + /// Render a cell's content against the column's own edge. + fn aligned_cell(&self, content: StyledLine) -> StyledLine { + let leading = match self.alignment() { + CellAlignment::Left => return content, + // A split column's own renderer places the halves; only a + // single-glyph stand-in reaches here, and it goes to the right + // edge the way the whole field does. + CellAlignment::Right | CellAlignment::Split => { + self.width.saturating_sub(content.width()) + } + }; + if leading == 0 { + return content; + } + let mut cell = StyledLine::new(); + cell.push_raw(" ".repeat(leading)); + cell.extend(content); + cell + } + /// Render a placeholder indicator (loading or skipped state). - /// Right-aligns for diff columns, left-aligns otherwise. fn placeholder_cell(&self, symbol: &str) -> StyledLine { let mut cell = StyledLine::new(); - if matches!(self.format, ColumnFormat::Diff(_)) { - let padding = self.width.saturating_sub(symbol.width()); - cell.push_raw(" ".repeat(padding)); - } cell.push_styled(symbol, Style::new().dimmed()); - cell + self.aligned_cell(cell) } /// Render a text cell with optional style, truncated to column width. @@ -571,7 +619,9 @@ impl ColumnLayout { format_relative_time_short(commit.timestamp), Style::new().dimmed(), ); - cell + // `now` and `4m` are different lengths with the unit at the + // end, so they only line up from the right. + self.aligned_cell(cell) } ColumnKind::Url => { // URL column: shows dev server URL from project config template diff --git a/src/commands/list/snapshots/wt__commands__list__layout__tests__snapshot_path_yields_to_summary.snap b/src/commands/list/snapshots/wt__commands__list__layout__tests__snapshot_path_yields_to_summary.snap index 2768799141..9bcd7413e8 100644 --- a/src/commands/list/snapshots/wt__commands__list__layout__tests__snapshot_path_yields_to_summary.snap +++ b/src/commands/list/snapshots/wt__commands__list__layout__tests__snapshot_path_yields_to_summary.snap @@ -2,8 +2,8 @@ source: src/commands/list/layout.rs expression: table --- - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit Age Message -@ main · ···· | a620bcf 1y Some comm… -+ hourly-maintenance · ···· ↑2 · · a620bcf 1y Some comm… -+ lab-continue · ···· ↑1 ↓2 +28 -1 Add extend and block insert in Markdown parser | a620bcf 1y Some comm… -+ dry-run-pager · ···· ↑3 ↓1 · | a620bcf 1y Some comm… + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit Age Message +@ main · ···· | a620bcf 1y Some comm… ++ hourly-maintenance · ···· ↑2 · · a620bcf 1y Some comm… ++ lab-continue · ···· ↑1 ↓2 +28 -1 Add extend and block insert in Markdown parser | a620bcf 1y Some comm… ++ dry-run-pager · ···· ↑3 ↓1 · | a620bcf 1y Some comm… diff --git a/src/styling/line.rs b/src/styling/line.rs index ecda895606..c996636a9e 100644 --- a/src/styling/line.rs +++ b/src/styling/line.rs @@ -149,6 +149,26 @@ impl StyledLine { self.segments.iter().map(|s| s.width()).sum() } + /// Drop trailing spaces. + /// + /// Padding places a cell; past the last one it places nothing, and a + /// reader who selects the line gets it anyway. A segment emptied by the + /// trim goes too, so no bare escape pair is left standing around nothing. + pub fn trim_end(&mut self) { + while let Some(last) = self.segments.last_mut() { + let trimmed = last.text.trim_end_matches(' '); + if trimmed.len() == last.text.len() { + return; + } + if trimmed.is_empty() { + self.segments.pop(); + } else { + last.text.truncate(trimmed.len()); + return; + } + } + } + /// Renders the entire line with ANSI escape codes pub fn render(&self) -> String { self.segments.iter().map(|s| s.render()).collect() diff --git a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_commands_from_bare_directory.snap b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_commands_from_bare_directory.snap index 4c8c45784c..e627ec9cc5 100644 --- a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_commands_from_bare_directory.snap +++ b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_commands_from_bare_directory.snap @@ -40,14 +40,15 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -^ main ^ . b834638 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +^ main ^ . b834638 1d Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_identifies_primary_correctly.snap b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_identifies_primary_correctly.snap index 36ba5bca49..3523527d30 100644 --- a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_identifies_primary_correctly.snap +++ b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_identifies_primary_correctly.snap @@ -40,16 +40,17 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^ . b37a293 1d Main commit -+ feature1 _ ../feature1 b37a293 1d Main commit -+ feature2 _ ../feature2 b37a293 1d Main commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^ . b37a293 1d Main commit ++ feature1 _ ../feature1 b37a293 1d Main commit ++ feature2 _ ../feature2 b37a293 1d Main commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_shows_no_bare_entry.snap b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_shows_no_bare_entry.snap index 84f255c42d..7077eb485c 100644 --- a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_shows_no_bare_entry.snap +++ b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_shows_no_bare_entry.snap @@ -40,14 +40,15 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^ . b834638 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^ . b834638 1d Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_worktrees.snap b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_worktrees.snap index 0780f58e11..0dd510d291 100644 --- a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_worktrees.snap +++ b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_worktrees.snap @@ -40,15 +40,16 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^ . 6c3da84 1d Initial commit on main -+ feature ↑ ↑1 +1 -1 ../feature 72413a3 1d Work on feature + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^ . 6c3da84 1d Initial commit on main ++ feature ↑ ↑1 +1 -1 ../feature 72413a3 1d Work on feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__bare_repository__nested_bare_repo_list_snapshot.snap b/tests/snapshots/integration__integration_tests__bare_repository__nested_bare_repo_list_snapshot.snap index d56dc3da91..e0722bfcca 100644 --- a/tests/snapshots/integration__integration_tests__bare_repository__nested_bare_repo_list_snapshot.snap +++ b/tests/snapshots/integration__integration_tests__bare_repository__nested_bare_repo_list_snapshot.snap @@ -40,15 +40,16 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^ . a6c13b1 1d Initial -+ feature _ ../feature a6c13b1 1d Initial + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^ . a6c13b1 1d Initial ++ feature _ ../feature a6c13b1 1d Initial ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__azure_no_ci.snap b/tests/snapshots/integration__integration_tests__ci_status__azure_no_ci.snap index b6a1af1518..902baf5308 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__azure_no_ci.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__azure_no_ci.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature _| | ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature _| | ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__azure_pipeline_passed.snap b/tests/snapshots/integration__integration_tests__ci_status__azure_pipeline_passed.snap index 375bf8f44e..bb58051b13 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__azure_pipeline_passed.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__azure_pipeline_passed.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | # . 05a4a45 16h Initial commit -+ feature _| | # ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | # ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | # ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | # ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | # . 05a4a45 16h Initial commit ++ feature _| | # ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | # ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | # ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | # ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__azure_pipeline_retriable_error.snap b/tests/snapshots/integration__integration_tests__ci_status__azure_pipeline_retriable_error.snap index 81ba74782b..abb9f17cad 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__azure_pipeline_retriable_error.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__azure_pipeline_retriable_error.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | ⚠ . 05a4a45 16h Initial commit -+ feature _| | ⚠ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | ⚠ . 05a4a45 16h Initial commit ++ feature _| | ⚠ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__azure_pr_conflicts.snap b/tests/snapshots/integration__integration_tests__ci_status__azure_pr_conflicts.snap index bef4ed638d..fe0b4bef30 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__azure_pr_conflicts.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__azure_pr_conflicts.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | #7 . 05a4a45 16h Initial commit -+ feature _| | #7 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | #7 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | #7 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | #7 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | #7 . 05a4a45 16h Initial commit ++ feature _| | #7 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | #7 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | #7 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | #7 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__azure_pr_list_retriable_error.snap b/tests/snapshots/integration__integration_tests__ci_status__azure_pr_list_retriable_error.snap index 81ba74782b..abb9f17cad 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__azure_pr_list_retriable_error.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__azure_pr_list_retriable_error.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | ⚠ . 05a4a45 16h Initial commit -+ feature _| | ⚠ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | ⚠ . 05a4a45 16h Initial commit ++ feature _| | ⚠ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__azure_pr_queued.snap b/tests/snapshots/integration__integration_tests__ci_status__azure_pr_queued.snap index b7806f02af..7d0b444116 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__azure_pr_queued.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__azure_pr_queued.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | #7 . 05a4a45 16h Initial commit -+ feature _| | #7 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | #7 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | #7 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | #7 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | #7 . 05a4a45 16h Initial commit ++ feature _| | #7 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | #7 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | #7 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | #7 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__azure_stale_pipeline.snap b/tests/snapshots/integration__integration_tests__ci_status__azure_stale_pipeline.snap index 31b1bc2602..7f0225cac7 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__azure_stale_pipeline.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__azure_stale_pipeline.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | # . 05a4a45 16h Initial commit -+ feature _| | # ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | # ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | # ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | # ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | # . 05a4a45 16h Initial commit ++ feature _| | # ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | # ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | # ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | # ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__filters_by_repo_owner.snap b/tests/snapshots/integration__integration_tests__ci_status__filters_by_repo_owner.snap index 76fdbb5e66..fd6f2b3411 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__filters_by_repo_owner.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__filters_by_repo_owner.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | #1 . 05a4a45 16h Initial commit -+ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | #1 . 05a4a45 16h Initial commit ++ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_blanked_500.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_blanked_500.snap index b3573c96e4..6b281e0739 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_blanked_500.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_blanked_500.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | ⚠ . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | ⚠ . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status.snap index d4bb6efee2..a695543b2e 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | # ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | # ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status_error_body.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status_error_body.snap index 431752ceff..8517469f70 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status_error_body.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status_error_body.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status_retriable_error.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status_retriable_error.snap index 431752ceff..8517469f70 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status_retriable_error.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_commit_status_retriable_error.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_fork_pr.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_fork_pr.snap index c4de6522dd..b4b9a09756 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_fork_pr.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_fork_pr.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | #7 ../repo.feature 9a059eb 1d feat: gitea fork feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | #7 ../repo.feature 9a059eb 1d feat: gitea fork feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_no_ci.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_no_ci.snap index 033ac735ac..4ce4a15ef4 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_no_ci.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_no_ci.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_not_found.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_not_found.snap index 033ac735ac..4ce4a15ef4 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_not_found.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_not_found.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_pr_conflicts.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_pr_conflicts.snap index 0d3ee87bb4..126d5493fc 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_pr_conflicts.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_pr_conflicts.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | #7 ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | #7 ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_pr_error_body.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_pr_error_body.snap index b3573c96e4..6b281e0739 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_pr_error_body.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_pr_error_body.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | ⚠ . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | ⚠ . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_remote_branch_uses_branch_remote.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_remote_branch_uses_branch_remote.snap index 56b3d623d3..747f7f2062 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_remote_branch_uses_branch_remote.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_remote_branch_uses_branch_remote.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,12 +58,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -| fork/feature-remote /↑ ↑1 +1 # 86db681 1d feat: fork feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +| fork/feature-remote /↑ ↑1 +1 # 86db681 1d feat: fork feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitea_retriable_error.snap b/tests/snapshots/integration__integration_tests__ci_status__gitea_retriable_error.snap index b3573c96e4..6b281e0739 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitea_retriable_error.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitea_retriable_error.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | ⚠ . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | ⚠ . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑| ↑1 +1 | ⚠ ../repo.feature 9c1e078 1d feat: gitea feature ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__github_pr_changes_requested.snap b/tests/snapshots/integration__integration_tests__ci_status__github_pr_changes_requested.snap index c344382802..6fd2cb5698 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__github_pr_changes_requested.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__github_pr_changes_requested.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | #1 . 05a4a45 16h Initial commit -+ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | #1 . 05a4a45 16h Initial commit ++ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__github_pr_conflicts.snap b/tests/snapshots/integration__integration_tests__ci_status__github_pr_conflicts.snap index 0ab1cd6559..888af87dec 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__github_pr_conflicts.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__github_pr_conflicts.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | #1 . 05a4a45 16h Initial commit -+ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | #1 . 05a4a45 16h Initial commit ++ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_ci_rate_limit.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_ci_rate_limit.snap index 81ba74782b..abb9f17cad 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_ci_rate_limit.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_ci_rate_limit.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | ⚠ . 05a4a45 16h Initial commit -+ feature _| | ⚠ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | ⚠ . 05a4a45 16h Initial commit ++ feature _| | ⚠ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_empty_mr_list_no_project_id.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_empty_mr_list_no_project_id.snap index b6a1af1518..902baf5308 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_empty_mr_list_no_project_id.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_empty_mr_list_no_project_id.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature _| | ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature _| | ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_filters_by_project_id.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_filters_by_project_id.snap index 587e6c2956..adbb5adbcf 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_filters_by_project_id.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_filters_by_project_id.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | !1 . 05a4a45 16h Initial commit -+ feature _| | !1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | !1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | !1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | !1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | !1 . 05a4a45 16h Initial commit ++ feature _| | !1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | !1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | !1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | !1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_mr_conflicts.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_mr_conflicts.snap index c296250d2f..e2beda1bf5 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_mr_conflicts.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_mr_conflicts.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | !1 . 05a4a45 16h Initial commit -+ feature _| | !1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | !1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | !1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | !1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | !1 . 05a4a45 16h Initial commit ++ feature _| | !1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | !1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | !1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | !1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_mr_view_failure.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_mr_view_failure.snap index 138e73cace..1f0df0a17c 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_mr_view_failure.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_mr_view_failure.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | ⚠ . 05a4a45 16h Initial commit -+ feature _| | ⚠ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | ⚠ . 05a4a45 16h Initial commit ++ feature _| | ⚠ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ⚠ ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ⚠ ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ⚠ ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_multiple_mrs_no_project_id.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_multiple_mrs_no_project_id.snap index b6a1af1518..902baf5308 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_multiple_mrs_no_project_id.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_multiple_mrs_no_project_id.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature _| | ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature _| | ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_pipeline_success.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_pipeline_success.snap index 375bf8f44e..bb58051b13 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_pipeline_success.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_pipeline_success.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | # . 05a4a45 16h Initial commit -+ feature _| | # ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | # ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | # ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | # ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | # . 05a4a45 16h Initial commit ++ feature _| | # ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | # ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | # ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | # ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_single_mr_no_project_id.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_single_mr_no_project_id.snap index 587e6c2956..adbb5adbcf 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_single_mr_no_project_id.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_single_mr_no_project_id.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | !1 . 05a4a45 16h Initial commit -+ feature _| | !1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | !1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | !1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | !1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | !1 . 05a4a45 16h Initial commit ++ feature _| | !1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | !1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | !1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | !1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__gitlab_stale_mr.snap b/tests/snapshots/integration__integration_tests__ci_status__gitlab_stale_mr.snap index 702cea1c56..0346295201 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__gitlab_stale_mr.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__gitlab_stale_mr.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | !1 . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | !1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | !1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | !1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑⇡ ↑1 +1 ⇡1 !1 ../repo.feature da7d235 1d Local commit + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | !1 . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | !1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | !1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | !1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑⇡ ↑1 +1 ⇡1 !1 ../repo.feature da7d235 1d Local commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__list_full_with_configured_platform_github.snap b/tests/snapshots/integration__integration_tests__ci_status__list_full_with_configured_platform_github.snap index 5db5809048..f23717654b 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__list_full_with_configured_platform_github.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__list_full_with_configured_platform_github.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ? ^| +3 | #1 . 05a4a45 16h Initial commit -+ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ? ^| +3 | #1 . 05a4a45 16h Initial commit ++ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Project config: [ci] is deprecated in favor of [forge] diff --git a/tests/snapshots/integration__integration_tests__ci_status__list_full_with_gitea_forge_platform.snap b/tests/snapshots/integration__integration_tests__ci_status__list_full_with_gitea_forge_platform.snap index c359c7d2c3..5179c7ac84 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__list_full_with_gitea_forge_platform.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__list_full_with_gitea_forge_platform.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ? ^| +2 | . 05a4a45 16h Initial commit -+ feature _ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ? ^| +2 | . 05a4a45 16h Initial commit ++ feature _ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__list_full_with_gitlab_remote.snap b/tests/snapshots/integration__integration_tests__ci_status__list_full_with_gitlab_remote.snap index ac48ff3249..ab77a434b6 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__list_full_with_gitlab_remote.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__list_full_with_gitlab_remote.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature _ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature _ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__list_full_with_invalid_configured_platform.snap b/tests/snapshots/integration__integration_tests__ci_status__list_full_with_invalid_configured_platform.snap index c22885250b..aaf7fa9bf9 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__list_full_with_invalid_configured_platform.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__list_full_with_invalid_configured_platform.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,12 +58,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ? ^| +3 | #1 . 05a4a45 16h Initial commit -+ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ? ^| +3 | #1 . 05a4a45 16h Initial commit ++ feature _| | #1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Project config: [ci] is deprecated in favor of [forge] diff --git a/tests/snapshots/integration__integration_tests__ci_status__stale_pr.snap b/tests/snapshots/integration__integration_tests__ci_status__stale_pr.snap index 76f744fedb..70144c115b 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__stale_pr.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__stale_pr.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | #1 . 05a4a45 16h Initial commit -+ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ↑⇡ ↑1 +1 ⇡1 #1 ../repo.feature da7d235 1d Local commit + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | #1 . 05a4a45 16h Initial commit ++ feature-a ↑| ↑1 +1 | #1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑| ↑1 +1 | #1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑| ↑1 +1 | #1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ↑⇡ ↑1 +1 ⇡1 #1 ../repo.feature da7d235 1d Local commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__ci_status__url_based_pushremote.snap b/tests/snapshots/integration__integration_tests__ci_status__url_based_pushremote.snap index 81c0350598..d14ad66da1 100644 --- a/tests/snapshots/integration__integration_tests__ci_status__url_based_pushremote.snap +++ b/tests/snapshots/integration__integration_tests__ci_status__url_based_pushremote.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature _ #1 ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature _ #1 ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__config_show__deprecated_commit_generation_project_level_shows_warning.snap b/tests/snapshots/integration__integration_tests__config_show__deprecated_commit_generation_project_level_shows_warning.snap index 0d23e66ea0..8a166c2c9d 100644 --- a/tests/snapshots/integration__integration_tests__config_show__deprecated_commit_generation_project_level_shows_warning.snap +++ b/tests/snapshots/integration__integration_tests__config_show__deprecated_commit_generation_project_level_shows_warning.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ User config: [projects."github.com/example/repo".commit-generation] is deprecated in favor of [projects."github.com/example/repo".commit.generation] diff --git a/tests/snapshots/integration__integration_tests__config_show__deprecated_commit_generation_section_shows_warning.snap b/tests/snapshots/integration__integration_tests__config_show__deprecated_commit_generation_section_shows_warning.snap index bae5fd03be..e788b967f0 100644 --- a/tests/snapshots/integration__integration_tests__config_show__deprecated_commit_generation_section_shows_warning.snap +++ b/tests/snapshots/integration__integration_tests__config_show__deprecated_commit_generation_section_shows_warning.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ User config: [commit-generation] is deprecated in favor of [commit.generation] diff --git a/tests/snapshots/integration__integration_tests__config_show__deprecated_template_variables_show_warning.snap b/tests/snapshots/integration__integration_tests__config_show__deprecated_template_variables_show_warning.snap index ecb7c00415..bb393175a9 100644 --- a/tests/snapshots/integration__integration_tests__config_show__deprecated_template_variables_show_warning.snap +++ b/tests/snapshots/integration__integration_tests__config_show__deprecated_template_variables_show_warning.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ User config: template variable repo_root is deprecated in favor of repo_path diff --git a/tests/snapshots/integration__integration_tests__config_show__deprecated_template_variables_verbose_shows_content.snap b/tests/snapshots/integration__integration_tests__config_show__deprecated_template_variables_verbose_shows_content.snap index c7dee5f1f5..dc9cd6f27e 100644 --- a/tests/snapshots/integration__integration_tests__config_show__deprecated_template_variables_verbose_shows_content.snap +++ b/tests/snapshots/integration__integration_tests__config_show__deprecated_template_variables_verbose_shows_content.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ User config: template variable repo_root is deprecated in favor of repo_path diff --git a/tests/snapshots/integration__integration_tests__config_show__explicit_config_path_not_found_shows_warning.snap b/tests/snapshots/integration__integration_tests__config_show__explicit_config_path_not_found_shows_warning.snap index 7dcc91ef2e..43eb71e8f7 100644 --- a/tests/snapshots/integration__integration_tests__config_show__explicit_config_path_not_found_shows_warning.snap +++ b/tests/snapshots/integration__integration_tests__config_show__explicit_config_path_not_found_shows_warning.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,11 +58,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Config file not found: /nonexistent/worktrunk/config.toml diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 99fa614331..4ae81f6d4c 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -103,18 +103,18 @@ The table renders progressively: branch names, paths, and commit hashes appear i List all worktrees:   wt list -   Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -  @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests -  ^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… -  + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… -  + fix-typos _| | 41ee083 4d Merge fix-auth: h… +   Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +  @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests +  ^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… +  + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… +  + fix-typos _| | 41ee083 4d Merge fix-auth: h…      ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path Include CI status and LLM summaries:   wt list --full -   Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit +   Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit   @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02   ^ main ^⇅ ⇡1 ⇣1 # 41ee083   + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 @@ -125,7 +125,7 @@ Include CI status and LLM summaries: Include branches that don't have worktrees:   wt list --branches --full -   Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit +   Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit   @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02   ^ main ^⇅ ⇡1 ⇣1 # 41ee083   + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index 25ba141462..f6e1b70c26 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -114,18 +114,18 @@ branch's changes. The main…± line diffs are local git, so they show b List all worktrees:   wt list -   Branch Status HEAD± main↕ main…± Remote⇅ Commit Age… -  @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m… -  ^ main ^⇅ ⇡1 ⇣1 41ee083 4d … -  + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h … -  + fix-typos _| | 41ee083 4d … +   Branch Status HEAD± main↕ main…± Remote⇅ Commit Ag… +  @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30… +  ^ main ^⇅ ⇡1 ⇣1 41ee083 4… +  + fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5… +  + fix-typos _| | 41ee083 4…      ○ Showing 4 worktrees, 1 with changes, 2 ahead, hidden: Path Include CI status and LLM summaries:   wt list --full -   Branch Status HEAD± main↕ main…± Summary … +   Branch Status HEAD± main↕ main…± Summary …   @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST …   ^ main ^⇅ …   + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with cons… @@ -136,7 +136,7 @@ Include CI status and LLM summaries: Include branches that don't have worktrees:   wt list --branches --full -   Branch Status HEAD± main↕ main…± Summary … +   Branch Status HEAD± main↕ main…± Summary …   @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST …   ^ main ^⇅ …   + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with cons… diff --git a/tests/snapshots/integration__integration_tests__list__commit_conflicts_with_full.snap b/tests/snapshots/integration__integration_tests__list__commit_conflicts_with_full.snap index 5dc85df1a2..e89bf9c238 100644 --- a/tests/snapshots/integration__integration_tests__list__commit_conflicts_with_full.snap +++ b/tests/snapshots/integration__integration_tests__list__commit_conflicts_with_full.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^⇡ ⇡2 . 1edd043 1d Main changes shared.txt -+ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ✗ ↑1 ↓1 +1 -1 ../repo.feature dee7183 1d Feature changes shared.txt + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^⇡ ⇡2 . 1edd043 1d Main changes shared.txt ++ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ✗ ↑1 ↓1 +1 -1 ../repo.feature dee7183 1d Feature changes shared.txt ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__commit_conflicts_without_full.snap b/tests/snapshots/integration__integration_tests__list__commit_conflicts_without_full.snap index c1e58b080e..59d6909402 100644 --- a/tests/snapshots/integration__integration_tests__list__commit_conflicts_without_full.snap +++ b/tests/snapshots/integration__integration_tests__list__commit_conflicts_without_full.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡2 . 1edd043 1d Main changes shared.txt -+ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ✗ ↑1 ↓1 +1 -1 ../repo.feature dee7183 1d Feature changes shared.txt + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡2 . 1edd043 1d Main changes shared.txt ++ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ✗ ↑1 ↓1 +1 -1 ../repo.feature dee7183 1d Feature changes shared.txt ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_bisect_symbol.snap b/tests/snapshots/integration__integration_tests__list__list_bisect_symbol.snap index 50dfc59599..b2c8cf54b9 100644 --- a/tests/snapshots/integration__integration_tests__list__list_bisect_symbol.snap +++ b/tests/snapshots/integration__integration_tests__list__list_bisect_symbol.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ bisecting ↻_ ../repo.bisecting 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ bisecting ↻_ ../repo.bisecting 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_branch_only_with_status.snap b/tests/snapshots/integration__integration_tests__list__list_branch_only_with_status.snap index f0012cbf2b..72f172d41c 100644 --- a/tests/snapshots/integration__integration_tests__list__list_branch_only_with_status.snap +++ b/tests/snapshots/integration__integration_tests__list__list_branch_only_with_status.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ branch-only /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ branch-only /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap index 7dfa122b65..332341ac27 100644 --- a/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap @@ -57,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± base↕ base…± Remote⇅ Path Commit Age Message -@ main ⚑^| | . 05a4a45 16h Initial commit -+ feature-a ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± base↕ base…± Remote⇅ Path Commit Age Message +@ main ⚑^| | . 05a4a45 16h Initial commit ++ feature-a ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Configured default branch nonexistent does not exist locally diff --git a/tests/snapshots/integration__integration_tests__list__list_detached_head.snap b/tests/snapshots/integration__integration_tests__list__list_detached_head.snap index f61df04094..1d4664d292 100644 --- a/tests/snapshots/integration__integration_tests__list__list_detached_head.snap +++ b/tests/snapshots/integration__integration_tests__list__list_detached_head.snap @@ -56,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ 05a4a45 ⊘^ . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ 05a4a45 ⊘^ . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_detached_head_in_worktree.snap b/tests/snapshots/integration__integration_tests__list__list_detached_head_in_worktree.snap index 4aab9cb626..47df728a0b 100644 --- a/tests/snapshots/integration__integration_tests__list__list_detached_head_in_worktree.snap +++ b/tests/snapshots/integration__integration_tests__list__list_detached_head_in_worktree.snap @@ -56,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ 05a4a45 ⊘_ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ 05a4a45 ⊘_ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_duplicate_branch.snap b/tests/snapshots/integration__integration_tests__list__list_duplicate_branch.snap index 3b74118daa..6fd6150be9 100644 --- a/tests/snapshots/integration__integration_tests__list__list_duplicate_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_duplicate_branch.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,13 +56,13 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature ⚑_ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ⚑_ ../repo.feature-dup 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature ⚑_ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ⚑_ ../repo.feature-dup 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_empty_repo.snap b/tests/snapshots/integration__integration_tests__list__list_empty_repo.snap index d37c88490b..b2d503c54f 100644 --- a/tests/snapshots/integration__integration_tests__list__list_empty_repo.snap +++ b/tests/snapshots/integration__integration_tests__list__list_empty_repo.snap @@ -46,6 +46,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -53,8 +54,8 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main · ·^ · . · · + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main · ·^ · . · · ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap index 13b6f1d39f..61d6b87d9a 100644 --- a/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap @@ -57,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± base↕ base…± Remote⇅ CI Path Commit Age Message -@ main ⚑^| | . 05a4a45 16h Initial commit -+ feature-a ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± base↕ base…± Remote⇅ CI Path Commit Age Message +@ main ⚑^| | . 05a4a45 16h Initial commit ++ feature-a ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Configured default branch nonexistent does not exist locally diff --git a/tests/snapshots/integration__integration_tests__list__list_handles_orphan_branch.snap b/tests/snapshots/integration__integration_tests__list__list_handles_orphan_branch.snap index ee0304b4d6..41a2b3ff0b 100644 --- a/tests/snapshots/integration__integration_tests__list__list_handles_orphan_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_handles_orphan_branch.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ assets /∅ 5020903 1d Add asset + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ assets /∅ 5020903 1d Add asset ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_large_diffs_alignment.snap b/tests/snapshots/integration__integration_tests__list__list_large_diffs_alignment.snap index e9fdd72f29..69183e8637 100644 --- a/tests/snapshots/integration__integration_tests__list__list_large_diffs_alignment.snap +++ b/tests/snapshots/integration__integration_tests__list__list_large_diffs_alignment.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,14 +56,14 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ fix ? – 💬 +1 ../repo.fix 05a4a45 16h Initial commit -+ diverged ! ↑ 💬 +40 -60 ↑1 +60 ../repo.diverged 96d1fd9 1d Diverged commit -+ feature-changes !? ↑ 🤖 +230 -100 ↑1 +100 ../repo.feature-changes f5306de 1d Add 100 lines + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ fix ? – 💬 +1 ../repo.fix 05a4a45 16h Initial commit ++ diverged ! ↑ 💬 +40 -60 ↑1 +60 ../repo.diverged 96d1fd9 1d Diverged commit ++ feature-changes !? ↑ 🤖 +230 -100 ↑1 +100 ../repo.feature-changes f5306de 1d Add 100 lines ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_locked_no_reason.snap b/tests/snapshots/integration__integration_tests__list__list_locked_no_reason.snap index 107020b0ed..bc60521e92 100644 --- a/tests/snapshots/integration__integration_tests__list__list_locked_no_reason.snap +++ b/tests/snapshots/integration__integration_tests__list__list_locked_no_reason.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ locked-no-reason ⊞_ ../repo.locked-no-reason 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ locked-no-reason ⊞_ ../repo.locked-no-reason 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_locked_worktree.snap b/tests/snapshots/integration__integration_tests__list__list_locked_worktree.snap index 5cd6803ec4..428641f55f 100644 --- a/tests/snapshots/integration__integration_tests__list__list_locked_worktree.snap +++ b/tests/snapshots/integration__integration_tests__list__list_locked_worktree.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ locked-feature ⊞_ ../repo.locked-feature 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ locked-feature ⊞_ ../repo.locked-feature 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_long_commit_message.snap b/tests/snapshots/integration__integration_tests__list__list_long_commit_message.snap index 760856f019..a0638d976b 100644 --- a/tests/snapshots/integration__integration_tests__list__list_long_commit_message.snap +++ b/tests/snapshots/integration__integration_tests__list__list_long_commit_message.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡2 . 8cf0f3d 1d Short message -+ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡2 . 8cf0f3d 1d Short message ++ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_many_worktrees_with_varied_stats.snap b/tests/snapshots/integration__integration_tests__list__list_many_worktrees_with_varied_stats.snap index 1aa73bda27..163061786e 100644 --- a/tests/snapshots/integration__integration_tests__list__list_many_worktrees_with_varied_stats.snap +++ b/tests/snapshots/integration__integration_tests__list__list_many_worktrees_with_varied_stats.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,15 +56,15 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ medium-name _ ../repo.medium-name 05a4a45 16h Initial commit -+ short _ ../repo.short 05a4a45 16h Initial commit -+ very-long-branch-name-here _ ../repo.very-long-branch-name-here 05a4a45 16h Initial commit -+ with-changes _ ../repo.with-changes 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ medium-name _ ../repo.medium-name 05a4a45 16h Initial commit ++ short _ ../repo.short 05a4a45 16h Initial commit ++ very-long-branch-name-here _ ../repo.very-long-branch-name-here 05a4a45 16h Initial commit ++ with-changes _ ../repo.with-changes 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_maximum_working_tree_symbols.snap b/tests/snapshots/integration__integration_tests__list__list_maximum_working_tree_symbols.snap index 80e5d4206b..e5a4104a1f 100644 --- a/tests/snapshots/integration__integration_tests__list__list_maximum_working_tree_symbols.snap +++ b/tests/snapshots/integration__integration_tests__list__list_maximum_working_tree_symbols.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature +!? ↑ +3 -3 ↑1 +4 ../repo.feature da422a1 1d Add files + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature +!? ↑ +3 -3 ↑1 +4 ../repo.feature da422a1 1d Add files ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_multiple_worktrees.snap b/tests/snapshots/integration__integration_tests__list__list_multiple_worktrees.snap index 6ad87e28ea..cae1a699d6 100644 --- a/tests/snapshots/integration__integration_tests__list__list_multiple_worktrees.snap +++ b/tests/snapshots/integration__integration_tests__list__list_multiple_worktrees.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_nested_worktree_current_indicator.snap b/tests/snapshots/integration__integration_tests__list__list_nested_worktree_current_indicator.snap index c844b30ab9..63f5ef4a3f 100644 --- a/tests/snapshots/integration__integration_tests__list__list_nested_worktree_current_indicator.snap +++ b/tests/snapshots/integration__integration_tests__list__list_nested_worktree_current_indicator.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ feature ⚑_ ./.worktrees/feature 05a4a45 16h Initial commit -^ main ? ^| +1 | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ feature ⚑_ ./.worktrees/feature 05a4a45 16h Initial commit +^ main ? ^| +1 | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_no_progressive_flag.snap b/tests/snapshots/integration__integration_tests__list__list_no_progressive_flag.snap index 303c971ee8..ef25694972 100644 --- a/tests/snapshots/integration__integration_tests__list__list_no_progressive_flag.snap +++ b/tests/snapshots/integration__integration_tests__list__list_no_progressive_flag.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature _ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature _ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_ordering_rules.snap b/tests/snapshots/integration__integration_tests__list__list_ordering_rules.snap index d5aa662db1..b5d8b910d3 100644 --- a/tests/snapshots/integration__integration_tests__list__list_ordering_rules.snap +++ b/tests/snapshots/integration__integration_tests__list__list_ordering_rules.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,15 +56,15 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ feature-current ↑ ↑1 +1 ../repo.feature-current f8d9dc9 23h Commit at 01:00 -^ main ^⇡ ⇡1 . 01cab36 1d Initial commit on main -+ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature-newest ↑ ↑1 +1 ../repo.feature-newest 57c29ea 21h Commit at 03:00 -+ feature-middle ↑ ↑1 +1 ../repo.feature-middle a63f373 22h Commit at 02:00 -+ feature-oldest ↑ ↑1 +1 ../repo.feature-oldest a9b3e5b 23h Commit at 00:30 + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ feature-current ↑ ↑1 +1 ../repo.feature-current f8d9dc9 23h Commit at 01:00 +^ main ^⇡ ⇡1 . 01cab36 1d Initial commit on main ++ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature-newest ↑ ↑1 +1 ../repo.feature-newest 57c29ea 21h Commit at 03:00 ++ feature-middle ↑ ↑1 +1 ../repo.feature-middle a63f373 22h Commit at 02:00 ++ feature-oldest ↑ ↑1 +1 ../repo.feature-oldest a9b3e5b 23h Commit at 00:30 ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_previous_worktree_gutter.snap b/tests/snapshots/integration__integration_tests__list__list_previous_worktree_gutter.snap index bb722926fc..ac37b6f279 100644 --- a/tests/snapshots/integration__integration_tests__list__list_previous_worktree_gutter.snap +++ b/tests/snapshots/integration__integration_tests__list__list_previous_worktree_gutter.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature _ ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature _ ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_primary_on_different_branch.snap b/tests/snapshots/integration__integration_tests__list__list_primary_on_different_branch.snap index 1fd4d57ad8..c34435ecb3 100644 --- a/tests/snapshots/integration__integration_tests__list__list_primary_on_different_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_primary_on_different_branch.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ develop ⚑^ . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ develop ⚑^ . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_progressive_with_branches.snap b/tests/snapshots/integration__integration_tests__list__list_progressive_with_branches.snap index cb1de4a929..d0a44e0a7a 100644 --- a/tests/snapshots/integration__integration_tests__list__list_progressive_with_branches.snap +++ b/tests/snapshots/integration__integration_tests__list__list_progressive_with_branches.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,13 +58,13 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ orphan-1 /_ 05a4a45 16h Initial commit -/ orphan-2 /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ orphan-1 /_ 05a4a45 16h Initial commit +/ orphan-2 /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_shows_warning_on_git_error.snap b/tests/snapshots/integration__integration_tests__list__list_shows_warning_on_git_error.snap index 89e9d6f546..efd477e3cf 100644 --- a/tests/snapshots/integration__integration_tests__list__list_shows_warning_on_git_error.snap +++ b/tests/snapshots/integration__integration_tests__list__list_shows_warning_on_git_error.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^· · . · · -+ feature · ··· · · · · ../repo.feature · · -+ feature-a ↑· ↑1 +1 · ../repo.feature-a · · -+ feature-b ↑· ↑1 +1 · ../repo.feature-b · · -+ feature-c ↑· ↑1 +1 · ../repo.feature-c · · + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^· · . · · ++ feature · ··· · · · · ../repo.feature · · ++ feature-a ↑· ↑1 +1 · ../repo.feature-a · · ++ feature-b ↑· ↑1 +1 · ../repo.feature-b · · ++ feature-c ↑· ↑1 +1 · ../repo.feature-c · · ----- stderr ----- ▲ Failed to batch-fetch commit details: fatal: bad object 0000000000000000000000000000000000000001 diff --git a/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap b/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap index f1600b8a2a..06a1be8960 100644 --- a/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap +++ b/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap @@ -56,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature ⊟         ../repo.feature 05a4a45     -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature ⊟         ../repo.feature 05a4a45 ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_status_column_padding_with_emoji.snap b/tests/snapshots/integration__integration_tests__list__list_status_column_padding_with_emoji.snap index 09923004eb..81e6cd3cbc 100644 --- a/tests/snapshots/integration__integration_tests__list__list_status_column_padding_with_emoji.snap +++ b/tests/snapshots/integration__integration_tests__list__list_status_column_padding_with_emoji.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,14 +56,14 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ main-symbol ↑ 💬 ↑1 +1 ../repo.main-symbol 113f1df 1d Symbol commit -+ pr-link ↑ 🤖 ↑1 +1 ../repo.pr-link 152b5de 1d PR commit -+ wli-sequence !? ↑ 🤖 +165 -112 ↑1 +200 ../repo.wli-sequence 4353e28 1d Initial content + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ main-symbol ↑ 💬 ↑1 +1 ../repo.main-symbol 113f1df 1d Symbol commit ++ pr-link ↑ 🤖 ↑1 +1 ../repo.pr-link 152b5de 1d PR commit ++ wli-sequence !? ↑ 🤖 +165 -112 ↑1 +200 ../repo.wli-sequence 4353e28 1d Initial content ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_task_dag_detached_head.snap b/tests/snapshots/integration__integration_tests__list__list_task_dag_detached_head.snap index 3c2a9428dd..62b369ccf1 100644 --- a/tests/snapshots/integration__integration_tests__list__list_task_dag_detached_head.snap +++ b/tests/snapshots/integration__integration_tests__list__list_task_dag_detached_head.snap @@ -57,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ 05a4a45 ⊘^ . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ 05a4a45 ⊘^ . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_task_dag_full_with_diffs.snap b/tests/snapshots/integration__integration_tests__list__list_task_dag_full_with_diffs.snap index dcd2e3eceb..b5d216f538 100644 --- a/tests/snapshots/integration__integration_tests__list__list_task_dag_full_with_diffs.snap +++ b/tests/snapshots/integration__integration_tests__list__list_task_dag_full_with_diffs.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,11 +58,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ? ↑ +1 ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature-b ↑ ↑2 +2 -1 ../repo.feature-b 391b2a7 1d Test commit + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ? ↑ +1 ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature-b ↑ ↑2 +2 -1 ../repo.feature-b 391b2a7 1d Test commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_task_dag_many_worktrees.snap b/tests/snapshots/integration__integration_tests__list__list_task_dag_many_worktrees.snap index 86a7ff9cd3..7e40c555b2 100644 --- a/tests/snapshots/integration__integration_tests__list__list_task_dag_many_worktrees.snap +++ b/tests/snapshots/integration__integration_tests__list__list_task_dag_many_worktrees.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,21 +57,21 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-1 _ ../repo.feature-1 05a4a45 16h Initial commit -+ feature-10 _ ../repo.feature-10 05a4a45 16h Initial commit -+ feature-2 _ ../repo.feature-2 05a4a45 16h Initial commit -+ feature-3 _ ../repo.feature-3 05a4a45 16h Initial commit -+ feature-4 _ ../repo.feature-4 05a4a45 16h Initial commit -+ feature-5 _ ../repo.feature-5 05a4a45 16h Initial commit -+ feature-6 _ ../repo.feature-6 05a4a45 16h Initial commit -+ feature-7 _ ../repo.feature-7 05a4a45 16h Initial commit -+ feature-8 _ ../repo.feature-8 05a4a45 16h Initial commit -+ feature-9 _ ../repo.feature-9 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-1 _ ../repo.feature-1 05a4a45 16h Initial commit ++ feature-10 _ ../repo.feature-10 05a4a45 16h Initial commit ++ feature-2 _ ../repo.feature-2 05a4a45 16h Initial commit ++ feature-3 _ ../repo.feature-3 05a4a45 16h Initial commit ++ feature-4 _ ../repo.feature-4 05a4a45 16h Initial commit ++ feature-5 _ ../repo.feature-5 05a4a45 16h Initial commit ++ feature-6 _ ../repo.feature-6 05a4a45 16h Initial commit ++ feature-7 _ ../repo.feature-7 05a4a45 16h Initial commit ++ feature-8 _ ../repo.feature-8 05a4a45 16h Initial commit ++ feature-9 _ ../repo.feature-9 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_task_dag_with_locked_worktree.snap b/tests/snapshots/integration__integration_tests__list__list_task_dag_with_locked_worktree.snap index b2b2ddada4..44d4241363 100644 --- a/tests/snapshots/integration__integration_tests__list__list_task_dag_with_locked_worktree.snap +++ b/tests/snapshots/integration__integration_tests__list__list_task_dag_with_locked_worktree.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,13 +57,13 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ locked ⊞_ ../repo.locked 05a4a45 16h Initial commit -+ normal _ ../repo.normal 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ locked ⊞_ ../repo.locked 05a4a45 16h Initial commit ++ normal _ ../repo.normal 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_task_dag_with_upstream.snap b/tests/snapshots/integration__integration_tests__list__list_task_dag_with_upstream.snap index adf18f252c..606c690e82 100644 --- a/tests/snapshots/integration__integration_tests__list__list_task_dag_with_upstream.snap +++ b/tests/snapshots/integration__integration_tests__list__list_task_dag_with_upstream.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,13 +58,13 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^⇡ ⇡1 . 01cab36 1d Initial commit on main -+ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ ahead ↑⇡ ↑1 +1 ⇡1 ../repo.ahead 76a1bef 1d Ahead commit -+ in-sync _| | ../repo.in-sync 01cab36 1d Initial commit on main + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^⇡ ⇡1 . 01cab36 1d Initial commit on main ++ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ ahead ↑⇡ ↑1 +1 ⇡1 ../repo.ahead 76a1bef 1d Ahead commit ++ in-sync _| | ../repo.in-sync 01cab36 1d Initial commit on main ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_unicode_commit_message.snap b/tests/snapshots/integration__integration_tests__list__list_unicode_commit_message.snap index b214ec9b01..cd6897c4f8 100644 --- a/tests/snapshots/integration__integration_tests__list__list_unicode_commit_message.snap +++ b/tests/snapshots/integration__integration_tests__list__list_unicode_commit_message.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡2 . ba0edbd 1d Fix bug with café ☕ handling -+ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature-test ⊂ ↓1 ../repo.feature-test 4b80044 1d Add support for 日本語 and émoji 🎉 + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡2 . ba0edbd 1d Fix bug with café ☕ handling ++ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature-test ⊂ ↓1 ../repo.feature-test 4b80044 1d Add support for 日本語 and émoji 🎉 ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_user_marker_with_special_characters.snap b/tests/snapshots/integration__integration_tests__list__list_user_marker_with_special_characters.snap index 22c268a3d9..df7950df7a 100644 --- a/tests/snapshots/integration__integration_tests__list__list_user_marker_with_special_characters.snap +++ b/tests/snapshots/integration__integration_tests__list__list_user_marker_with_special_characters.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,13 +56,13 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ emoji _ 🔄 ../repo.emoji 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ multi _ 👨‍💻 ../repo.multi 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ emoji _ 🔄 ../repo.emoji 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ multi _ 👨‍💻 ../repo.multi 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_warns_when_commit_details_batch_fails.snap b/tests/snapshots/integration__integration_tests__list__list_warns_when_commit_details_batch_fails.snap index ab79a1bd61..9deb0329c8 100644 --- a/tests/snapshots/integration__integration_tests__list__list_warns_when_commit_details_batch_fails.snap +++ b/tests/snapshots/integration__integration_tests__list__list_warns_when_commit_details_batch_fails.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . · · -+ · ·· · · · ../repo.feature · · -+ feature-a ↑ ↑1 +1 ../repo.feature-a · · -+ feature-b ↑ ↑1 +1 ../repo.feature-b · · -+ feature-c ↑ ↑1 +1 ../repo.feature-c · · + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . · · ++ · ·· · · · ../repo.feature · · ++ feature-a ↑ ↑1 +1 ../repo.feature-a · · ++ feature-b ↑ ↑1 +1 ../repo.feature-b · · ++ feature-c ↑ ↑1 +1 ../repo.feature-c · · ----- stderr ----- ▲ Failed to batch-fetch commit details: fatal: bad object 0000000000000000000000000000000000000001 diff --git a/tests/snapshots/integration__integration_tests__list__list_warns_when_default_branch_missing_worktree.snap b/tests/snapshots/integration__integration_tests__list__list_warns_when_default_branch_missing_worktree.snap index 1fd4d57ad8..c34435ecb3 100644 --- a/tests/snapshots/integration__integration_tests__list__list_warns_when_default_branch_missing_worktree.snap +++ b/tests/snapshots/integration__integration_tests__list__list_warns_when_default_branch_missing_worktree.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ develop ⚑^ . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ develop ⚑^ . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_branches_flag.snap b/tests/snapshots/integration__integration_tests__list__list_with_branches_flag.snap index 1efc6b94e1..91108ef8db 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_branches_flag.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_branches_flag.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,15 +57,15 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature-with-worktree _ ../repo.feature-with-worktree 05a4a45 16h Initial commit -/ another-branch /_ 05a4a45 16h Initial commit -/ feature-without-worktree /_ 05a4a45 16h Initial commit -/ fix-bug /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature-with-worktree _ ../repo.feature-with-worktree 05a4a45 16h Initial commit +/ another-branch /_ 05a4a45 16h Initial commit +/ feature-without-worktree /_ 05a4a45 16h Initial commit +/ fix-bug /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_branches_flag_no_available.snap b/tests/snapshots/integration__integration_tests__list__list_with_branches_flag_no_available.snap index 5401a9eb4d..5acbc23ba3 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_branches_flag_no_available.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_branches_flag_no_available.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_branches_flag_only_branches.snap b/tests/snapshots/integration__integration_tests__list__list_with_branches_flag_only_branches.snap index 92e6037891..8b6ac23403 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_branches_flag_only_branches.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_branches_flag_only_branches.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,14 +57,14 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ branch-alpha /_ 05a4a45 16h Initial commit -/ branch-beta /_ 05a4a45 16h Initial commit -/ branch-gamma /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ branch-alpha /_ 05a4a45 16h Initial commit +/ branch-beta /_ 05a4a45 16h Initial commit +/ branch-gamma /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_c_flag.snap b/tests/snapshots/integration__integration_tests__list__list_with_c_flag.snap index 0d37e27a24..e79a47df1d 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_c_flag.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_c_flag.snap @@ -36,17 +36,18 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" --- success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap index 330e717dbf..451de71e0d 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap @@ -56,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± base↕ base…± Remote⇅ Path Commit Age Message -@ main ⚑^| | . 05a4a45 16h Initial commit -+ feature-a ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± base↕ base…± Remote⇅ Path Commit Age Message +@ main ⚑^| | . 05a4a45 16h Initial commit ++ feature-a ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Configured default branch nonexistent does not exist locally diff --git a/tests/snapshots/integration__integration_tests__list__list_with_remotes_and_branches.snap b/tests/snapshots/integration__integration_tests__list__list_with_remotes_and_branches.snap index 07a986c993..1a2cf713a5 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_remotes_and_branches.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_remotes_and_branches.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,15 +58,15 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ local-only-1 /_ 05a4a45 16h Initial commit -/ local-only-2 /_ 05a4a45 16h Initial commit -| origin/remote-only-1 /_ 05a4a45 16h Initial commit -| origin/remote-only-2 /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ local-only-1 /_ 05a4a45 16h Initial commit +/ local-only-2 /_ 05a4a45 16h Initial commit +| origin/remote-only-1 /_ 05a4a45 16h Initial commit +| origin/remote-only-2 /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_remotes_and_full.snap b/tests/snapshots/integration__integration_tests__list__list_with_remotes_and_full.snap index e5bcab163e..71d728bcd5 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_remotes_and_full.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_remotes_and_full.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,12 +58,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -| origin/feature-remote /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +| origin/feature-remote /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_remotes_filters_tracked_branches.snap b/tests/snapshots/integration__integration_tests__list__list_with_remotes_filters_tracked_branches.snap index f187a9d8fa..a791e2e394 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_remotes_filters_tracked_branches.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_remotes_filters_tracked_branches.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -| origin/remote-only /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +| origin/remote-only /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_remotes_filters_tracked_worktrees.snap b/tests/snapshots/integration__integration_tests__list__list_with_remotes_filters_tracked_worktrees.snap index 20f533cfa3..5f58a12947 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_remotes_filters_tracked_worktrees.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_remotes_filters_tracked_worktrees.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,13 +57,13 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature-with-worktree _| | ../repo.feature-with-worktree 05a4a45 16h Initial commit -| origin/remote-only /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature-with-worktree _| | ../repo.feature-with-worktree 05a4a45 16h Initial commit +| origin/remote-only /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_remotes_flag.snap b/tests/snapshots/integration__integration_tests__list__list_with_remotes_flag.snap index 822b9e094f..5efeb9562c 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_remotes_flag.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_remotes_flag.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,13 +57,13 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -| origin/remote-feature-1 /_ 05a4a45 16h Initial commit -| origin/remote-feature-2 /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +| origin/remote-feature-1 /_ 05a4a45 16h Initial commit +| origin/remote-feature-2 /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_with_user_marker.snap b/tests/snapshots/integration__integration_tests__list__list_with_user_marker.snap index 96ba18d37e..65367c7703 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_user_marker.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_user_marker.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡1 . 33323bc 1d Initial commit -+ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints -+ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component -+ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡1 . 33323bc 1d Initial commit ++ feature-api ↑ 🤖 ↑1 +1 ../repo.feature-api 70343f0 1d Add REST API endpoints ++ review-ui ? ↑ 💬 +1 ↑1 +1 ../repo.review-ui a585d6e 1d Add dashboard component ++ wip-docs ? – +1 ../repo.wip-docs 33323bc 1d Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__maximum_status_symbols.snap b/tests/snapshots/integration__integration_tests__list__maximum_status_symbols.snap index 99c34254a1..1ad26ab9cd 100644 --- a/tests/snapshots/integration__integration_tests__list__maximum_status_symbols.snap +++ b/tests/snapshots/integration__integration_tests__list__maximum_status_symbols.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^⇡ ⇡2 . c51a68f 1d Main advances -+ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature +!?⊞✗⇅🤖 +3 -2 ↑2 ↓1 +3 -1 ⇡1 ⇣1 ../repo.feature 342be36 1d Local commit + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^⇡ ⇡2 . c51a68f 1d Main advances ++ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature +!?⊞✗⇅🤖 +3 -2 ↑2 ↓1 +3 -1 ⇡1 ⇣1 ../repo.feature 342be36 1d Local commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__maximum_status_with_git_operation.snap b/tests/snapshots/integration__integration_tests__list__maximum_status_with_git_operation.snap index 30b8bedec1..d93373fdd6 100644 --- a/tests/snapshots/integration__integration_tests__list__maximum_status_with_git_operation.snap +++ b/tests/snapshots/integration__integration_tests__list__maximum_status_with_git_operation.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^⇡ ⇡2 . 27eb0ee 1d Main conflicting changes -+ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature +!?✘✗ 🤖 +8 ↑1 ↓1 +3 -2 ../repo.feature 27eb0ee 1d Main conflicting changes + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^⇡ ⇡2 . 27eb0ee 1d Main conflicting changes ++ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature +!?✘✗ 🤖 +8 ↑1 ↓1 +3 -2 ../repo.feature 27eb0ee 1d Main conflicting changes ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__quickstart_list.snap b/tests/snapshots/integration__integration_tests__list__quickstart_list.snap index c4953dcca3..7230008fee 100644 --- a/tests/snapshots/integration__integration_tests__list__quickstart_list.snap +++ b/tests/snapshots/integration__integration_tests__list__quickstart_list.snap @@ -56,9 +56,9 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… -^ main ^⇡ ⇡1 0e631ad 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-auth + ↑ +27 -8 ↑1 +31 4bc72dc 2h Add authenticati… +^ main ^⇡ ⇡1 0e631ad 1d Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__readme_example_list.snap b/tests/snapshots/integration__integration_tests__list__readme_example_list.snap index da931640c2..e1a77f9677 100644 --- a/tests/snapshots/integration__integration_tests__list__readme_example_list.snap +++ b/tests/snapshots/integration__integration_tests__list__readme_example_list.snap @@ -56,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests -^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… -+ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… -+ fix-typos _| | 41ee083 4d Merge fix-auth: h… + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 6814f02 30m Add API tests +^ main ^⇅ ⇡1 ⇣1 41ee083 4d Merge fix-auth: h… ++ fix-auth ↕| ↑2 ↓1 +25 -11 | b772e68 5h Add secure token… ++ fix-typos _| | 41ee083 4d Merge fix-auth: h… ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__readme_example_list_branches.snap b/tests/snapshots/integration__integration_tests__list__readme_example_list_branches.snap index d589a97432..f810f5faa0 100644 --- a/tests/snapshots/integration__integration_tests__list__readme_example_list_branches.snap +++ b/tests/snapshots/integration__integration_tests__list__readme_example_list_branches.snap @@ -58,7 +58,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 diff --git a/tests/snapshots/integration__integration_tests__list__readme_example_list_full.snap b/tests/snapshots/integration__integration_tests__list__readme_example_list_full.snap index 1a0859677a..1bdcfc8602 100644 --- a/tests/snapshots/integration__integration_tests__list__readme_example_list_full.snap +++ b/tests/snapshots/integration__integration_tests__list__readme_example_list_full.snap @@ -57,7 +57,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit + Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit @ feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 Refactor API to REST architecture with middleware ⇡3 #412 6814f02 ^ main ^⇅ ⇡1 ⇣1 # 41ee083 + fix-auth ↕| ↑2 ↓1 +25 -11 Harden auth with constant-time token validation | #408 b772e68 diff --git a/tests/snapshots/integration__integration_tests__list__readme_example_list_marker.snap b/tests/snapshots/integration__integration_tests__list__readme_example_list_marker.snap index d8866d9616..a1ec07c2cf 100644 --- a/tests/snapshots/integration__integration_tests__list__readme_example_list_marker.snap +++ b/tests/snapshots/integration__integration_tests__list__readme_example_list_marker.snap @@ -56,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ main ^⇡ ⇡1 33323bc 1d Initial commit -+ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp… -+ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… -+ wip-docs ? – +1 33323bc 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ main ^⇡ ⇡1 33323bc 1d Initial commit ++ feature-api ↑ 🤖 ↑1 +1 70343f0 1d Add REST API endp… ++ review-ui ? ↑ 💬 +1 ↑1 +1 a585d6e 1d Add dashboard com… ++ wip-docs ? – +1 33323bc 1d Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__task_dag_ordering_stability.snap b/tests/snapshots/integration__integration_tests__list__task_dag_ordering_stability.snap index 27ed367540..4aab49809a 100644 --- a/tests/snapshots/integration__integration_tests__list__task_dag_ordering_stability.snap +++ b/tests/snapshots/integration__integration_tests__list__task_dag_ordering_stability.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,15 +57,15 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ feature-current ↑ ↑1 +1 ../repo.feature-current f8d9dc9 23h Commit at 01:00 -^ main ^⇡ ⇡1 . 01cab36 1d Initial commit on main -+ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature-newest ↑ ↑1 +1 ../repo.feature-newest 57c29ea 21h Commit at 03:00 -+ feature-middle ↑ ↑1 +1 ../repo.feature-middle a63f373 22h Commit at 02:00 -+ feature-oldest ↑ ↑1 +1 ../repo.feature-oldest a9b3e5b 23h Commit at 00:30 + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ feature-current ↑ ↑1 +1 ../repo.feature-current f8d9dc9 23h Commit at 01:00 +^ main ^⇡ ⇡1 . 01cab36 1d Initial commit on main ++ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature-newest ↑ ↑1 +1 ../repo.feature-newest 57c29ea 21h Commit at 03:00 ++ feature-middle ↑ ↑1 +1 ../repo.feature-middle a63f373 22h Commit at 02:00 ++ feature-oldest ↑ ↑1 +1 ../repo.feature-oldest a9b3e5b 23h Commit at 00:30 ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__tips_dev_server_workflow.snap b/tests/snapshots/integration__integration_tests__list__tips_dev_server_workflow.snap index a7461f50ac..bbae5b06bd 100644 --- a/tests/snapshots/integration__integration_tests__list__tips_dev_server_workflow.snap +++ b/tests/snapshots/integration__integration_tests__list__tips_dev_server_workflow.snap @@ -56,7 +56,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ URL Commit + Branch Status HEAD± main↕ main…± Remote⇅ URL Commit @ main ? ^⇅ +5 ⇡1 ⇣1 http://localhost:12107 41ee083 + feature-api + ↕⇡ +54 -5 ↑4 ↓1 +234 -24 ⇡3 http://localhost:10703 6814f02 + fix-auth ↕| ↑2 ↓1 +25 -11 | http://localhost:16460 b772e68 diff --git a/tests/snapshots/integration__integration_tests__list__with_upstream_tracking.snap b/tests/snapshots/integration__integration_tests__list__with_upstream_tracking.snap index 59a6d80c48..7e74052765 100644 --- a/tests/snapshots/integration__integration_tests__list__with_upstream_tracking.snap +++ b/tests/snapshots/integration__integration_tests__list__with_upstream_tracking.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,17 +58,17 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^⇡ ⇡1 . 01cab36 1d Initial commit on main -+ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ ahead ↑⇡ ↑2 +2 ⇡2 ../repo.ahead a25eff2 1d Ahead commit 2 -+ behind _⇣ ⇣1 ../repo.behind 01cab36 1d Initial commit on main -+ diverged ↑⇅ ↑1 +1 ⇡1 ⇣1 ../repo.diverged f035d2c 1d Diverged local commit -+ gone-upstream _ ../repo.gone-upstream 01cab36 1d Initial commit on main -+ in-sync _| | ../repo.in-sync 01cab36 1d Initial commit on main -+ no-upstream _ ../repo.no-upstream 01cab36 1d Initial commit on main + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^⇡ ⇡1 . 01cab36 1d Initial commit on main ++ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ ahead ↑⇡ ↑2 +2 ⇡2 ../repo.ahead a25eff2 1d Ahead commit 2 ++ behind _⇣ ⇣1 ../repo.behind 01cab36 1d Initial commit on main ++ diverged ↑⇅ ↑1 +1 ⇡1 ⇣1 ../repo.diverged f035d2c 1d Diverged local commit ++ gone-upstream _ ../repo.gone-upstream 01cab36 1d Initial commit on main ++ in-sync _| | ../repo.in-sync 01cab36 1d Initial commit on main ++ no-upstream _ ../repo.no-upstream 01cab36 1d Initial commit on main ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__working_tree_conflicts_with_full.snap b/tests/snapshots/integration__integration_tests__list__working_tree_conflicts_with_full.snap index 24ce01bbfb..9bea2404d1 100644 --- a/tests/snapshots/integration__integration_tests__list__working_tree_conflicts_with_full.snap +++ b/tests/snapshots/integration__integration_tests__list__working_tree_conflicts_with_full.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^⇡ ⇡2 . 1edd043 1d Main changes shared.txt -+ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ! ✗ +1 -1 ↓1 ../repo.feature a756a74 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^⇡ ⇡2 . 1edd043 1d Main changes shared.txt ++ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ! ✗ +1 -1 ↓1 ../repo.feature a756a74 1d Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__working_tree_conflicts_without_full.snap b/tests/snapshots/integration__integration_tests__list__working_tree_conflicts_without_full.snap index d796171433..89366d4611 100644 --- a/tests/snapshots/integration__integration_tests__list__working_tree_conflicts_without_full.snap +++ b/tests/snapshots/integration__integration_tests__list__working_tree_conflicts_without_full.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡2 . 1edd043 1d Main changes shared.txt -+ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature ! ✗ +1 -1 ↓1 ../repo.feature a756a74 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡2 . 1edd043 1d Main changes shared.txt ++ feature-a ↕ ↑1 ↓2 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓2 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓2 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature ! ✗ +1 -1 ↓1 ../repo.feature a756a74 1d Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_branches_enabled.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_branches_enabled.snap index c25ded8e6c..4bb0cc93ff 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_branches_enabled.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_branches_enabled.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_branches_flag_overrides_file.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_branches_flag_overrides_file.snap index 3d27b016eb..b11156ba4b 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_branches_flag_overrides_file.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_branches_flag_overrides_file.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_after_subcommand.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_after_subcommand.snap index 9dc84ec57a..f878ff5de7 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_after_subcommand.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_after_subcommand.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,12 +58,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_beats_file.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_beats_file.snap index d71619e831..a0d5779136 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_beats_file.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_beats_file.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,12 +58,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_deprecated_key_silently_migrated.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_deprecated_key_silently_migrated.snap index cb5a1b141a..c648443744 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_deprecated_key_silently_migrated.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_deprecated_key_silently_migrated.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,12 +58,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_malformed_warns_on_stderr.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_malformed_warns_on_stderr.snap index 7d3af46c54..144f28fd11 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_malformed_warns_on_stderr.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_cli_override_malformed_warns_on_stderr.snap @@ -50,6 +50,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -57,12 +58,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- ▲ Ignoring --config-set overrides: garbage diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_columns_env_override_not_yet_supported.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_columns_env_override_not_yet_supported.snap index b5328edd74..9bb058980b 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_columns_env_override_not_yet_supported.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_columns_env_override_not_yet_supported.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" WORKTRUNK__LIST__COLUMNS: "branch,age" @@ -56,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Ignoring env var overrides: WORKTRUNK__LIST__COLUMNS=branch,age diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_env_deprecated_type_mismatch_drops_layer.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_env_deprecated_type_mismatch_drops_layer.snap index d49b8a958e..daa8306d90 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_env_deprecated_type_mismatch_drops_layer.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_env_deprecated_type_mismatch_drops_layer.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" WORKTRUNK__COMMIT_GENERATION__COMMAND: "42" @@ -56,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Ignoring env var overrides: WORKTRUNK__COMMIT_GENERATION__COMMAND=42 diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_bad_value_preserves_file_config.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_bad_value_preserves_file_config.snap index c09dfc1763..cdc3486d4f 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_bad_value_preserves_file_config.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_bad_value_preserves_file_config.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" WORKTRUNK__LIST__BRANCHES: not-a-bool @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- ▲ Ignoring env var overrides: WORKTRUNK__LIST__BRANCHES=not-a-bool diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_bad_value_warns_on_stderr.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_bad_value_warns_on_stderr.snap index b97cdf2c56..c67925b309 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_bad_value_warns_on_stderr.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_bad_value_warns_on_stderr.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" WORKTRUNK__LIST__BRANCHES: not-a-bool @@ -56,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Ignoring env var overrides: WORKTRUNK__LIST__BRANCHES=not-a-bool diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_preserves_file_fields.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_preserves_file_fields.snap index 9d38580aa8..7dfdab3e44 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_preserves_file_fields.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_preserves_file_fields.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" WORKTRUNK__LIST__TIMEOUT_MS: "0" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_validation_failure.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_validation_failure.snap index 6fedb329cd..9b1f6591d4 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_validation_failure.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_env_override_validation_failure.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" WORKTRUNK_WORKTREE_PATH: "" @@ -56,11 +57,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Config validation warning: worktree-path cannot be empty diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_full_and_branches.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_full_and_branches.snap index bb1b2d54e6..429eb2da37 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_full_and_branches.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_full_and_branches.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ feature /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ feature /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_full_enabled.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_full_enabled.snap index 65ca9f0689..1ed2f47014 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_full_enabled.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_full_enabled.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_config_warns_on_stderr.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_config_warns_on_stderr.snap index edbc77da15..fc4e2200a8 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_config_warns_on_stderr.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_config_warns_on_stderr.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ User config @ [TEST_CONFIG] failed to parse, skipping diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_non_section_field_warns_on_stderr.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_non_section_field_warns_on_stderr.snap index e85319629f..934c18caf6 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_non_section_field_warns_on_stderr.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_non_section_field_warns_on_stderr.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ User config @ [TEST_CONFIG] failed to parse, skipping diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_system_config_non_section_field.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_system_config_non_section_field.snap index 2dc11e5029..dc9ed6e0de 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_system_config_non_section_field.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_system_config_non_section_field.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ? ^| +1 | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ? ^| +1 | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ System config @ [TEST_SYSTEM_CONFIG_FILE] failed to parse, skipping diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_system_config_warns_on_stderr.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_system_config_warns_on_stderr.snap index 8581df0a57..84f9b9932e 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_system_config_warns_on_stderr.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_malformed_system_config_warns_on_stderr.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ? ^| +2 | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ? ^| +2 | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ System config @ [TEST_SYSTEM_CONFIG_FILE] failed to parse, skipping diff --git a/tests/snapshots/integration__integration_tests__list_config__list_config_validation_error_warns_on_stderr.snap b/tests/snapshots/integration__integration_tests__list_config__list_config_validation_error_warns_on_stderr.snap index 50171c2f85..9378d120cc 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_config_validation_error_warns_on_stderr.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_config_validation_error_warns_on_stderr.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Config validation warning: worktree-path cannot be empty diff --git a/tests/snapshots/integration__integration_tests__list_config__list_custom_column_empty_everywhere_dropped.snap b/tests/snapshots/integration__integration_tests__list_config__list_custom_column_empty_everywhere_dropped.snap index ca6aea7d5f..4dcba45a24 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_custom_column_empty_everywhere_dropped.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_custom_column_empty_everywhere_dropped.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_custom_columns.snap b/tests/snapshots/integration__integration_tests__list_config__list_custom_columns.snap index 563f333723..6da86a4ea8 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_custom_columns.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_custom_columns.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Ticket Codename Commit Age Message -@ main ^| | . mellow-kite 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a JIRA-1234 fearless-iguanodon 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b spirited-mink f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c secure-sicklebill 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Ticket Codename Commit Age Message +@ main ^| | . mellow-kite 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a JIRA-1234 fearless-iguanodon 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b spirited-mink f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c secure-sicklebill 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_no_config.snap b/tests/snapshots/integration__integration_tests__list_config__list_no_config.snap index ca6aea7d5f..4dcba45a24 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_no_config.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_no_config.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_project_url_column.snap b/tests/snapshots/integration__integration_tests__list_config__list_project_url_column.snap index e261db44b2..356cec0359 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_project_url_column.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_project_url_column.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path URL Commit Age Message -@ main ? ^| +2 | . http://localhost:12107 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a http://localhost:11521 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b http://localhost:14072 f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c http://localhost:14303 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path URL Commit Age Message +@ main ? ^| +2 | . http://localhost:12107 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a http://localhost:11521 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b http://localhost:14072 f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c http://localhost:14303 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap b/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap index 3cf69e3c3f..ae89c7db62 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap @@ -56,13 +56,13 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial comm… -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-… -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-… -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-… -+ 05a4a45 ⊘_ ../repo.feature-detached 05a4a45 16h Initial comm… -+ feature-gone ⊟         ../repo.feature-gone 05a4a45     + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial comm… ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-… ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-… ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-… ++ 05a4a45 ⊘_ ../repo.feature-detached 05a4a45 16h Initial comm… ++ feature-gone ⊟         ../repo.feature-gone 05a4a45 ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_layout__diverse_rows.snap b/tests/snapshots/integration__integration_tests__list_layout__diverse_rows.snap index 15e79a4994..d7fc6c6634 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__diverse_rows.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__diverse_rows.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a + ? ↑ +121 -1 ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ -1 ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ 日本語 _ ../repo.日本語 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a + ? ↑ +121 -1 ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ -1 ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ 日本語 _ ../repo.日本語 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_100.snap b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_100.snap index f740a04792..a8cef8a372 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_100.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_100.snap @@ -56,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age -@ main ^| | 05a4a45 16h -+ a-very-long-branch-name-that-is… _ 05a4a45 16h -+ feature-a ↑ ↑1 +1 1b87d47 16h -+ feature-b ↑ ↑1 +1 f62940f 16h -+ feature-c ↑ ↑1 +1 345c7c9 16h + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age +@ main ^| | 05a4a45 16h ++ a-very-long-branch-name-that-is… _ 05a4a45 16h ++ feature-a ↑ ↑1 +1 1b87d47 16h ++ feature-b ↑ ↑1 +1 f62940f 16h ++ feature-c ↑ ↑1 +1 345c7c9 16h ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_200.snap b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_200.snap index 9b06bf05e9..46a145b53e 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_200.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_200.snap @@ -56,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ a-very-long-branch-name-that-is… _ ../repo.a-very-long-branch-name-that-is-fifty-eight-chars-long-ok 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ a-very-long-branch-name-that-is… _ ../repo.a-very-long-branch-name-that-is-fifty-eight-chars-long-ok 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_60.snap b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_60.snap index b4a0128e14..32dd4e2f1a 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_60.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__long_branch_width_60.snap @@ -56,13 +56,14 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± -@ main ^| -+ a-very-long-branch-name-that-is… _ -+ feature-a ↑ -+ feature-b ↑ -+ feature-c ↑ + Branch Status HEAD± +@ main ^| ++ a-very-long-branch-name-that-is… _ ++ feature-a ↑ ++ feature-b ↑ ++ feature-c ↑ ----- stderr ----- -○ Showing 5 worktrees, 3 ahead, hidden: main↕, main…±, Remote⇅, Path, Commit, Age, Message +○ Showing 5 worktrees, 3 ahead, hidden: main↕, main…±, + Remote⇅, Path, Commit, Age, Message diff --git a/tests/snapshots/integration__integration_tests__list_layout__no_remote_width_100.snap b/tests/snapshots/integration__integration_tests__list_layout__no_remote_width_100.snap index 273ec36415..541921316e 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__no_remote_width_100.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__no_remote_width_100.snap @@ -56,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Commit Age Message -@ main ^ 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 1b87d47 16h Add feature-a… -+ feature-b ↑ ↑1 +1 f62940f 16h Add feature-b… -+ feature-c ↑ ↑1 +1 345c7c9 16h Add feature-c… -+ feature-with-a-longer-name _ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Commit Age Message +@ main ^ 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 1b87d47 16h Add feature-a… ++ feature-b ↑ ↑1 +1 f62940f 16h Add feature-b… ++ feature-c ↑ ↑1 +1 345c7c9 16h Add feature-c… ++ feature-with-a-longer-name _ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_layout__width_120.snap b/tests/snapshots/integration__integration_tests__list_layout__width_120.snap index 1cb9a9523b..355bdeca6a 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__width_120.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__width_120.snap @@ -56,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message -@ main ^| | 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 345c7c9 16h Add feature-c file -+ feature/implement-oauth2-social… ? – +1 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Commit Age Message +@ main ^| | 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 345c7c9 16h Add feature-c file ++ feature/implement-oauth2-social… ? – +1 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_layout__width_30.snap b/tests/snapshots/integration__integration_tests__list_layout__width_30.snap index 1dd07b58b9..52df9c3836 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__width_30.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__width_30.snap @@ -65,4 +65,8 @@ exit_code: 0 ----- stderr ----- -○ Showing 5 worktrees, 1 with changes, 3 ahead, hidden: Status, HEAD±, main↕, main…±, Remote⇅, Path, Commit, Age, Message +○ Showing 5 worktrees, 1 with + changes, 3 ahead, hidden: + Status, HEAD±, main↕, + main…±, Remote⇅, Path, + Commit, Age, Message diff --git a/tests/snapshots/integration__integration_tests__list_layout__width_60.snap b/tests/snapshots/integration__integration_tests__list_layout__width_60.snap index 13b8cfb697..d0ef6b8f87 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__width_60.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__width_60.snap @@ -56,13 +56,14 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± -@ main ^| -+ feature-a ↑ -+ feature-b ↑ -+ feature-c ↑ -+ feature/implement-oauth2-social… ? – +1 + Branch Status HEAD± +@ main ^| ++ feature-a ↑ ++ feature-b ↑ ++ feature-c ↑ ++ feature/implement-oauth2-social… ? – +1 ----- stderr ----- -○ Showing 5 worktrees, 1 with changes, 3 ahead, hidden: main↕, main…±, Remote⇅, Path, Commit, Age, Message +○ Showing 5 worktrees, 1 with changes, 3 ahead, hidden: + main↕, main…±, Remote⇅, Path, Commit, Age, Message diff --git a/tests/snapshots/integration__integration_tests__security__commit_message_with_directive_not_executed.snap b/tests/snapshots/integration__integration_tests__security__commit_message_with_directive_not_executed.snap index 766f55c474..03f937d481 100644 --- a/tests/snapshots/integration__integration_tests__security__commit_message_with_directive_not_executed.snap +++ b/tests/snapshots/integration__integration_tests__security__commit_message_with_directive_not_executed.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^⇡ ⇡1 . 252b848 1d Fix bug __WORKTRUNK_EXEC__echo PWNED > /tmp/hacked4 -+ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ feature _ ../repo.feature 252b848 1d Fix bug __WORKTRUNK_EXEC__echo PWNED > /tmp/hacked4 + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^⇡ ⇡1 . 252b848 1d Fix bug __WORKTRUNK_EXEC__echo PWNED > /tmp/hacked4 ++ feature-a ↕ ↑1 ↓1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↕ ↑1 ↓1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↕ ↑1 ↓1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ feature _ ../repo.feature 252b848 1d Fix bug __WORKTRUNK_EXEC__echo PWNED > /tmp/hacked4 ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__security__literal_escape_like_branch_names_displayed_safely.snap b/tests/snapshots/integration__integration_tests__security__literal_escape_like_branch_names_displayed_safely.snap index f6b4dacc19..fb073259df 100644 --- a/tests/snapshots/integration__integration_tests__security__literal_escape_like_branch_names_displayed_safely.snap +++ b/tests/snapshots/integration__integration_tests__security__literal_escape_like_branch_names_displayed_safely.snap @@ -49,6 +49,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -56,12 +57,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -/ fix-backslash-x1b-test /_ 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file +/ fix-backslash-x1b-test /_ 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__step_promote__promote_shows_mismatch_in_list.snap b/tests/snapshots/integration__integration_tests__step_promote__promote_shows_mismatch_in_list.snap index 3fd4c88184..b1b0a39cbc 100644 --- a/tests/snapshots/integration__integration_tests__step_promote__promote_shows_mismatch_in_list.snap +++ b/tests/snapshots/integration__integration_tests__step_promote__promote_shows_mismatch_in_list.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ feature ⚑^ . 05a4a45 16h Initial commit -+ main ⚑_| | ../repo.feature 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ feature ⚑^ . 05a4a45 16h Initial commit ++ main ⚑_| | ../repo.feature 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__switch__list_with_relative_paths.snap b/tests/snapshots/integration__integration_tests__switch__list_with_relative_paths.snap index 33de153fef..9b09af19c0 100644 --- a/tests/snapshots/integration__integration_tests__switch__list_with_relative_paths.snap +++ b/tests/snapshots/integration__integration_tests__switch__list_with_relative_paths.snap @@ -48,6 +48,7 @@ info: WORKTRUNK_TEST_PARENT_SHELL: "" WORKTRUNK_TEST_POWERSHELL_ENV: "0" WORKTRUNK_TEST_POWERSHELL_INSTALLED: "0" + WORKTRUNK_TEST_PROBE_TIMEOUT_MS: "60000" WORKTRUNK_TEST_SKIP_URL_HEALTH_CHECK: "1" WORKTRUNK_TEST_ZSH_INSTALLED: "0" XDG_CONFIG_HOME: "[TEST_CONFIG_HOME]" @@ -55,12 +56,12 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^| | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file -+ relative-test _ ../repo.relative-test 05a4a45 16h Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ^| | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ++ relative-test _ ../repo.relative-test 05a4a45 16h Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__switch_picker__switch_picker_abort_escape_list.snap b/tests/snapshots/integration__integration_tests__switch_picker__switch_picker_abort_escape_list.snap index 0bc9789bb4..2fe60dddd0 100644 --- a/tests/snapshots/integration__integration_tests__switch_picker__switch_picker_abort_escape_list.snap +++ b/tests/snapshots/integration__integration_tests__switch_picker__switch_picker_abort_escape_list.snap @@ -3,5 +3,5 @@ source: tests/integration_tests/switch_picker.rs expression: list --- > - Branch Status HEAD± main↕ main…± Remote + Branch Status HEAD± main↕ main…± Remote > @ main ^ diff --git a/tests/snapshots/integration__integration_tests__switch_picker__switch_picker_preview_log_list.snap b/tests/snapshots/integration__integration_tests__switch_picker__switch_picker_preview_log_list.snap index 5c2be72248..8f8eaec60d 100644 --- a/tests/snapshots/integration__integration_tests__switch_picker__switch_picker_preview_log_list.snap +++ b/tests/snapshots/integration__integration_tests__switch_picker__switch_picker_preview_log_list.snap @@ -3,6 +3,6 @@ source: tests/integration_tests/switch_picker.rs expression: list --- > - Branch Status HEAD± main↕ main…± Remot + Branch Status HEAD± main↕ main…± Remot @ main ^ > + feature ↑ ↑1 +1 -1 From 0389b08435dedefbed4cb6fea87de7f2959ace47 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Wed, 2 Sep 2026 17:55:04 -0700 Subject: [PATCH 09/17] Fix trailing padding on an empty last cell and hidden-column ordering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Line::trim_end` stopped at the first segment with no trailing space, and an empty segment qualifies — `render_text_cell` pushes exactly one for an empty cell. So a row whose last allocated column rendered empty kept the padding run before it, which is the defect the method was added to remove. Skip empty segments rather than stopping on them. The hidden-column list is documented as display order, but sorted by `column_display_index`, the static registry order that display order falls back to when no `[list] columns` selection is set. With a selection, the footer named the dropped columns in an order they would never have appeared in. Sort by `display_sort_key`, which the allocation pass already uses. Also add `⊘` to the two Gate 2 priority lists in `status_symbols.rs`, and `has_branch` to its inputs — the docs page and `model/state.rs` were updated for the detached glyph and this file wasn't. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --- src/commands/list/layout.rs | 4 +++- src/commands/list/model/status_symbols.rs | 15 ++++++++------- src/styling/line.rs | 9 +++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/commands/list/layout.rs b/src/commands/list/layout.rs index e87007a3c6..0d8fb74a11 100644 --- a/src/commands/list/layout.rs +++ b/src/commands/list/layout.rs @@ -1214,7 +1214,9 @@ fn allocate_columns_with_priority( .copied() .filter(|kind| !allocated_kinds.contains(kind)) .collect(); - hidden_kinds.sort_by_key(|&kind| column_display_index(kind)); + // Display order, which a `[list] columns` selection defines; + // `column_display_index` is only the registry order it falls back to. + hidden_kinds.sort_by_key(|&kind| display_sort_key(kind, selected)); let hidden_columns = hidden_kinds .into_iter() .map(|kind| match kind { diff --git a/src/commands/list/model/status_symbols.rs b/src/commands/list/model/status_symbols.rs index 1f11cf94d0..72e268b80e 100644 --- a/src/commands/list/model/status_symbols.rs +++ b/src/commands/list/model/status_symbols.rs @@ -22,7 +22,7 @@ //! | 0 | `STAGED` | `+` | Are there staged changes? | //! | 1 | `MODIFIED` | `!` | Are there unstaged modifications? | //! | 2 | `UNTRACKED` | `?` | Are there untracked files? | -//! | 3 | `WORKTREE_STATE` | `✘ ↻ ⊟ ⊞ ⚑ /` | Operation / worktree attribute | +//! | 3 | `WORKTREE_STATE` | `✘ ↻ ⊟ ⊞ ⊘ ⚑ /` | Operation / worktree attribute | //! | 4 | `MAIN_STATE` | `^ _ ⊂ ✗ – ↕ ↑ ↓` | Relationship to the default branch | //! | 5 | `UPSTREAM_DIVERGENCE` | \| ⇅ ⇡ ⇣ | Relationship to the tracked remote | //! | 6 | `USER_MARKER` | emoji / text | User-defined annotation | @@ -55,15 +55,15 @@ //! //! # Gate 2: Worktree state (position 3) //! -//! **Renders:** at most one of `✘ ↻ ⊟ ⊞ ⚑ /`, priority -//! `✘ > ↻ > ⊟ > ⊞ > ⚑ > /`. The operation family (`✘↻`) comes from live +//! **Renders:** at most one of `✘ ↻ ⊟ ⊞ ⊘ ⚑ /`, priority +//! `✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > /`. The operation family (`✘↻`) comes from live //! task data; the attribute family (`⊟⊞⚑/`) is metadata, always known. //! `⚑` covers both irregular-mapping states — a duplicated branch outranks //! an off-template path, and the JSON `worktree.state` names which. //! //! **Inputs:** `data.has_conflicts`, `data.git_operation`, plus metadata -//! (`locked`, `prunable`, `duplicate_branch`, `branch_worktree_mismatch`, -//! `ItemKind::Branch`). +//! (`locked`, `prunable`, `has_branch`, `duplicate_branch`, +//! `branch_worktree_mismatch`, `ItemKind::Branch`). //! //! **Rule — short-circuit on priority:** a higher-priority signal, once known //! to be positive, resolves the gate immediately without waiting for @@ -270,7 +270,7 @@ impl PositionMask { pub(crate) const STAGED: usize = 0; // + (staged changes) pub(crate) const MODIFIED: usize = 1; // ! (modified files) pub(crate) const UNTRACKED: usize = 2; // ? (untracked files) - pub(crate) const WORKTREE_STATE: usize = 3; // Worktree: ✘↻/⚑⊟⊞ + pub(crate) const WORKTREE_STATE: usize = 3; // Worktree: ✘↻/⚑⊘⊟⊞ pub(crate) const MAIN_STATE: usize = 4; // Main relationship: ^_⊂✗↕↑↓ pub(crate) const UPSTREAM_DIVERGENCE: usize = 5; // Remote: |⇅⇡⇣ pub(crate) const USER_MARKER: usize = 6; @@ -377,11 +377,12 @@ impl WorkingTreeStatus { /// ## Mutual Exclusivity /// /// **Worktree state (operations take priority over location):** -/// Priority: ✘ > ↻ > ⊟ > ⊞ > ⚑ > / +/// Priority: ✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > / /// - ✘: Actual conflicts (must resolve) /// - ↻: A git operation is in progress (rebase, merge, cherry-pick, revert, bisect) /// - ⊟: Prunable (directory missing) /// - ⊞: Locked worktree +/// - ⊘: Detached HEAD — the worktree is on a commit, not a branch /// - ⚑: Irregular branch ⇔ worktree mapping — the branch is checked out in /// more than one worktree, or the path is off-template (informational, dim /// yellow) diff --git a/src/styling/line.rs b/src/styling/line.rs index c996636a9e..70d4cf883c 100644 --- a/src/styling/line.rs +++ b/src/styling/line.rs @@ -156,6 +156,15 @@ impl StyledLine { /// trim goes too, so no bare escape pair is left standing around nothing. pub fn trim_end(&mut self) { while let Some(last) = self.segments.last_mut() { + // An empty segment has no trailing space to find, so stopping on + // one would leave the padding before it in place. `render_text_cell` + // pushes exactly that for an empty cell, which is how a row whose + // last column renders empty kept the padding the header line + // didn't. + if last.text.is_empty() { + self.segments.pop(); + continue; + } let trimmed = last.text.trim_end_matches(' '); if trimmed.len() == last.text.len() { return; From 5b15d40970a52948be3a1494f037bc92cb704d39 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Wed, 2 Sep 2026 18:16:25 -0700 Subject: [PATCH 10/17] Realign the two snapshots main added PR #3992's bare-repo and malformed-config listings picked up this branch's header centring and right-aligned Age. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --- ...pository__bare_repo_list_no_worktrees_branches.snap | 6 +++--- ...malformed_project_config_warns_and_still_lists.snap | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_no_worktrees_branches.snap b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_no_worktrees_branches.snap index 9933d59e1d..2ab4580827 100644 --- a/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_no_worktrees_branches.snap +++ b/tests/snapshots/integration__integration_tests__bare_repository__bare_repo_list_no_worktrees_branches.snap @@ -48,9 +48,9 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -/ feature /_ b834638 1d Initial commit -/ main /_ b834638 1d Initial commit + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +/ feature /_ b834638 1d Initial commit +/ main /_ b834638 1d Initial commit ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list_config__list_malformed_project_config_warns_and_still_lists.snap b/tests/snapshots/integration__integration_tests__list_config__list_malformed_project_config_warns_and_still_lists.snap index 976f9dc234..6efd02570b 100644 --- a/tests/snapshots/integration__integration_tests__list_config__list_malformed_project_config_warns_and_still_lists.snap +++ b/tests/snapshots/integration__integration_tests__list_config__list_malformed_project_config_warns_and_still_lists.snap @@ -56,11 +56,11 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ? ^| +2 | . 05a4a45 16h Initial commit -+ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file -+ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file -+ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message +@ main ? ^| +2 | . 05a4a45 16h Initial commit ++ feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file ++ feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file ++ feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file ----- stderr ----- ▲ Project config @ _REPO_/.config/wt.toml failed to parse, skipping From 72cbe1d80919413cba4bf303a579f25fdb03cd79 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Wed, 2 Sep 2026 18:48:25 -0700 Subject: [PATCH 11/17] Finish the detached glyph's docs; simplify trim_end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four more sites in `status_symbols.rs` still listed the Gate 2 glyph set and its priority without `⊘`: the `PositionMask::FULL` entry (whose prose also needed detached between locked and duplicate_branch), the module docstring's Gate 2 walkthrough, the `StatusSymbols` display-order list, and the gate's own comment in `refresh_status_symbols`. Testing `trimmed.is_empty()` before the length comparison covers an empty segment and an all-space one in one branch, which leaves no special case to explain. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --- src/commands/list/model/status_symbols.rs | 8 ++++---- src/styling/line.rs | 21 ++++++++------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/commands/list/model/status_symbols.rs b/src/commands/list/model/status_symbols.rs index 72e268b80e..32abc46f17 100644 --- a/src/commands/list/model/status_symbols.rs +++ b/src/commands/list/model/status_symbols.rs @@ -75,7 +75,7 @@ //! `↻`, whichever operation it is. //! 3. `has_conflicts == Some(false)` and `git_operation == Some(None)` and //! metadata says prunable → `⊟`. -//! 4. …continuing down through `⊞`, `⚑`, `/`, nothing. +//! 4. …continuing down through `⊞`, `⊘`, `⚑`, `/`, nothing. //! //! Until both `has_conflicts` and `git_operation` are known, we cannot rule //! out `✘/↻`, so the position renders `·` even if metadata would otherwise @@ -282,7 +282,7 @@ impl PositionMask { 1, // STAGED: + (1 char) 1, // MODIFIED: ! (1 char) 1, // UNTRACKED: ? (1 char) - 1, // WORKTREE_STATE: ✘↻/⊟⊞⚑ (1 char, priority: conflicts > in-progress operation > prunable > locked > duplicate_branch > branch_worktree_mismatch > branch) + 1, // WORKTREE_STATE: ✘↻/⊟⊞⊘⚑ (1 char, priority: conflicts > in-progress operation > prunable > locked > detached > duplicate_branch > branch_worktree_mismatch > branch) 1, // MAIN_STATE: ^_⊂✗–↕↑↓ (1 char, priority: is_main > orphan > empty > integrated > would_conflict > same_commit > diverged > ahead > behind) 1, // UPSTREAM_DIVERGENCE: |⇡⇣⇅ (1 char) 2, // USER_MARKER: single emoji or two chars (allocate 2) @@ -369,7 +369,7 @@ impl WorkingTreeStatus { /// Symbols are categorized to enable vertical alignment in table output. /// Display order (left to right): /// - Working tree: +, !, ? (staged, modified, untracked - NOT mutually exclusive) -/// - Worktree state: ✘, ↻, /, ⚑, ⊟, ⊞ (operations + location) +/// - Worktree state: ✘, ↻, /, ⚑, ⊘, ⊟, ⊞ (operations + location) /// - Main state: ^, ✗, _, ⊂, ↕, ↑, ↓ (relationship to default branch - single-stroke vertical arrows) /// - Upstream divergence: |, ⇅, ⇡, ⇣ (relationship to remote - vertical arrows) /// - User marker: custom labels, emoji @@ -567,7 +567,7 @@ impl StatusSymbols { }; // Gate 2 — worktree state (position 3). Operation family (`✘↻`) - // takes priority over metadata family (`⚑⊟⊞/`). The gate is + // takes priority over metadata family (`⚑⊘⊟⊞/`). The gate is // `Loading` iff `operation_state` is still `None` — even when // `worktree_state` metadata would yield `⊟`, we cannot safely show // it without ruling out a pending operation signal. Once diff --git a/src/styling/line.rs b/src/styling/line.rs index 70d4cf883c..e7f9d014d1 100644 --- a/src/styling/line.rs +++ b/src/styling/line.rs @@ -156,25 +156,20 @@ impl StyledLine { /// trim goes too, so no bare escape pair is left standing around nothing. pub fn trim_end(&mut self) { while let Some(last) = self.segments.last_mut() { - // An empty segment has no trailing space to find, so stopping on - // one would leave the padding before it in place. `render_text_cell` - // pushes exactly that for an empty cell, which is how a row whose - // last column renders empty kept the padding the header line - // didn't. - if last.text.is_empty() { + let trimmed = last.text.trim_end_matches(' '); + // Nothing but spaces left, which includes a segment that was empty + // to begin with — `render_text_cell` pushes one of those for an + // empty cell, and stopping on it would leave the padding before it + // standing. + if trimmed.is_empty() { self.segments.pop(); continue; } - let trimmed = last.text.trim_end_matches(' '); if trimmed.len() == last.text.len() { return; } - if trimmed.is_empty() { - self.segments.pop(); - } else { - last.text.truncate(trimmed.len()); - return; - } + last.text.truncate(trimmed.len()); + return; } } From 7fd0072c5ba68a0d2553250fa7c8d32aba6b9343 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Wed, 2 Sep 2026 19:14:43 -0700 Subject: [PATCH 12/17] Name the detached glyph in the attribute family too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docstring listed `⊘` in the Gate 2 priority order and then omitted it from the family breakdown on the next line. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01XAUYWFN9d9oh6jyoQiouHb --- src/commands/list/model/status_symbols.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/list/model/status_symbols.rs b/src/commands/list/model/status_symbols.rs index 32abc46f17..11c4b5d759 100644 --- a/src/commands/list/model/status_symbols.rs +++ b/src/commands/list/model/status_symbols.rs @@ -57,7 +57,7 @@ //! //! **Renders:** at most one of `✘ ↻ ⊟ ⊞ ⊘ ⚑ /`, priority //! `✘ > ↻ > ⊟ > ⊞ > ⊘ > ⚑ > /`. The operation family (`✘↻`) comes from live -//! task data; the attribute family (`⊟⊞⚑/`) is metadata, always known. +//! task data; the attribute family (`⊟⊞⊘⚑/`) is metadata, always known. //! `⚑` covers both irregular-mapping states — a duplicated branch outranks //! an off-template path, and the JSON `worktree.state` names which. //! From c92553d21ba20f78e1a1d365ff6d25628f987e40 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sun, 6 Sep 2026 11:33:33 -0700 Subject: [PATCH 13/17] Fix wrapped progressive list footers --- src/commands/list/progressive_table.rs | 57 ++++++++++++++++----- tests/integration_tests/list_progressive.rs | 28 ++++++++++ 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/commands/list/progressive_table.rs b/src/commands/list/progressive_table.rs index 0742a018da..bceab36b4b 100644 --- a/src/commands/list/progressive_table.rs +++ b/src/commands/list/progressive_table.rs @@ -187,19 +187,25 @@ impl ProgressiveTable { /// Fit a content string to the table's width and, when color is off, strip /// the escapes the render code baked into it. /// - /// Truncation runs first because the width math is escape-aware, and a cut - /// through styled content appends its own reset; stripping afterwards removes - /// that reset along with the rest. + /// Each physical row gets its own width budget. Truncation runs first because + /// the width math is escape-aware, and a cut through styled content appends + /// its own reset; stripping afterwards removes that reset along with the rest. /// Every content line — header, rows, footer, on both the in-place and /// overflow paths — goes through here, so no other content path calls /// [`truncate_visible`] directly. fn prepare(&self, content: &str) -> String { - let truncated = truncate_visible(content, self.max_width); - if self.strip_content { - anstream::adapter::strip_str(&truncated).to_string() - } else { - truncated - } + content + .split('\n') + .map(|row| { + let truncated = truncate_visible(row, self.max_width); + if self.strip_content { + anstream::adapter::strip_str(&truncated).to_string() + } else { + truncated + } + }) + .collect::>() + .join("\n") } /// Print the skeleton table to stdout. @@ -334,6 +340,23 @@ impl ProgressiveTable { stdout.flush() } + /// Replace the final footer after every data row has settled. + /// + /// The summary is the only content that can span physical rows. Nothing is + /// redrawn after it, so the renderer can clear through the prompt reserve, + /// write the complete footer, and establish a fresh reserve below it. + fn redraw_final_footer(&self) -> std::io::Result<()> { + let mut stdout = stdout(); + let footer_idx = self.lines.len() - 1; + + stdout.execute(MoveUp(1))?; + stdout.execute(MoveToColumn(0))?; + stdout.execute(Clear(ClearType::FromCursorDown))?; + writeln!(stdout, "{}", self.lines[footer_idx])?; + write_prompt_reserve(&mut stdout)?; + stdout.flush() + } + /// Finalize the table with final row content and footer. /// /// For the normal case, updates rows in-place and redraws the footer. @@ -371,12 +394,22 @@ impl ProgressiveTable { write_prompt_reserve(&mut stdout)?; stdout.flush() } else { - // Normal: update rows in-place + footer + // Redraw rows while the on-screen footer still occupies one physical + // row, then replace it with the final summary, which may wrap. for (idx, row) in final_rows.into_iter().enumerate() { self.update_row(idx, row); } - self.update_footer(final_footer); - self.flush() + self.flush()?; + + let footer_idx = self.lines.len() - 1; + let final_footer = self.prepare(&final_footer); + if self.lines[footer_idx] != final_footer { + self.lines[footer_idx] = final_footer; + if self.rendered { + self.redraw_final_footer()?; + } + } + Ok(()) } } } diff --git a/tests/integration_tests/list_progressive.rs b/tests/integration_tests/list_progressive.rs index 5f5f351de8..3212cd0070 100644 --- a/tests/integration_tests/list_progressive.rs +++ b/tests/integration_tests/list_progressive.rs @@ -146,6 +146,34 @@ fn test_list_progressive_overflow(mut repo: TestRepo) { ); } +/// A wrapped final footer occupies several physical rows. The progressive +/// renderer must preserve every row instead of applying one width budget to +/// the whole string and replacing the continuation with an ellipsis. +#[rstest] +fn test_list_progressive_wraps_final_footer(mut repo: TestRepo) { + for branch in ["feature-alpha", "feature-beta", "feature-gamma"] { + repo.add_worktree(branch); + } + + let mut opts = ProgressiveCaptureOptions::with_byte_interval(500); + opts.terminal_size = (24, 40); + + let output = capture_progressive_output(&repo, "list", &[], opts); + assert_eq!(output.exit_code, 0); + + let final_text = output.final_output(); + let footer = final_text + .split_once("Showing") + .map(|(_, footer)| footer) + .expect("final output should contain the summary footer"); + assert!(footer.contains("hidden:"), "footer:\n{footer}"); + assert!(footer.contains("Path"), "footer:\n{footer}"); + assert!( + !footer.lines().any(|line| line.trim() == "…"), + "footer:\n{footer}" + ); +} + /// Tests progressive rendering with no worktrees (fast path). #[rstest] fn test_list_progressive_fast_command(repo: TestRepo) { From 879de26195f59d6379cc56c59eb09bc480fed303 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sun, 6 Sep 2026 11:54:27 -0700 Subject: [PATCH 14/17] test: pin progressive footer cursor row --- tests/integration_tests/list_progressive.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/integration_tests/list_progressive.rs b/tests/integration_tests/list_progressive.rs index 3212cd0070..5e657b8ab8 100644 --- a/tests/integration_tests/list_progressive.rs +++ b/tests/integration_tests/list_progressive.rs @@ -172,6 +172,19 @@ fn test_list_progressive_wraps_final_footer(mut repo: TestRepo) { !footer.lines().any(|line| line.trim() == "…"), "footer:\n{footer}" ); + + // The footer redraw moves up exactly one row before clearing. An + // off-by-one there would swallow the spacer and every assertion above + // would still pass, since they only look below "Showing". + let rows: Vec<_> = final_text.lines().collect(); + let footer_start = rows + .iter() + .position(|row| row.contains("Showing")) + .expect("final output should contain the summary footer"); + assert!( + rows[footer_start - 1].trim().is_empty(), + "spacer row should survive the footer redraw.\nFinal output:\n{final_text}" + ); } /// Tests progressive rendering with no worktrees (fast path). From 72c2133deb80a5d964373ee377034a3f9479f79b Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Mon, 7 Sep 2026 14:28:02 -0700 Subject: [PATCH 15/17] Simplify list column labels and alignment --- .claude/skills/writing-user-outputs/SKILL.md | 3 +- dev/config.example.toml | 4 +- docs/src/content/docs/config.md | 4 +- docs/src/content/docs/list.md | 2 +- .../skills/worktrunk/reference/config.md | 4 +- .../skills/worktrunk/reference/list.md | 2 +- skills/worktrunk/reference/config.md | 4 +- skills/worktrunk/reference/list.md | 2 +- src/cli/mod.rs | 6 +- src/commands/list/collect/mod.rs | 2 - src/commands/list/columns.rs | 83 +----------- src/commands/list/layout.rs | 120 +++--------------- src/commands/list/progressive_table.rs | 22 +++- src/commands/list/render.rs | 89 +++++++++++-- ...ests__snapshot_path_yields_to_summary.snap | 4 +- src/commands/picker/mod.rs | 2 - ...ation_tests__help__help_config_create.snap | 4 +- ...gration_tests__help__help_config_long.snap | 4 +- ...tegration_tests__help__help_list_long.snap | 2 +- ...tion_tests__help__help_list_narrow_80.snap | 4 +- ...nches_with_nonexistent_default_branch.snap | 2 +- ..._full_with_nonexistent_default_branch.snap | 2 +- ..._list_with_nonexistent_default_branch.snap | 2 +- ...yout__detached_and_prunable_width_120.snap | 2 +- 24 files changed, 149 insertions(+), 226 deletions(-) diff --git a/.claude/skills/writing-user-outputs/SKILL.md b/.claude/skills/writing-user-outputs/SKILL.md index 4d55495e8f..f304e19fc0 100644 --- a/.claude/skills/writing-user-outputs/SKILL.md +++ b/.claude/skills/writing-user-outputs/SKILL.md @@ -1110,7 +1110,8 @@ eprintln!("{}", success_message(format!( - **Single-value numeric columns** (Age): right-aligned, so `now` and `4m` line up on the unit - **Diff columns** (HEAD±, main↕): two right-aligned halves either side of a - separator (`+999 -999`) + separator (`+999 -999`); a state for the whole field, such as a loading or + in-sync marker, is centered A header follows its content, except over a diff column, where it centres: pushed to either edge it stands over one half and reads as that half's label, diff --git a/dev/config.example.toml b/dev/config.example.toml index 58ac92a503..b83d81791d 100644 --- a/dev/config.example.toml +++ b/dev/config.example.toml @@ -119,8 +119,8 @@ # - `branch` — The branch name # - `status` — Git status symbols, plus any user-defined status # - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -# - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) -# - `branch-diff` — Line changes against the default branch (header `main…±`, likewise) +# - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) +# - `branch-diff` — Line changes against the default branch (header `main…±`) # - `summary` — An LLM-generated summary of the branch # - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) # - `ci` — CI status of the head commit diff --git a/docs/src/content/docs/config.md b/docs/src/content/docs/config.md index 1df44f71cc..5d0413d832 100644 --- a/docs/src/content/docs/config.md +++ b/docs/src/content/docs/config.md @@ -212,8 +212,8 @@ Valid built-in names: - `branch` — The branch name - `status` — Git status symbols, plus any user-defined status - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) -- `branch-diff` — Line changes against the default branch (header `main…±`, likewise) +- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) +- `branch-diff` — Line changes against the default branch (header `main…±`) - `summary` — An LLM-generated summary of the branch - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) - `ci` — CI status of the head commit diff --git a/docs/src/content/docs/list.md b/docs/src/content/docs/list.md index 3124f66d07..c5302e7efe 100644 --- a/docs/src/content/docs/list.md +++ b/docs/src/content/docs/list.md @@ -91,7 +91,7 @@ $ wt list --format=json | Age | Time since last commit | | Message | Last commit message (truncated) | -The `main↕` and `main…±` headers name the default branch: its own name where it fits the width those columns already need (`trunk↕`, `master…±`), and `base` where it doesn't, so a long name never widens every row. +The `main↕` and `main…±` headers keep the familiar `main` label in every repository. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. diff --git a/plugins/worktrunk/skills/worktrunk/reference/config.md b/plugins/worktrunk/skills/worktrunk/reference/config.md index 12a8a410e3..72abed5ef6 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/config.md +++ b/plugins/worktrunk/skills/worktrunk/reference/config.md @@ -206,8 +206,8 @@ Valid built-in names: - `branch` — The branch name - `status` — Git status symbols, plus any user-defined status - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) -- `branch-diff` — Line changes against the default branch (header `main…±`, likewise) +- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) +- `branch-diff` — Line changes against the default branch (header `main…±`) - `summary` — An LLM-generated summary of the branch - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) - `ci` — CI status of the head commit diff --git a/plugins/worktrunk/skills/worktrunk/reference/list.md b/plugins/worktrunk/skills/worktrunk/reference/list.md index 8227ad43b9..359aabd183 100644 --- a/plugins/worktrunk/skills/worktrunk/reference/list.md +++ b/plugins/worktrunk/skills/worktrunk/reference/list.md @@ -78,7 +78,7 @@ $ wt list --format=json | Age | Time since last commit | | Message | Last commit message (truncated) | -The `main↕` and `main…±` headers name the default branch: its own name where it fits the width those columns already need (`trunk↕`, `master…±`), and `base` where it doesn't, so a long name never widens every row. +The `main↕` and `main…±` headers keep the familiar `main` label in every repository. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. diff --git a/skills/worktrunk/reference/config.md b/skills/worktrunk/reference/config.md index 12a8a410e3..72abed5ef6 100644 --- a/skills/worktrunk/reference/config.md +++ b/skills/worktrunk/reference/config.md @@ -206,8 +206,8 @@ Valid built-in names: - `branch` — The branch name - `status` — Git status symbols, plus any user-defined status - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) -- `branch-diff` — Line changes against the default branch (header `main…±`, likewise) +- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) +- `branch-diff` — Line changes against the default branch (header `main…±`) - `summary` — An LLM-generated summary of the branch - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) - `ci` — CI status of the head commit diff --git a/skills/worktrunk/reference/list.md b/skills/worktrunk/reference/list.md index 8227ad43b9..359aabd183 100644 --- a/skills/worktrunk/reference/list.md +++ b/skills/worktrunk/reference/list.md @@ -78,7 +78,7 @@ $ wt list --format=json | Age | Time since last commit | | Message | Last commit message (truncated) | -The `main↕` and `main…±` headers name the default branch: its own name where it fits the width those columns already need (`trunk↕`, `master…±`), and `base` where it doesn't, so a long name never widens every row. +The `main↕` and `main…±` headers keep the familiar `main` label in every repository. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](https://worktrunk.dev/config/#list); `--format=json` carries every field at any width. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 0f3d846179..5da113a57b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -859,7 +859,7 @@ $ wt list --format=json | Age | Time since last commit | | Message | Last commit message (truncated) | -The `main↕` and `main…±` headers name the default branch: its own name where it fits the width those columns already need (`trunk↕`, `master…±`), and `base` where it doesn't, so a long name never widens every row. +The `main↕` and `main…±` headers keep the familiar `main` label in every repository. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (`hidden: Commit, Age, Message`). A column with nothing to show on any row — `Remote⇅` in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [`[list] columns`](/config/#list); `--format=json` carries every field at any width. @@ -2156,8 +2156,8 @@ Valid built-in names: - `branch` — The branch name - `status` — Git status symbols, plus any user-defined status - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) -- `branch-diff` — Line changes against the default branch (header `main…±`, likewise) +- `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) +- `branch-diff` — Line changes against the default branch (header `main…±`) - `summary` — An LLM-generated summary of the branch - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`) - `ci` — CI status of the head commit diff --git a/src/commands/list/collect/mod.rs b/src/commands/list/collect/mod.rs index 4543a51c37..7b032782b4 100644 --- a/src/commands/list/collect/mod.rs +++ b/src/commands/list/collect/mod.rs @@ -1390,7 +1390,6 @@ pub fn collect( has_remote: repo.primary_remote().is_ok(), url_template: url_template.as_deref(), max_pr_number, - default_branch: default_branch.as_deref(), }, ); @@ -2630,7 +2629,6 @@ remove the file manually to continue."; has_remote: true, url_template: None, max_pr_number: None, - default_branch: None, }, ); let placeholder = super::super::render::PLACEHOLDER; diff --git a/src/commands/list/columns.rs b/src/commands/list/columns.rs index caea64b339..82da10949e 100644 --- a/src/commands/list/columns.rs +++ b/src/commands/list/columns.rs @@ -1,7 +1,3 @@ -use std::borrow::Cow; - -use unicode_width::UnicodeWidthStr; - use super::collect::TaskKind; /// Logical identifier for each column rendered by `wt list`. @@ -28,48 +24,16 @@ pub enum ColumnKind { Custom(u8), } -/// Label the two default-branch columns fall back to when the branch's own -/// name is too long to head them (see [`base_label`]). -pub const GENERIC_BASE_LABEL: &str = "base"; - -/// Widest default-branch name that can head `AheadBehind` and `BranchDiff`. -/// -/// Both columns are sized by their data, not their header: `↑99 ↓99` is 7 -/// cells and `+999 -999` is 9. Subtracting each header's suffix (`↕` is 1 -/// cell, `…±` is 2) leaves budgets of 6 and 7, and the tighter one governs so -/// the two columns can't disagree about which label to use. A name within it -/// costs the table nothing — `test_base_label_never_widens_its_columns` pins -/// that. -const MAX_BASE_LABEL: usize = 6; - -/// The label the two default-branch columns head with. -/// -/// The repo's own default-branch name where it fits — `trunk↕`, `master…±` — -/// so the header names what the numbers are measured against instead of -/// asserting `main` in a repo that has no such branch. A longer name would -/// widen two columns on every row to spell out a word the reader takes in -/// once, so it gives way to `base`. Both columns take this one decision, so -/// they always name the same thing. -pub fn base_label(default_branch: Option<&str>) -> &str { - default_branch - .filter(|name| name.width() <= MAX_BASE_LABEL) - .unwrap_or(GENERIC_BASE_LABEL) -} - impl ColumnKind { /// This column's header text. - /// - /// `base` is the default-branch label from [`base_label`]; only - /// `AheadBehind` and `BranchDiff` — the columns measured against the - /// default branch — read it. - pub fn header(self, base: &str) -> Cow<'static, str> { - Cow::Borrowed(match self { + pub const fn header(self) -> &'static str { + match self { ColumnKind::Gutter => "", ColumnKind::Branch => "Branch", ColumnKind::Status => "Status", ColumnKind::WorkingDiff => "HEAD±", - ColumnKind::AheadBehind => return Cow::Owned(format!("{base}↕")), - ColumnKind::BranchDiff => return Cow::Owned(format!("{base}…±")), + ColumnKind::AheadBehind => "main↕", + ColumnKind::BranchDiff => "main…±", ColumnKind::Path => "Path", ColumnKind::Upstream => "Remote⇅", ColumnKind::Url => "URL", @@ -81,7 +45,7 @@ impl ColumnKind { // Header is the resolved column name; layout substitutes it when // building `ColumnLayout`. ColumnKind::Custom(_) => "", - }) + } } /// Get the base priority for this column (lower = more important). @@ -514,7 +478,7 @@ mod tests { for kind in COLUMN_SPECS.iter().map(|spec| spec.kind) { if kind != ColumnKind::Gutter { assert!( - !kind.header(GENERIC_BASE_LABEL).is_empty(), + !kind.header().is_empty(), "{:?} should have a non-empty header", kind ); @@ -522,41 +486,6 @@ mod tests { } } - #[test] - fn test_base_label_names_the_default_branch_when_it_fits() { - assert_eq!(base_label(Some("main")), "main"); - assert_eq!(base_label(Some("trunk")), "trunk"); - assert_eq!(base_label(Some("master")), "master"); - // Past the budget the generic label stands in, as it does when the - // default branch can't be resolved at all. - assert_eq!(base_label(Some("develop")), GENERIC_BASE_LABEL); - assert_eq!(base_label(None), GENERIC_BASE_LABEL); - - assert_eq!(ColumnKind::AheadBehind.header("trunk"), "trunk↕"); - assert_eq!(ColumnKind::BranchDiff.header("trunk"), "trunk…±"); - // Every other column ignores the label. - assert_eq!(ColumnKind::Upstream.header("trunk"), "Remote⇅"); - } - - /// A label within [`MAX_BASE_LABEL`] costs the table nothing: both - /// default-branch columns are already as wide as their data (`↑99 ↓99`, - /// `+999 -999`), so the header fits inside the width they'd have anyway. - /// This is what lets the real branch name in without widening every row. - #[test] - fn test_base_label_never_widens_its_columns() { - for name in ["main", "master", "trunk", "dev", "Δmain"] { - let base = base_label(Some(name)); - assert!( - ColumnKind::AheadBehind.header(base).width() <= 7, - "{name}: ahead/behind header outgrew its data width" - ); - assert!( - ColumnKind::BranchDiff.header(base).width() <= 9, - "{name}: branch-diff header outgrew its data width" - ); - } - } - #[test] fn test_all_column_kinds_have_priority() { // Every ColumnKind variant must be in COLUMN_SPECS so priority() works correctly. diff --git a/src/commands/list/layout.rs b/src/commands/list/layout.rs index 0d8fb74a11..8e260c635c 100644 --- a/src/commands/list/layout.rs +++ b/src/commands/list/layout.rs @@ -313,12 +313,6 @@ pub struct RepoFacts<'a> { /// Largest PR/MR number any previous fetch cached, which sizes the CI /// column before this run's fetch reports. pub max_pr_number: Option, - - /// The default branch's name, which heads the two columns measured - /// against it. `None` when it can't be resolved; either way the label - /// itself comes from [`base_label`](super::columns::base_label), which - /// falls back to `base`. - pub default_branch: Option<&'a str>, } /// Tracks which columns have actual data (vs just headers) @@ -340,9 +334,6 @@ pub struct LayoutMetadata { pub widths: ColumnWidths, pub data_flags: ColumnDataFlags, pub status_position_mask: super::model::PositionMask, - /// What the two default-branch columns head with, resolved once here so - /// every header, width floor and footer entry names the same thing. - pub base_label: String, } /// Added to a column's base priority when it has nothing to show on any row. @@ -815,15 +806,14 @@ fn build_estimated_widths( // `PositionMask::FULL`, so the column has to be at least that wide or the // cell overflows it. Read the width off the mask rather than restating its // arithmetic — the two can't drift. - let base = super::columns::base_label(facts.default_branch); let status_fixed = fit_header( - &ColumnKind::Status.header(base), + ColumnKind::Status.header(), super::model::PositionMask::FULL.total_width(), ); - let working_diff_fixed = fit_header(&ColumnKind::WorkingDiff.header(base), 9); // "+999 -999" - let ahead_behind_fixed = fit_header(&ColumnKind::AheadBehind.header(base), 7); // "↑99 ↓99" - let branch_diff_fixed = fit_header(&ColumnKind::BranchDiff.header(base), 9); // "+999 -999" - let upstream_fixed = fit_header(&ColumnKind::Upstream.header(base), 7); // "↑99 ↓99" + let working_diff_fixed = fit_header(ColumnKind::WorkingDiff.header(), 9); // "+999 -999" + let ahead_behind_fixed = fit_header(ColumnKind::AheadBehind.header(), 7); // "↑99 ↓99" + let branch_diff_fixed = fit_header(ColumnKind::BranchDiff.header(), 9); // "+999 -999" + let upstream_fixed = fit_header(ColumnKind::Upstream.header(), 7); // "↑99 ↓99" let age_estimate = 4; // "11mo" (short format) // CI column: PR/MR reference ("#3035"), sized from the cached largest // number seen; "#9999" before the first fetch populates the cache. A @@ -831,7 +821,7 @@ fn build_estimated_widths( // (`PrStatus::format_cell`) — the layout never resizes mid-render — and // the ratcheted cache sizes the next invocation correctly. let ci_estimate = fit_header( - &ColumnKind::CiStatus.header(base), + ColumnKind::CiStatus.header(), super::ci_status::pr_ref_width(facts.max_pr_number.unwrap_or(9999)), ); @@ -857,7 +847,7 @@ fn build_estimated_widths( // URL width estimated from template + longest branch (or fallback) // When url_width is 0 (no template), don't allocate any space for URL column let url_estimate = if url_width > 0 { - fit_header(&ColumnKind::Url.header(base), url_width) + fit_header(ColumnKind::Url.header(), url_width) } else { 0 }; @@ -898,7 +888,6 @@ fn build_estimated_widths( widths, data_flags, status_position_mask: super::model::PositionMask::FULL, - base_label: base.to_string(), } } @@ -1074,7 +1063,7 @@ fn allocate_columns_with_priority( let is_first = !needs_spacing(&pending); let min_width = if spec.shrinkable { - Some(spec.kind.header(&metadata.base_label).width().max(1)) + Some(spec.kind.header().width().max(1)) } else { None }; @@ -1192,7 +1181,7 @@ fn allocate_columns_with_priority( ColumnKind::Custom(i) => { std::borrow::Cow::Owned(custom_columns[i as usize].name.clone()) } - kind => kind.header(&metadata.base_label), + kind => std::borrow::Cow::Borrowed(kind.header()), }; columns.push(ColumnLayout { kind: col.spec.kind, @@ -1221,7 +1210,7 @@ fn allocate_columns_with_priority( .into_iter() .map(|kind| match kind { ColumnKind::Custom(i) => custom_columns[i as usize].name.clone(), - kind => kind.header(&metadata.base_label).into_owned(), + kind => kind.header().to_string(), }) .collect(); @@ -1294,8 +1283,7 @@ pub fn calculate_layout_with_width( .max() .unwrap_or(0) .min(MAX_BRANCH); - let base = super::columns::base_label(facts.default_branch); - let max_branch = fit_header(&ColumnKind::Branch.header(base), max_branch); + let max_branch = fit_header(ColumnKind::Branch.header(), max_branch); let path_data_width = items .iter() @@ -1303,7 +1291,7 @@ pub fn calculate_layout_with_width( .map(|path| shorten_path(path, main_worktree_path).width()) .max() .unwrap_or(0); - let max_path_width = fit_header(&ColumnKind::Path.header(base), path_data_width); + let max_path_width = fit_header(ColumnKind::Path.header(), path_data_width); // The Path column is redundant with Branch unless a path says something the // branch name doesn't: the worktree sits off-template, two worktrees share @@ -1357,7 +1345,7 @@ pub fn calculate_layout_with_width( .map(|item| item.short_sha.width()) .max() .unwrap_or(0); - let commit_width = fit_header(&ColumnKind::Commit.header(base), commit_data_width); + let commit_width = fit_header(ColumnKind::Commit.header(), commit_data_width); allocate_columns_with_priority( &metadata, @@ -1376,16 +1364,14 @@ mod tests { use worktrunk::git::LineDiff; use worktrunk::styling::terminal_width; - /// The default repository context: a remote exists, the default branch is - /// `main`, and there's no dev-server URL template or cached PR/MR number. - /// Tests that care about one of these override it - /// (`RepoFacts { has_remote: false, ..test_facts() }`). + /// The default repository context: a remote exists, with no dev-server URL + /// template or cached PR/MR number. Tests that care about one of these + /// override it (`RepoFacts { has_remote: false, ..test_facts() }`). fn test_facts() -> RepoFacts<'static> { RepoFacts { has_remote: true, url_template: None, max_pr_number: None, - default_branch: Some("main"), } } @@ -2187,80 +2173,6 @@ mod tests { ); } - /// The two default-branch columns head with the repo's own branch name, - /// and the layout is the piece that has to agree with itself about it: - /// the rendered header, the width floor and the hidden-column footer all - /// read the one label. A name that fits costs no width, so a `trunk` repo - /// lays out exactly as a `main` one does. - #[test] - fn test_headers_name_the_default_branch() { - let items = vec![make_test_item("feature-branch")]; - let layout_at = |default_branch, width| { - calculate_layout_with_width( - &items, - &non_full_run_tasks(), - Destination { - width, - link_style: LinkStyle::Expanded, - }, - Path::new("/test"), - ColumnSelection { - custom: &[], - selected: None, - }, - RepoFacts { - default_branch, - ..test_facts() - }, - ) - }; - - let trunk = layout_at(Some("trunk"), 200); - let ahead_behind = - find_column(&trunk, ColumnKind::AheadBehind).expect("AheadBehind allocated"); - assert_eq!(ahead_behind.header, "trunk↕"); - assert_eq!( - find_column(&trunk, ColumnKind::BranchDiff) - .expect("BranchDiff allocated") - .header, - "trunk…±" - ); - - let main = layout_at(Some("main"), 200); - assert_eq!( - ahead_behind.width, - find_column(&main, ColumnKind::AheadBehind).unwrap().width, - "a name within the budget doesn't widen the column" - ); - - // A name too long to head the columns gives way to the generic label, - // and an unresolved default branch lands on the same one. - assert_eq!( - find_column( - &layout_at(Some("development"), 200), - ColumnKind::AheadBehind - ) - .unwrap() - .header, - "base↕" - ); - assert_eq!( - find_column(&layout_at(None, 200), ColumnKind::AheadBehind) - .unwrap() - .header, - "base↕" - ); - - // The footer names a dropped column by the same label the header - // would have carried. - let narrow = layout_at(Some("trunk"), 40); - assert!( - narrow.hidden_columns.iter().any(|name| name == "trunk…±"), - "the footer names the hidden column as the header would: {:?}", - narrow.hidden_columns - ); - } - #[test] fn test_layout_renders_column_iff_task_planned() { // The layout stage honors the run plan literally: a column renders iff one diff --git a/src/commands/list/progressive_table.rs b/src/commands/list/progressive_table.rs index bceab36b4b..8dfef04a72 100644 --- a/src/commands/list/progressive_table.rs +++ b/src/commands/list/progressive_table.rs @@ -27,6 +27,14 @@ //! Stripping uses [`anstream::adapter::strip_str`], the exact transform an anstream //! stream in `Never` mode applies, so progressive output matches the buffered path //! by construction rather than by approximation. +//! +//! # Row-count invariant +//! +//! Incremental redraw addresses entries by physical row, so the header, every +//! data row, and the loading footer must each remain single-line. Only the final +//! summary may span rows: [`ProgressiveTable::finalize`] installs it after all +//! incremental redraws are complete, through a redraw path that clears and +//! rebuilds the region below the last data row. use crossterm::{ ExecutableCommand, @@ -84,8 +92,10 @@ fn write_prompt_reserve(stdout: &mut std::io::Stdout) -> std::io::Result<()> { /// in. The crossterm cursor-control sequences written alongside them are never /// stripped; see the module docs for that split. pub struct ProgressiveTable { - /// Previously rendered content for each line (header + rows + spacer + footer), - /// each already passed through [`ProgressiveTable::prepare`] + /// Previously rendered single-line entries (header + rows + spacer + loading + /// footer), each already passed through [`ProgressiveTable::prepare`]. The + /// final footer may replace the last entry with multiline content only after + /// incremental redraw is finished. lines: Vec, /// Maximum width for content (terminal width - safety margin) max_width: usize, @@ -245,6 +255,10 @@ impl ProgressiveTable { } let prepared = self.prepare(&content); + debug_assert!( + !prepared.contains('\n'), + "incrementally redrawn table rows must occupy one physical line" + ); // Line index: header (0) + row_idx let line_idx = row_idx + 1; @@ -270,6 +284,10 @@ impl ProgressiveTable { /// `true` if the content changed, `false` if unchanged. pub fn update_footer(&mut self, content: String) -> bool { let prepared = self.prepare(&content); + debug_assert!( + !prepared.contains('\n'), + "incrementally redrawn table footers must occupy one physical line" + ); // Footer is the last line let footer_idx = self.lines.len() - 1; diff --git a/src/commands/list/render.rs b/src/commands/list/render.rs index 156836daec..9640ef9ed8 100644 --- a/src/commands/list/render.rs +++ b/src/commands/list/render.rs @@ -426,16 +426,15 @@ impl ColumnLayout { } } - /// Render a cell's content against the column's own edge. + /// Render a whole-cell state according to the column's value shape. fn aligned_cell(&self, content: StyledLine) -> StyledLine { let leading = match self.alignment() { CellAlignment::Left => return content, - // A split column's own renderer places the halves; only a - // single-glyph stand-in reaches here, and it goes to the right - // edge the way the whole field does. - CellAlignment::Right | CellAlignment::Split => { - self.width.saturating_sub(content.width()) - } + CellAlignment::Right => self.width.saturating_sub(content.width()), + // A split column's value renderer owns the two halves. A loading + // or skipped marker describes the whole field, so it sits between + // them just like the header and the in-sync marker do. + CellAlignment::Split => self.width.saturating_sub(content.width()) / 2, }; if leading == 0 { return content; @@ -693,6 +692,7 @@ mod tests { use super::*; use crate::commands::list::layout::DiffDisplayConfig; use ansi_str::AnsiStr; + use insta::assert_snapshot; use std::path::PathBuf; use worktrunk::styling::{ADDITION, DELETION}; @@ -717,6 +717,75 @@ mod tests { } } + /// Alignment follows the shape of the value: text starts at the left, + /// scalar values end at the right, and labels or states for a split value + /// sit over its centre. + #[test] + fn test_alignment_policy_by_column_shape() { + let columns = [ + ColumnLayout { + kind: ColumnKind::Branch, + header: std::borrow::Cow::Borrowed("Branch"), + start: 0, + width: 8, + format: ColumnFormat::Text, + }, + ColumnLayout { + kind: ColumnKind::Time, + header: std::borrow::Cow::Borrowed("Age"), + start: 0, + width: 4, + format: ColumnFormat::Text, + }, + ColumnLayout { + kind: ColumnKind::WorkingDiff, + header: std::borrow::Cow::Borrowed("HEAD±"), + start: 0, + width: 9, + format: ColumnFormat::Diff(DiffColumnConfig { + positive_digits: 3, + negative_digits: 3, + total_width: 9, + display: DiffDisplayConfig { + variant: super::super::columns::DiffVariant::Signs, + positive_style: ADDITION, + negative_style: DELETION, + }, + }), + }, + ]; + + let headers = columns + .iter() + .cloned() + .map(cell_layout) + .map(|layout| { + layout + .render_header_line() + .render() + .ansi_strip() + .into_owned() + }) + .collect::>() + .join("|"); + let placeholders = columns + .iter() + .map(|column| { + column + .placeholder_cell(PLACEHOLDER) + .render() + .ansi_strip() + .into_owned() + }) + .collect::>() + .join("|"); + + assert_snapshot!(format!("{headers}\n{placeholders}"), @" + Branch| Age| HEAD± + ·| ·| · + "); + } + #[test] fn test_format_aligned_produces_fixed_width_output() { use super::super::columns::DiffVariant; @@ -1628,11 +1697,11 @@ mod tests { Default::default(), ); let cell = col.render_cell(&wt_item, &cell_layout(col.clone()), PLACEHOLDER); - insta::assert_snapshot!(cell.render(), @" ·"); + insta::assert_snapshot!(cell.render(), @" ·"); // Stale placeholder let cell = col.render_cell(&wt_item, &cell_layout(col.clone()), "·"); - insta::assert_snapshot!(cell.render(), @" ·"); + insta::assert_snapshot!(cell.render(), @" ·"); } #[test] @@ -1662,7 +1731,7 @@ mod tests { let item = ListItem::new_branch("abc123".into(), "feat".into()); assert!(item.upstream.is_none()); let cell = col.render_cell(&item, &cell_layout(col.clone()), PLACEHOLDER); - insta::assert_snapshot!(cell.render(), @" ·"); + insta::assert_snapshot!(cell.render(), @" ·"); // upstream: Some(default) (loaded, no active upstream) → blank let mut item = ListItem::new_branch("abc123".into(), "feat".into()); diff --git a/src/commands/list/snapshots/wt__commands__list__layout__tests__snapshot_path_yields_to_summary.snap b/src/commands/list/snapshots/wt__commands__list__layout__tests__snapshot_path_yields_to_summary.snap index 9bcd7413e8..cb2fbb1983 100644 --- a/src/commands/list/snapshots/wt__commands__list__layout__tests__snapshot_path_yields_to_summary.snap +++ b/src/commands/list/snapshots/wt__commands__list__layout__tests__snapshot_path_yields_to_summary.snap @@ -4,6 +4,6 @@ expression: table --- Branch Status HEAD± main↕ main…± Summary Remote⇅ CI Commit Age Message @ main · ···· | a620bcf 1y Some comm… -+ hourly-maintenance · ···· ↑2 · · a620bcf 1y Some comm… ++ hourly-maintenance · ···· ↑2 · · a620bcf 1y Some comm… + lab-continue · ···· ↑1 ↓2 +28 -1 Add extend and block insert in Markdown parser | a620bcf 1y Some comm… -+ dry-run-pager · ···· ↑3 ↓1 · | a620bcf 1y Some comm… ++ dry-run-pager · ···· ↑3 ↓1 · | a620bcf 1y Some comm… diff --git a/src/commands/picker/mod.rs b/src/commands/picker/mod.rs index 2f8fef04d0..070e4557dc 100644 --- a/src/commands/picker/mod.rs +++ b/src/commands/picker/mod.rs @@ -3255,7 +3255,6 @@ pub mod tests { has_remote: true, url_template: None, max_pr_number: None, - default_branch: None, }, )); (row, token, rendered, morphed) @@ -4239,7 +4238,6 @@ pub mod tests { has_remote: true, url_template: None, max_pr_number: None, - default_branch: None, }, ); diff --git a/tests/snapshots/integration__integration_tests__help__help_config_create.snap b/tests/snapshots/integration__integration_tests__help__help_config_create.snap index 23d55dd41d..2d034369ab 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_create.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_create.snap @@ -195,8 +195,8 @@ Creates ~/.config/worktrunk/config.toml with the following content:   # - `branch` — The branch name   # - `status` — Git status symbols, plus any user-defined status   # - `working-diff` — Uncommitted line changes against `HEAD`, including untracked files (header `HEAD±`) -  # - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`, or whatever the default branch is called) -  # - `branch-diff` — Line changes against the default branch (header `main…±`, likewise) +  # - `ahead-behind` — Commits ahead of and behind the default branch (header `main↕`) +  # - `branch-diff` — Line changes against the default branch (header `main…±`)   # - `summary` — An LLM-generated summary of the branch   # - `upstream` — Commits ahead of and behind the upstream tracking branch (header `Remote⇅`)   # - `ci` — CI status of the head commit diff --git a/tests/snapshots/integration__integration_tests__help__help_config_long.snap b/tests/snapshots/integration__integration_tests__help__help_config_long.snap index e4f4ecd437..c5f4f557a3 100644 --- a/tests/snapshots/integration__integration_tests__help__help_config_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_config_long.snap @@ -243,8 +243,8 @@ Valid built-in names: - branch — The branch name - status — Git status symbols, plus any user-defined status - working-diff — Uncommitted line changes against HEAD, including untracked files (header HEAD±) -- ahead-behind — Commits ahead of and behind the default branch (header main↕, or whatever the default branch is called) -- branch-diff — Line changes against the default branch (header main…±, likewise) +- ahead-behind — Commits ahead of and behind the default branch (header main↕) +- branch-diff — Line changes against the default branch (header main…±) - summary — An LLM-generated summary of the branch - upstream — Commits ahead of and behind the upstream tracking branch (header Remote⇅) - ci — CI status of the head commit diff --git a/tests/snapshots/integration__integration_tests__help__help_list_long.snap b/tests/snapshots/integration__integration_tests__help__help_list_long.snap index 4ae81f6d4c..3583fd043e 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_long.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_long.snap @@ -158,7 +158,7 @@ Output as JSON for scripting: Age Time since last commit Message Last commit message (truncated) -The main↕ and main…± headers name the default branch: its own name where it fits the width those columns already need (trunk↕, master…±), and base where it doesn't, so a long name never widens every row. +The main↕ and main…± headers keep the familiar main label in every repository. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs from identity to nice-to-have — and the summary footer names them (hidden: Commit, Age, Message). A column with nothing to show on any row — Remote⇅ in a repo with no remote — ranks below every populated column, so it goes first. A wider terminal brings them back. To pin a set rather than leave it to the width, name the columns in [list] columns; --format=json carries every field at any width. diff --git a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap index f6e1b70c26..db8377f9f9 100644 --- a/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap +++ b/tests/snapshots/integration__integration_tests__help__help_list_narrow_80.snap @@ -173,9 +173,7 @@ Output as JSON for scripting: Age Time since last commit Message Last commit message (truncated) -The main↕ and main…± headers name the default branch: its own name where it fits - the width those columns already need (trunk↕, master…±), and base where it -doesn't, so a long name never widens every row. +The main↕ and main…± headers keep the familiar main label in every repository. The table sizes itself to the terminal. When the columns don't all fit, the least important go first — roughly right to left, since the order above runs diff --git a/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap index 332341ac27..2b33717377 100644 --- a/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_branches_with_nonexistent_default_branch.snap @@ -57,7 +57,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± base↕ base…± Remote⇅ Path Commit Age Message + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message @ main ⚑^| | . 05a4a45 16h Initial commit + feature-a ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ../repo.feature-b f62940f 16h Add feature-b file diff --git a/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap index 61d6b87d9a..97765aa1b5 100644 --- a/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_full_with_nonexistent_default_branch.snap @@ -57,7 +57,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± base↕ base…± Remote⇅ CI Path Commit Age Message + Branch Status HEAD± main↕ main…± Remote⇅ CI Path Commit Age Message @ main ⚑^| | . 05a4a45 16h Initial commit + feature-a ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ../repo.feature-b f62940f 16h Add feature-b file diff --git a/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap b/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap index 451de71e0d..fe429e823d 100644 --- a/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap +++ b/tests/snapshots/integration__integration_tests__list__list_with_nonexistent_default_branch.snap @@ -56,7 +56,7 @@ info: success: true exit_code: 0 ----- stdout ----- - Branch Status HEAD± base↕ base…± Remote⇅ Path Commit Age Message + Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message @ main ⚑^| | . 05a4a45 16h Initial commit + feature-a ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ../repo.feature-b f62940f 16h Add feature-b file diff --git a/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap b/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap index ae89c7db62..3d86d86064 100644 --- a/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap +++ b/tests/snapshots/integration__integration_tests__list_layout__detached_and_prunable_width_120.snap @@ -62,7 +62,7 @@ exit_code: 0 + feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-… + feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-… + 05a4a45 ⊘_ ../repo.feature-detached 05a4a45 16h Initial comm… -+ feature-gone ⊟         ../repo.feature-gone 05a4a45 ++ feature-gone ⊟         ../repo.feature-gone 05a4a45 ----- stderr ----- From 18ac14560d24823f98fd12bcf901ef75b45389e1 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Mon, 7 Sep 2026 14:35:36 -0700 Subject: [PATCH 16/17] Update centered list placeholders --- ...tion__integration_tests__list__list_empty_repo.snap | 2 +- ...n_tests__list__list_shows_warning_on_git_error.snap | 10 +++++----- ...__list_skips_operations_for_prunable_worktrees.snap | 2 +- ...st__list_warns_when_commit_details_batch_fails.snap | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/snapshots/integration__integration_tests__list__list_empty_repo.snap b/tests/snapshots/integration__integration_tests__list__list_empty_repo.snap index b2d503c54f..26021f16c7 100644 --- a/tests/snapshots/integration__integration_tests__list__list_empty_repo.snap +++ b/tests/snapshots/integration__integration_tests__list__list_empty_repo.snap @@ -55,7 +55,7 @@ success: true exit_code: 0 ----- stdout ----- Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main · ·^ · . · · +@ main · ·^ · . · · ----- stderr ----- diff --git a/tests/snapshots/integration__integration_tests__list__list_shows_warning_on_git_error.snap b/tests/snapshots/integration__integration_tests__list__list_shows_warning_on_git_error.snap index efd477e3cf..7d5ff68006 100644 --- a/tests/snapshots/integration__integration_tests__list__list_shows_warning_on_git_error.snap +++ b/tests/snapshots/integration__integration_tests__list__list_shows_warning_on_git_error.snap @@ -57,11 +57,11 @@ success: true exit_code: 0 ----- stdout ----- Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message -@ main ^· · . · · -+ feature · ··· · · · · ../repo.feature · · -+ feature-a ↑· ↑1 +1 · ../repo.feature-a · · -+ feature-b ↑· ↑1 +1 · ../repo.feature-b · · -+ feature-c ↑· ↑1 +1 · ../repo.feature-c · · +@ main ^· · . · · ++ feature · ··· · · · · ../repo.feature · · ++ feature-a ↑· ↑1 +1 · ../repo.feature-a · · ++ feature-b ↑· ↑1 +1 · ../repo.feature-b · · ++ feature-c ↑· ↑1 +1 · ../repo.feature-c · · ----- stderr ----- ▲ Failed to batch-fetch commit details: fatal: bad object 0000000000000000000000000000000000000001 diff --git a/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap b/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap index 06a1be8960..9a6a2aa0f7 100644 --- a/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap +++ b/tests/snapshots/integration__integration_tests__list__list_skips_operations_for_prunable_worktrees.snap @@ -58,7 +58,7 @@ exit_code: 0 ----- stdout ----- Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message @ main ^| | . 05a4a45 16h Initial commit -+ feature ⊟         ../repo.feature 05a4a45 ++ feature ⊟         ../repo.feature 05a4a45 + feature-a ↑ ↑1 +1 ../repo.feature-a 1b87d47 16h Add feature-a file + feature-b ↑ ↑1 +1 ../repo.feature-b f62940f 16h Add feature-b file + feature-c ↑ ↑1 +1 ../repo.feature-c 345c7c9 16h Add feature-c file diff --git a/tests/snapshots/integration__integration_tests__list__list_warns_when_commit_details_batch_fails.snap b/tests/snapshots/integration__integration_tests__list__list_warns_when_commit_details_batch_fails.snap index 9deb0329c8..03c61a7695 100644 --- a/tests/snapshots/integration__integration_tests__list__list_warns_when_commit_details_batch_fails.snap +++ b/tests/snapshots/integration__integration_tests__list__list_warns_when_commit_details_batch_fails.snap @@ -58,7 +58,7 @@ exit_code: 0 ----- stdout ----- Branch Status HEAD± main↕ main…± Remote⇅ Path Commit Age Message @ main ^| | . · · -+ · ·· · · · ../repo.feature · · ++ · ·· · · · ../repo.feature · · + feature-a ↑ ↑1 +1 ../repo.feature-a · · + feature-b ↑ ↑1 +1 ../repo.feature-b · · + feature-c ↑ ↑1 +1 ../repo.feature-c · · From c3616318035cc552687ea2bed9491f0d94525efa Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Mon, 7 Sep 2026 14:55:54 -0700 Subject: [PATCH 17/17] Share list column alignment --- src/commands/list/render.rs | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/src/commands/list/render.rs b/src/commands/list/render.rs index 9640ef9ed8..66050f5288 100644 --- a/src/commands/list/render.rs +++ b/src/commands/list/render.rs @@ -253,26 +253,9 @@ impl LayoutConfig { self.render_line(|column| { let mut cell = StyledLine::new(); if !column.header.is_empty() { - let padding = column.width.saturating_sub(column.header.width()); - // A diff column is two right-aligned halves — `+999` then - // `-999` — so a header at either edge stands over one of them - // and reads as its label: pushed right, `HEAD±` sits over the - // deletions and a lone `+1` lands to its left. Centring - // straddles the separator, which is what the header names. - // Age is a single right-aligned value, so its header goes - // right with it; everything else is text, read from the left. - let leading = match column.alignment() { - CellAlignment::Split => padding / 2, - CellAlignment::Right => padding, - CellAlignment::Left => 0, - }; - if leading > 0 { - cell.push_raw(" ".repeat(leading)); - } - cell.push_styled(column.header.to_string(), style); } - cell + column.aligned_cell(cell) }) } @@ -426,7 +409,12 @@ impl ColumnLayout { } } - /// Render a whole-cell state according to the column's value shape. + /// Place whole-cell content according to the column's value shape. + /// + /// A diff column is two right-aligned halves, so its own value renderer + /// places each half. Content that names or describes the whole field — its + /// header, loading state, or in-sync marker — sits over their separator. + /// Age is a single right-aligned value; everything else reads from the left. fn aligned_cell(&self, content: StyledLine) -> StyledLine { let leading = match self.alignment() { CellAlignment::Left => return content, @@ -601,11 +589,8 @@ impl ColumnLayout { // checking counts directly is simpler than threading the enum through. if active.ahead == 0 && active.behind == 0 { let mut cell = StyledLine::new(); - // Center the symbol in the column width - let padding_left = (self.width.saturating_sub(1)) / 2; - cell.push_raw(" ".repeat(padding_left)); cell.push_styled("|", Style::new().dimmed()); - return cell; + return self.aligned_cell(cell); } self.render_diff_cell(active.ahead, active.behind) }