Merge pull request #12940 from Kilo-Org/improve-multi-project-ui-design

fix(agent-manager): tighten multi-project sidebar layout
This commit is contained in:
Marius
2026-08-06 12:49:34 +02:00
committed by GitHub
37 changed files with 657 additions and 160 deletions
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Fix the Agent Manager sidebar keyboard shortcut badge so it appears on the right edge of local rows and only while hovered or holding the jump modifier, give worktree titles more room by no longer reserving space for hidden row actions, align project names and section headings with the row icons below them, and show which worktree a session belongs to in the search palette.
@@ -0,0 +1,437 @@
# Plan: Agent Manager sidebar — shortcut badge fix + reclaim title width
Worktree: `/Users/marius/Documents/git/kilocode/.kilo/worktrees/mewing-profit`
All paths below are relative to `packages/kilo-vscode/`.
Rules:
- Do exactly these edits. Do not refactor anything else.
- Solid.js, not React: `class=`, not `className=`.
- Do **not** add `kilocode_change` markers. This package is Kilo-owned and CI fails if you do.
- Do **not** add new i18n strings. None are needed.
- Out of scope: the per-project `SESSIONS` list in the tree (tracked in
https://github.com/Kilo-Org/kilocode/issues/12928). Do not touch `UnassignedSessionsSection.tsx`.
Do task A, verify, then task B, verify, then task C.
## Task A — Shortcut badge on the right, hidden until hover or ⌘ held
The `⌘1` badge on "local" rows is always visible, and in multi-project mode it renders on the
*left*, between the icon and the label. Worktree rows already behave correctly. Cause:
`.am-shortcut-badge` has no default `opacity: 0` — worktree badges are only hidden because their
container `.am-wt-hover-actions` is hidden, and local rows never got that container.
### A1 — `webview-ui/agent-manager/ProjectSidebarBody.tsx`
Replace the whole `<button class="am-local-item">` block (lines **301-348**) with this. The badge
`<Show>` moves from before `am-local-text` to the end, and stats + badge get wrapped:
```tsx
<button
class="am-local-item"
classList={{ "am-local-item-active": active() && props.selection === "local" }}
data-sidebar-id={`${props.project.id}:local`}
onClick={() => props.onSelectLocal(props.project.id)}
>
<Show when={!props.localBusy?.()} fallback={<Spinner class="am-worktree-spinner" />}>
<svg class="am-local-icon" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="3.5" width="15" height="10" rx="1" stroke="currentColor" />
<path d="M6 16.5H14" stroke="currentColor" stroke-linecap="square" />
<path d="M10 13.5V16.5" stroke="currentColor" />
</svg>
</Show>
<div class="am-local-text">
<span class="am-local-label">{props.t("agentManager.local")}</span>
<Show when={props.local?.branch}>
<span class="am-local-branch">{props.local!.branch}</span>
</Show>
</div>
<div class="am-wt-actions-cell">
<Show
when={
props.local &&
(props.local.additions || props.local.deletions || props.local.ahead || props.local.behind)
}
>
<div class="am-worktree-stats">
<Show when={props.local!.behind}>
<span class="am-worktree-behind">{props.local!.behind}</span>
</Show>
<Show when={props.local!.ahead}>
<span class="am-worktree-commits">{props.local!.ahead}</span>
</Show>
<Show when={props.local!.additions}>
<span class="am-stat-additions">+{props.local!.additions}</span>
</Show>
<Show when={props.local!.deletions}>
<span class="am-stat-deletions">{props.local!.deletions}</span>
</Show>
</div>
</Show>
<div class="am-wt-hover-actions">
<Show when={props.shortcutMap?.().get(`${props.project.id}:local`)}>
{(shortcut) => (
<span class="am-shortcut-badge">
{isMac ? "⌘" : "Ctrl+"}
{shortcut()}
</span>
)}
</Show>
</div>
</div>
</button>
```
The `` in `am-stat-deletions` is U+2212 MINUS SIGN, not a hyphen. Copy it verbatim.
### A2 — `webview-ui/agent-manager/SidebarBody.tsx`
Same bug in legacy single-project mode, but the badge is already last, so only the wrapper is
missing.
Insert **before** line **127** (`<Show when={props.localStats() === undefined}>`):
```tsx
<div class="am-wt-actions-cell">
```
Replace line **182**:
```tsx
<span class="am-shortcut-badge">{isMac ? "⌘" : "Ctrl+"}1</span>
```
with:
```tsx
<div class="am-wt-hover-actions">
<span class="am-shortcut-badge">{isMac ? "⌘" : "Ctrl+"}1</span>
</div>
</div>
```
Result: one `am-wt-actions-cell` div containing the skeleton `<Show>`, the stats `<Show>`, and the
hover-actions div, closing before `</button>`. Prettier fixes indentation in task D.
### A3 — `webview-ui/agent-manager/agent-manager.css`
Delete lines **569-575** and replace with the rule that actually works:
```css
.am-local-item .am-shortcut-badge {
right: 8px;
}
.am-local-item:hover .am-shortcut-badge {
opacity: 1;
}
```
becomes:
```css
.am-local-item:hover .am-wt-hover-actions {
opacity: 1;
visibility: visible;
}
```
(`right: 8px` never applied — the badge is `position: static`. The `opacity: 1` never did anything
because nothing set `opacity: 0` first.)
### A4 — same file, delete lines **609-611** entirely, add nothing:
```css
.am-show-shortcuts .am-local-item .am-shortcut-badge {
opacity: 1;
}
```
The rule at lines 597-600 is not scoped to worktree items, so after A1/A2 it already covers local
rows.
### A5 — same file, lines **986-989**, add `visibility: hidden` to match worktree behaviour:
```css
.am-local-item:hover .am-worktree-stats,
.am-local-item:hover .am-worktree-stats-skeleton {
opacity: 0;
visibility: hidden;
}
```
### Verify A
```bash
cd packages/kilo-vscode && bun run typecheck && bun run lint
```
## Task B — Stop reserving width for invisible content
`.am-wt-actions-cell` is a grid whose children all stack in cell 1/1, so the cell is permanently as
wide as its widest child. `visibility: hidden` does not remove layout, so the invisible hover
actions (~42px) and the loading skeleton (~48px) reserve width on every row forever. That is why
titles truncate ~40px before the right edge.
All edits in `webview-ui/agent-manager/agent-manager.css`.
### B1 — Take hover actions out of grid flow
Lines **475-482**. Add the four `position` lines at the top, keep everything else:
```css
.am-wt-hover-actions {
position: absolute;
top: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 2px;
opacity: 0;
visibility: hidden;
}
```
Absolutely positioned children never size grid tracks. `.am-wt-actions-cell` is already
`position: relative` (line 464). Do not edit the `.am-wt-actions-cell > *` rule at lines 469-472.
### B2 — Same for the loading skeleton
Add immediately after the rule from B1:
```css
/* Skeleton is a placeholder, not real content — it must not reserve title width. */
.am-wt-actions-cell > .am-worktree-stats-skeleton {
position: absolute;
top: 0;
right: 0;
bottom: 0;
}
```
Do not edit the base `.am-worktree-stats-skeleton` rule (lines 744-748) or `.am-pr-badge-skeleton`.
After B1+B2 the cell is sized only by `.am-worktree-stats` and `.am-worktree-delete-hint`, which
are the only things that should reserve space.
### B3 — Fade the title under the overlaying actions
The actions now overlay the row instead of sitting in reserved space, so a long title would render
behind them. Same fix the codebase already uses for the local branch at lines 577-580. Add after
B2:
```css
.am-worktree-item:hover .am-worktree-branch {
mask-image: linear-gradient(to right, black calc(100% - 48px), transparent 100%);
-webkit-mask-image: linear-gradient(to right, black calc(100% - 48px), transparent 100%);
}
```
### B4 — Remove one layer of nested padding
Lines **365-370**, change `padding: 0 6px;` to `padding: 0;`:
```css
.am-project-body {
display: flex;
flex-direction: column;
min-height: 0;
padding: 0;
}
```
Gains 6px per side on every row in a project. Rows keep their own 10px inset
(`.am-local-item` line 106, `.am-worktree-item` line 425) so they stay indented under the project
header. Do not change those, and leave `.am-project-body .am-section-header` (lines 273-275) alone.
### B5 — Remove the remaining outer list gutters in multi-project mode
The sidebar keeps 8px horizontal padding for the header controls, but the project list should use
the full width up to its scrollbar. In the same CSS file, extend `.am-projects-list` with:
```css
.am-projects-list {
display: flex;
flex-direction: column;
min-height: 0;
overflow-y: auto;
margin-inline: -8px;
}
```
Do not remove padding from `.am-sidebar` itself. This makes project cards and rows reach the
scrollbar without moving the `PROJECTS` header and its controls to the edge.
### Verify B
```bash
cd packages/kilo-vscode && bun run typecheck && bun run lint
```
## Task C — Show where a palette session lives
In the ⌘F search palette, multi-project session results show only the project name, so you cannot
tell whether a session is in a worktree or at the project root.
File: `webview-ui/agent-manager/ProjectList.tsx`. In the session loop (lines **90-106**), add the
two `const` lines and change `meta` and `search`:
```tsx
for (const session of props.sessions[project.id] ?? []) {
const wt = session.worktreeId ? state.worktrees.find((item) => item.id === session.worktreeId) : undefined
const where = wt ? wt.label || wt.branch : props.t("agentManager.local")
items.push({
key: `${project.id}:session:${session.id}`,
projectId: project.id,
kind: "session",
group: "sessions",
title: session.title || props.t("agentManager.session.untitled"),
meta: [project.label, where],
search: [project.label, where, wt?.branch, session.title, session.id].filter(Boolean).join(" "),
updatedAt: session.updatedAt,
state: "idle",
visible: project.expanded,
sessionId: session.id,
location: session.worktreeId ? "worktree" : "local",
worktreeId: session.worktreeId ?? undefined,
})
}
```
`state` is already in scope (line 55) and already null-checked (line 56). `meta` is joined with
` · ` by the renderer (`SidebarSearchMenu.tsx:143`), so no separator work is needed.
## Task E — Align the project heading with the row icons
Every row in the projects tree used a different left inset, which made the sidebar look busy and
indented for no reason. Measured against a full-bleed `.am-projects-list` (starting at x=0):
| Element | Before | After |
|---|---|---|
| `PROJECTS` label | 16px | 10px |
| Project chevron | 12px | 10px |
| `WORKTREES` / `SESSIONS` label | 6px | 10px |
| `.am-local-item` icon | 10px | 10px |
| `.am-worktree-item` icon | 10px | 10px |
Changes in `agent-manager.css`:
- `.am-local-item` padding `8px 10px``8px 6px`
- `.am-worktree-item` padding `6px 10px``6px 6px`
- `.am-project-item` padding `6px 8px 6px 12px``6px 6px`
- `.am-project-body .am-section-header` padding-left `6px` → unchanged at `6px`
- `.am-projects > .am-section-header` gets `padding-left: 2px`, because that heading sits inside
`.am-sidebar`'s own 8px padding rather than in the pulled-out list
The leading columns carry no padding of their own (`.am-sidebar-header-toggle` and
`.am-sidebar-header-chevron` are bare 16px boxes), so row padding is the only lever.
### Symmetric gutter
`.am-projects-list` uses `margin-left: -4px; margin-right: -4px`, pulling out of `.am-sidebar`'s
8px padding to an even 4px gutter. An earlier attempt used `-8px` on the left for true full bleed,
but that is wrong twice over: the resize handle's inner half then covered every card, so card
clicks started a resize, and a selected row's `border-radius: var(--radius-sm)` background clipped
flat against the window edge while still being inset on the right, which read as a bar bleeding off
the sidebar rather than a card.
4px is also the most reclaimable on the right. The handle's hit area is 8px wide centered on the
border, reaching 5px back into the content area (255-263 in a 260px sidebar). At this gutter the
row's hover actions end at 249, leaving 6px of clearance; anything tighter puts row buttons under
the handle and turns clicks into resize drags.
### Two tab stops
Reducing the insets exposed that labels sat at four different offsets: the non-collapsible
`WORKTREES` heading at 6px (it passes no `onToggle`, so `SidebarSectionHeader` renders no chevron),
collapsible headings at 28px, worktree card text at 30px, and local card text at 32px because
`.am-local-icon` was 18px wide while `.am-wt-icon` held a 16px glyph.
Normalized to one 16px leading column with an 8px gap, giving exactly two tab stops:
- `.am-sidebar-header-main` gap `6px``8px`, matching the card icon gap
- new `.am-project-body .am-sidebar-header:not(.am-sidebar-header-toggleable) .am-sidebar-header-main { padding-left: 24px }`
so a heading with no chevron still reserves the column
- `.am-local-icon` `18px``16px`
- `.am-wt-icon` gains `width: 16px` and `justify-content: center`
Measured result at 260px:
| Tab stop | Elements |
|---|---|
| 10px | `PROJECTS` label, project chevron, local icon, worktree icon, `SESSIONS` chevron |
| 34px | project name, `WORKTREES` label, `SESSIONS` label, local text, worktree text |
```
cardLeftGap=4 | cardRightGap=4 | actionsRight=249 | handleZoneFrom=255
```
`PROJECTS` intentionally stays at the 10px glyph stop rather than being pushed to 34px: it is the
root heading, so indenting it further than the projects beneath it would invert the hierarchy.
### Rejected: a per-project icon in the heading
Superset shows a GitHub **owner** avatar per project (`https://github.com/{owner}.png?size=64`,
falling back to the project's initial). Our webview CSP already allows `https:` images, so it was
feasible, but it does not fit this repo set: of the local projects, ~17 are `Kilo-Org` remotes and
would share one identical Kilo logo, and ~20 have no git remote at all and would show nothing.
An owner avatar answers "who owns this", which is not the question the sidebar needs answered.
Superset's repo-file scanner (`favicon-discovery.ts`) is dead code there, so it was not an option
worth copying either. Left out entirely as out of scope.
## Task D — Final checks
From `packages/kilo-vscode`:
```bash
bun run format
bun run typecheck
bun run lint
bun run test:unit
bun run check-kilocode-change
bun run compile
```
Then create `.changeset/agent-manager-sidebar-density.md`:
```md
---
"kilo-code": patch
---
Fix the Agent Manager sidebar keyboard shortcut badge so it appears on the right edge of local rows and only while hovered or holding the jump modifier, give worktree titles more room by no longer reserving space for hidden row actions, and show which worktree a session belongs to in the search palette.
```
## Visual regression baselines
Task A and B change existing snapshots: `WorktreeItemDefault`, `WorktreeItemActive`,
`WorktreeItemPendingDelete`, `WorktreeItemStale`, `WorktreeItemWithStats`, `WorktreeItemGrouped`,
`SidebarSearchOpen`, `MultiProjectSidebar` in `webview-ui/src/stories/agent-manager.stories.tsx`.
**Do not run or update visual regression tests.** `tests/visual-regression.spec.ts` is skipped on
macOS, so you cannot produce valid Linux baselines locally. CI regenerates them and may push a
baseline commit. If a push is then rejected, do **not** `git pull --rebase`; run
`git fetch && git push --force-with-lease`.
## Manual test
Enable `kilo-code.new.experimental.multiProject` in Kilo Settings → Experimental, add a second
project, open Agent Manager (`Cmd+Shift+M`).
1. Nothing hovered: no `⌘N` badge visible anywhere.
2. Hover a `local` row: badge appears at the **right** edge, git stats fade out.
3. Hold ⌘: badges appear on all local and worktree rows, all right-aligned. Release: all gone.
4. Worktree rows with no git changes show noticeably longer titles than before. Hover one with a
long title: it fades under the badge and trash button instead of colliding.
5. ⌘F: session results read `<project> · <worktree or local>`.
6. Turn the experimental setting off: the `local` row's `⌘1` badge is hidden until hover.
## Known issue, not fixed here
`⌘1``⌘9` index the full nav order, which includes session rows that render no badge
(`navigate.ts:214-218` + `section-helpers.ts:147-153`). So with 4 worktrees you see `⌘1``⌘5` and
`⌘6``⌘9` silently land on unlabelled session rows. Deliberately left alone: fixing it means
changing jump semantics and rewriting `tests/unit/navigate.test.ts:745-757`, and it becomes moot
once #12928 moves sessions out of the tree.
@@ -5,7 +5,7 @@ description: "Autonomous AI agent orchestration for your codebase"
# {% $markdoc.frontmatter.title %}
Gastown by Kilo is an autonomous agent orchestration platform that manages teams of AI agents working on your codebase. Built on [Gastown](https://gastown.dev) — the open protocol for agent orchestration — Kilo's implementation coordinates coding agents, a code review agent, and a conversational coordinator to ship features, fix bugs, and maintain your projects with minimal human intervention.
Gastown by Kilo is an autonomous agent orchestration platform that manages teams of AI agents working on your codebase. Built on [Gastown](https://github.com/gastownhall/gastown) — the open protocol for agent orchestration — Kilo's implementation coordinates coding agents, a code review agent, and a conversational coordinator to ship features, fix bugs, and maintain your projects with minimal human intervention.
You describe the work. Agents figure out how to do it, write the code, review each other's output, and land clean PRs — while you stay in control of what ships.
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e9241e89fae82f69f1fab0371423ab45c905b54ff0c60ec1408b54d60f6c4c6c
size 19636
oid sha256:785cb43d5208977a69f03056bf16e0bbc32b29aeba1d14257848a32d19590bd7
size 19702
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a96dd287b6ffdbdc0360baa9af99d09c3b709a5adaef18f5e7b7e43a83e44746
size 3134
oid sha256:950cf0a3053a283651b2232b4a4e2cfdced3c6feb2f053b0a5976f8d169cfcf1
size 3193
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:60daae227133cedd35f93753128453177400f83233268ae19af984bc9a72ad4b
size 5203
oid sha256:c02aed8f52a7b22d14d2068e1f87b65bb42ed6737115dadf89615ff5a4c870e5
size 5214
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4cda12827fa0de0590119ab707f6403fc98291843701ab0d4300e493b4b284aa
size 14965
oid sha256:faae43fcbe8c42aef80184b4a815bbe6a024e0345243b5b8eb1be04d62b8611f
size 15184
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8099695f3cb2f79a54f6ada28eaf30a4e31afc5e660e293853d1ff34a2afaed1
size 1605
oid sha256:82ee97e40bd03a7c126e154c1644d878e374f3a573d81b22daf602a8e74ed840
size 1596
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0bee9b58fb35c2484a9da4bffcae07f98a942c1097df33b549e13de781790824
size 8026
oid sha256:4d7fa00cb51b6ab591a6f25f6f863d17b78cfcd8166cf09d179ab485e394a084
size 8149
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bf6056375e5bf9d9ad78487d58a7fc1fb2d570333d057d412f1d70893a73e5df
size 6176
oid sha256:1d841a283f99962a7aa3e0ecc1b24aba5a5351534e74ebe3a6794f79e6d9f374
size 6204
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ebd138c8d5e59bc09ce0d8d571d5598622fb61631cf5a698c80380872bef0a4
size 7031
oid sha256:adb4fa97f6e2dfbf1d5dd7aa58181d548c99cc0ca170f8618af72d3e6f252974
size 7008
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b629ad25ef4f3da8bd8a9e59c64cc05279dfc262db2d8d9d25c3697f48ef8393
size 12834
oid sha256:8caa6d6b2d7e1b0bbab8e47a486e27bcbfd7716ef84ad8771980a658c8cc41ab
size 13002
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b78544f9c306dbcb0278502b1f929f0af5389a90b73362abbea6f76612c5def9
size 7761
oid sha256:089e30f789f0f592e0ee3794dfe736fc503290d05e300830fa720a5b75046894
size 7768
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4785426a34c06b0c74fe62c117528bb16ea579b2d677902c839336f6a29a380e
size 5926
oid sha256:3792bcfd82e37fd2ae9664f0abfbd50a6823be2da55b96640557d3696b490ab9
size 6018
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e2b861de27fb8e6d64becd50bb4b3b05d748fba74b98aafaf5cb2a182ff74655
size 26259
oid sha256:f65d353dac1fd4bf4ddaee9f7171e348b19bb279cb220b7f745651c3d65ad821
size 26237
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:afa789c231b370c2ba874029a8a0597b5ca7ffff5624ddd343fcd54139a94012
size 6132
oid sha256:4a133a9d0b13ba7effcd1dab38a1843526b7aac99a9be5df9647d1ba7fb8d858
size 6140
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a0f11ac60e7dd3e393739e85d279bca7e53dc22af01bade22a741e5267d96300
size 11053
oid sha256:aad33864d63a6d5eb7868daaa7989e3271806ddd385b4847a5a4221d189c4d5c
size 11063
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:94227c822d88aa83f286136c05d92c8209b2cb08eaf6870cf79172aa004e8402
size 39173
oid sha256:d1c6647a2cb4da318b9ec60761c248345c2a6fdc6e95a0dc5e03c31a939ace4c
size 38950
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:facae793453671cf34b950352dc19041d420d096fac2a7358d1a4550e05a8033
size 3383
oid sha256:f2349f8c0d7049811702e5b8cbeb5fc2f402bdda8630d1a5210a970a13284197
size 3694
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fc81d91792781a9784775db3af44ea8058e87df9e029c2d7fc5acf1548fa7ec7
size 3423
oid sha256:311bb5062867449b9d5e2de41e77b399be1953fa6663097fa730218bed64a30a
size 3753
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2923838fbc9ce24851e2b8ed6c5d4c2a098db9667fc528d1c00162595640bc05
size 3550
oid sha256:52cd6565a96bee5f55872fad4faf1c88030bec70d223187e51942bb70c2da253
size 3687
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c087f8e2b50bf43f9ca03ab31e8955e995304473688bf8db88fb6901d743a1d4
size 3541
oid sha256:f2349f8c0d7049811702e5b8cbeb5fc2f402bdda8630d1a5210a970a13284197
size 3694
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2923838fbc9ce24851e2b8ed6c5d4c2a098db9667fc528d1c00162595640bc05
size 3550
oid sha256:4e29131b2a6e68fc26d032396c872698d1882eacc3830fe4059fca810981915c
size 3679
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6c13cc44cec6092d8294a535a3e6057d6e3a2b2d59cb18e09b0a073576972f80
size 3582
oid sha256:62b2731184acdc022a75338779296b8d217d5abe565d8d763075bfc68939b2e8
size 3695
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79f5495b171fc9de3d04b7e15cc30ac2f5082c6d7db3818b3e288ce9c3ba41f3
size 3461
oid sha256:b36b2ab3912ec70139285c19a226e02c35cb982a321e3cd140d84a29756d817e
size 3590
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dda50cfb4ebebd29e6f0791ec13ee9e16e48c9fa6f6d6be03c78b1d900516427
size 3604
oid sha256:1b787fe5310a002e7c5ac2bf8101964c0e4576d16db973e77a5e893b0db1fb66
size 3728
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2680e8f97baee13ab92ac6fb3bcce56eb9cbb7e0c5fb0f76a76251abc016eac3
size 3567
oid sha256:e2bcc9c6e0dbf398194431bdbca27138608304e95c6de5874aeff2ca06fe73e6
size 3684
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2680e8f97baee13ab92ac6fb3bcce56eb9cbb7e0c5fb0f76a76251abc016eac3
size 3567
oid sha256:e2bcc9c6e0dbf398194431bdbca27138608304e95c6de5874aeff2ca06fe73e6
size 3684
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:270e0dafc2601996e7d048873e55db4680e8803d6800dc44b1fa6ce6239827cc
size 2017
oid sha256:769935237790bbf85293a260a4dded22ad0f33dcffb41cbcd0d7695136775bd2
size 2215
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5feb854ee2693b655ab504b8e2ed8794af77a357c3d716e2deb9668a08f3aa0c
size 1847
oid sha256:6905b74776e20e3bdf0ca2ba6a89888e9160f5055d2f1719bc0cafa59f808134
size 2056
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1d9935cc07b9eefe0c1eca1852b00ac2b39b76cdf12ee2f01c52e4ac0771b39e
size 2910
oid sha256:8cb40c61ea8472804f6a067d64cec84a6c492756abf386acded8e2df57868653
size 3012
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:54e7142d591391c6e99631c9bf87b011321e52f17a8ee39e5aeeaa9b1350f2f9
size 1831
oid sha256:3ed27d7e73679fe2ccbe267cf6ca52143882ae72026d01beb8862e6add661531
size 2207
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7e4a37b5b7d71e0eb7f043176f410030385a8a61f3913bd6419b1d96c6cad2de
size 2422
oid sha256:71034449e848b4d6f2fe88624694842cfddd8a1890996f6cbc66499cbefafb33
size 2547
@@ -22,6 +22,11 @@ import { ProjectBranchDialog } from "./ProjectBranchDialog"
import type { ProjectStore } from "./project/store"
import type { ModeRouter } from "./mode-router"
const place = (state: AgentManagerStateMessage, session: ProjectSessionInfo, local: string) => {
const wt = state.worktrees.find((item) => item.id === session.worktreeId)
return wt?.label || wt?.branch || local
}
interface Props {
projects: AgentProjectSnapshot[]
states: Record<string, AgentManagerStateMessage>
@@ -88,14 +93,16 @@ export const ProjectList: Component<Props> = (props) => {
})
}
for (const session of props.sessions[project.id] ?? []) {
const wt = state.worktrees.find((item) => item.id === session.worktreeId)
const where = place(state, session, props.t("agentManager.local"))
items.push({
key: `${project.id}:session:${session.id}`,
projectId: project.id,
kind: "session",
group: "sessions",
title: session.title || props.t("agentManager.session.untitled"),
meta: [project.label],
search: [project.label, session.title, session.id].filter(Boolean).join(" "),
meta: [project.label, where],
search: [project.label, where, wt?.branch, session.title, session.id].filter(Boolean).join(" "),
updatedAt: session.updatedAt,
state: "idle",
visible: project.expanded,
@@ -311,40 +311,45 @@ export const ProjectSidebarBody: Component<Props> = (props) => {
<path d="M10 13.5V16.5" stroke="currentColor" />
</svg>
</Show>
<Show when={props.shortcutMap?.().get(`${props.project.id}:local`)}>
{(shortcut) => (
<span class="am-shortcut-badge">
{isMac ? "⌘" : "Ctrl+"}
{shortcut()}
</span>
)}
</Show>
<div class="am-local-text">
<span class="am-local-label">{props.t("agentManager.local")}</span>
<Show when={props.local?.branch}>
<span class="am-local-branch">{props.local!.branch}</span>
</Show>
</div>
<Show
when={
props.local && (props.local.additions || props.local.deletions || props.local.ahead || props.local.behind)
}
>
<div class="am-worktree-stats">
<Show when={props.local!.behind}>
<span class="am-worktree-behind">{props.local!.behind}</span>
</Show>
<Show when={props.local!.ahead}>
<span class="am-worktree-commits">{props.local!.ahead}</span>
</Show>
<Show when={props.local!.additions}>
<span class="am-stat-additions">+{props.local!.additions}</span>
</Show>
<Show when={props.local!.deletions}>
<span class="am-stat-deletions">{props.local!.deletions}</span>
<div class="am-wt-actions-cell">
<Show
when={
props.local &&
(props.local.additions || props.local.deletions || props.local.ahead || props.local.behind)
}
>
<div class="am-worktree-stats">
<Show when={props.local!.behind}>
<span class="am-worktree-behind">{props.local!.behind}</span>
</Show>
<Show when={props.local!.ahead}>
<span class="am-worktree-commits">{props.local!.ahead}</span>
</Show>
<Show when={props.local!.additions}>
<span class="am-stat-additions">+{props.local!.additions}</span>
</Show>
<Show when={props.local!.deletions}>
<span class="am-stat-deletions">{props.local!.deletions}</span>
</Show>
</div>
</Show>
<div class="am-wt-hover-actions">
<Show when={props.shortcutMap?.().get(`${props.project.id}:local`)}>
{(shortcut) => (
<span class="am-shortcut-badge">
{isMac ? "⌘" : "Ctrl+"}
{shortcut()}
</span>
)}
</Show>
</div>
</Show>
</div>
</button>
<div class="am-section">
@@ -124,62 +124,66 @@ export const SidebarBody: Component<SidebarBodyProps> = (props) => {
<span class="am-local-branch">{props.repoBranch()}</span>
</Show>
</div>
<Show when={props.localStats() === undefined}>
<div class="am-worktree-stats-skeleton">
<div class="am-worktree-stats-skeleton-row" />
<div class="am-worktree-stats-skeleton-row" style={{ width: "70%" }} />
<div class="am-wt-actions-cell">
<Show when={props.localStats() === undefined}>
<div class="am-worktree-stats-skeleton">
<div class="am-worktree-stats-skeleton-row" />
<div class="am-worktree-stats-skeleton-row" style={{ width: "70%" }} />
</div>
</Show>
<Show
when={
props.localStats() &&
(props.localStats()!.files > 0 ||
props.localStats()!.additions > 0 ||
props.localStats()!.deletions > 0 ||
props.localStats()!.ahead > 0 ||
props.localStats()!.behind > 0)
}
>
<div class="am-worktree-stats">
<Show
when={props.localStats()!.additions > 0 || props.localStats()!.deletions > 0}
fallback={
<Show when={props.localStats()!.files > 0}>
<span class="am-stat-files">{props.localStats()!.files}f</span>
</Show>
}
>
<div class="am-worktree-stats-row">
<Show when={props.localStats()!.additions > 0}>
<span class="am-stat-additions">+{props.localStats()!.additions}</span>
</Show>
<Show when={props.localStats()!.deletions > 0}>
<span class="am-stat-deletions">
{""}
{props.localStats()!.deletions}
</span>
</Show>
</div>
</Show>
<Show when={props.localStats()!.ahead > 0 || props.localStats()!.behind > 0}>
<div class="am-worktree-stats-row">
<Show when={props.localStats()!.ahead > 0}>
<span class="am-worktree-commits">
{"↑"}
{props.localStats()!.ahead}
</span>
</Show>
<Show when={props.localStats()!.behind > 0}>
<span class="am-worktree-behind">
{"↓"}
{props.localStats()!.behind}
</span>
</Show>
</div>
</Show>
</div>
</Show>
<div class="am-wt-hover-actions">
<span class="am-shortcut-badge">{isMac ? "⌘" : "Ctrl+"}1</span>
</div>
</Show>
<Show
when={
props.localStats() &&
(props.localStats()!.files > 0 ||
props.localStats()!.additions > 0 ||
props.localStats()!.deletions > 0 ||
props.localStats()!.ahead > 0 ||
props.localStats()!.behind > 0)
}
>
<div class="am-worktree-stats">
<Show
when={props.localStats()!.additions > 0 || props.localStats()!.deletions > 0}
fallback={
<Show when={props.localStats()!.files > 0}>
<span class="am-stat-files">{props.localStats()!.files}f</span>
</Show>
}
>
<div class="am-worktree-stats-row">
<Show when={props.localStats()!.additions > 0}>
<span class="am-stat-additions">+{props.localStats()!.additions}</span>
</Show>
<Show when={props.localStats()!.deletions > 0}>
<span class="am-stat-deletions">
{""}
{props.localStats()!.deletions}
</span>
</Show>
</div>
</Show>
<Show when={props.localStats()!.ahead > 0 || props.localStats()!.behind > 0}>
<div class="am-worktree-stats-row">
<Show when={props.localStats()!.ahead > 0}>
<span class="am-worktree-commits">
{"↑"}
{props.localStats()!.ahead}
</span>
</Show>
<Show when={props.localStats()!.behind > 0}>
<span class="am-worktree-behind">
{"↓"}
{props.localStats()!.behind}
</span>
</Show>
</div>
</Show>
</div>
</Show>
<span class="am-shortcut-badge">{isMac ? "⌘" : "Ctrl+"}1</span>
</div>
</button>
{/* WORKTREES section */}
@@ -103,7 +103,7 @@ html[data-theme="kilo-vscode"]
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
padding: 8px 6px;
min-height: 40px;
box-sizing: border-box;
border-radius: var(--radius-sm);
@@ -144,8 +144,9 @@ html[data-theme="kilo-vscode"]
}
.am-local-icon {
width: 18px;
height: 18px;
/* 16px to match .am-wt-icon and the heading chevron column. */
width: 16px;
height: 16px;
flex-shrink: 0;
color: var(--text-weak);
}
@@ -198,11 +199,19 @@ html[data-theme="kilo-vscode"]
.am-sidebar-header-main {
display: flex;
align-items: center;
gap: 6px;
/* Matches the card icon gap so heading labels share the cards' tab stop. */
gap: 8px;
flex: 1;
min-width: 0;
}
/* Headings without a toggle still reserve the 16px chevron column plus its gap,
so a non-collapsible heading like WORKTREES lands on the same tab stop as
collapsible ones like SESSIONS instead of sitting flush left. */
.am-project-body .am-sidebar-header:not(.am-sidebar-header-toggleable) .am-sidebar-header-main {
padding-left: 24px;
}
.am-sidebar-header-chevron {
display: flex;
align-items: center;
@@ -274,6 +283,12 @@ html[data-theme="kilo-vscode"]
padding-left: 6px;
}
/* This heading sits outside the projects list, so it needs its own inset to
land on the same glyph line as the rows inside the list. */
.am-projects > .am-section-header {
padding-left: 2px;
}
.am-section-label {
font-size: var(--font-size-small);
font-weight: 600;
@@ -301,6 +316,12 @@ html[data-theme="kilo-vscode"]
flex-direction: column;
min-height: 0;
overflow-y: auto;
/* Pull out of .am-sidebar's 8px padding to an even 4px gutter on both sides,
so a selected row's rounded background is inset symmetrically. 4px on the
right is also the most we can reclaim before the resize handle's hit area
(8px wide, centered on the border) would cover the row's own controls. */
margin-left: -4px;
margin-right: -4px;
}
.am-projects-tools,
@@ -324,7 +345,9 @@ html[data-theme="kilo-vscode"]
.am-project-item {
min-height: 34px;
padding: 6px 8px 6px 12px;
/* Leading column aligns with the card icons below it (.am-local-item and
.am-worktree-item both use a 6px inset), so the tree reads as one line. */
padding: 6px 6px;
box-sizing: border-box;
}
@@ -366,7 +389,7 @@ html[data-theme="kilo-vscode"]
display: flex;
flex-direction: column;
min-height: 0;
padding: 0 6px;
padding: 0;
}
.am-project-body > .am-local-item {
@@ -422,7 +445,7 @@ html[data-theme="kilo-vscode"]
display: flex;
align-items: flex-start;
gap: 8px;
padding: 6px 10px;
padding: 6px 6px;
min-height: 36px;
box-sizing: border-box;
border-radius: var(--radius-sm);
@@ -439,6 +462,8 @@ html[data-theme="kilo-vscode"]
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 20px;
}
@@ -474,6 +499,10 @@ html[data-theme="kilo-vscode"]
/* Hover actions (shortcut badge + close button) — hidden by default, shown on hover */
.am-wt-hover-actions {
position: absolute;
top: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: flex-end;
@@ -492,6 +521,20 @@ html[data-theme="kilo-vscode"]
visibility: hidden;
}
/* Placeholder content must not reserve title width. */
.am-wt-actions-cell > .am-worktree-stats-skeleton {
position: absolute;
top: 0;
right: 0;
bottom: 0;
}
.am-worktree-item:hover .am-worktree-branch,
.am-show-shortcuts .am-worktree-item:has(.am-shortcut-badge) .am-worktree-branch {
mask-image: linear-gradient(to right, black calc(100% - 48px), transparent 100%);
-webkit-mask-image: linear-gradient(to right, black calc(100% - 48px), transparent 100%);
}
/* Row 2: always present for consistent height; PR badge right-aligned */
.am-wt-row2 {
display: flex;
@@ -566,15 +609,13 @@ html[data-theme="kilo-vscode"]
white-space: nowrap;
}
.am-local-item .am-shortcut-badge {
right: 8px;
}
.am-local-item:hover .am-shortcut-badge {
.am-local-item:hover .am-wt-hover-actions {
opacity: 1;
visibility: visible;
}
.am-local-item:hover .am-local-branch {
.am-local-item:hover .am-local-branch,
.am-show-shortcuts .am-local-item:has(.am-shortcut-badge) .am-local-branch {
mask-image: linear-gradient(to right, black calc(100% - 40px), transparent 100%);
-webkit-mask-image: linear-gradient(to right, black calc(100% - 40px), transparent 100%);
}
@@ -606,9 +647,6 @@ html[data-theme="kilo-vscode"]
opacity: 0;
visibility: hidden;
}
.am-show-shortcuts .am-local-item .am-shortcut-badge {
opacity: 1;
}
.am-worktree-item:has(.am-worktree-rename-input) .am-wt-row2 {
display: none;
@@ -986,6 +1024,7 @@ html[data-theme="kilo-vscode"]
.am-local-item:hover .am-worktree-stats,
.am-local-item:hover .am-worktree-stats-skeleton {
opacity: 0;
visibility: hidden;
}
/* User-defined sections — collapsible, color-coded groups */