Emit a move error for every failure path and roll back a worktree left by a
failed move, so the Agent Manager row cannot stay stuck on its last stage.
Fail patch capture loudly through the platform command line instead of
treating a git failure as a clean tree.
Keep the worktree session editor identity as the path alone and hand the
forked session over through a one-shot store, so a move no longer opens a
rival tab for the same worktree.
Stop clicks and drags in the empty space under a short list from acting on
the last row, carry the dragged row identity through the placeholder, let a
failed branch status surface instead of posing as healthy git, and require a
persisted session before offering Move to Worktree.
Also make three worktree RPC tests void-returning: JUnit silently skips
@Test methods that return a value, so they never ran.
New Worktree and Move to Worktree were offered mid-turn, where neither is a
sensible thing to invoke: the move aborts the session and forks it, and both
compete with a running agent. The dock's action row now withdraws while the
session is active (any busy state, including a pending permission or question),
matching the VS Code sidebar dock. A move in progress keeps the row so its
spinner and stage stay visible, and the PR row is unaffected because it is
informational rather than an action.
The dock sits outside the scroll pane, so releasing its row resizes the
transcript viewport twice per turn instead of once per session. SessionScroll
already re-pins from its viewport listener, but followTail() routed the
not-following case through followBottom(false), which bumped the generation
counter and aborted in-flight multi-pass chains on the first streamed content
growth. It now re-pins only when already following and otherwise just refreshes
the jump button, which is what onViewport wanted all along.
The connection-panel layout tests compared an absolute connection.y across a
state change, encoding an assumption that the bottom container never changes
height. They now assert the actual invariant: the banner stays one content gap
above that container.
Add a branch/PR dock above the chat prompt in the JetBrains sidebar. It shows
the current branch's PR (state badge + title + changes) when one exists, and
otherwise a centered row of New Worktree / Move to Worktree actions plus a
changes badge, matching the VS Code sidebar dock.
New Worktree and Move to Worktree are registered platform actions
(Kilo.Chat.NewWorktree, Kilo.Chat.MoveToWorktree) rendered through an
ActionToolbar; each is invisible when not enabled, so the dock collapses when
there is nothing to show. New Worktree opens the Agent Manager New Worktree
dialog and switches to that tab after the dialog closes. Move to Worktree
captures uncommitted changes, creates a worktree at the source HEAD, transfers
the changes, and forks the session into it, streaming progress into the dock.
Branch/PR status uses the session's resolved directory (not the synthetic
split-mode project path) so the PR always matches the checked-out branch.
Keep ActiveList titles bold, but keep descriptions muted even under selection so the secondary line does not collapse into the title foreground.
Recolor the resting worktree glyphs to the New UI secondary icon palette, which matches Label.infoForeground/context help text, and paint the running spinner in neutral icon grey instead of green so it stays bright without becoming a colored status light.
ActiveList titles were rendered bold for every row, which made Agent Manager, history, and settings lists read heavier than standard IDE lists and trees. Use the platform plain text style for the primary row title while keeping the explicit foreground so selected and deleting rows retain their color behavior.
Keep the PR state badge first, but render the pull request number after the PR title so the header reads like the worktree list and avoids leading with metadata.
Use the Kilo running spinner anywhere the session UI showed the default platform spinner, so progress indicators match the Agent Manager running state.
Restore branch and lock glyphs as the resting Agent Manager worktree icons, add a JetBrains-styled local monitor icon for the current checkout, and remove the now-unused idle dot icon.
The workflows page landed on main while selectionIndex() was being removed here, so
the merge left it calling a helper that no longer exists. Delete moves the highlight
to the row that took the slot, which is what ActiveListSelection.Slide does.
The platform resolves every svg-backed icon to a 1x1 placeholder when
GraphicsEnvironment.isHeadless(), which is how CI runs but not how the tests run
locally on macOS. The test compared those placeholder widths against the
custom-painted activity icons, so it passed locally and failed CI with
expected:<1> but was:<16>.
Turn real loading on for the assertions with IconLoader.activate(), the platform's
TestOnly hook for exactly this, and restore the ambient state afterwards. Tighten
the load assertions to > 1 so the placeholder can no longer satisfy them.
Verified by forcing -Djava.awt.headless=true on the frontend test JVM: the test
fails before this change and the whole frontend suite passes after it.
Four call sites still rebuilt their list with ActiveListSelection.Index(selectedIndex()),
a hand-rolled Slide that pinned the old slot even when the selected row survived at a
new index — McpSettingsUi.edit could therefore land on a different server whenever the
reload reordered rows. Replace them with Slide, or Key where the mutated row is known,
and delete the helper and the Index policy behind them.
Scrolling was a policy case too (PreserveNoScroll), which meant a plain refresh dragged
the viewport back to the selection on every status poll. Scroll from sync() only when
the refresh actually moved the selection, which collapses Preserve/PreserveNoScroll into
one policy and leaves the viewport alone while rows are merely re-found.
Also fold the remaining duplicates into the shared path: WorktreeSessionEditorPanel names
the shown session with Key instead of update-then-select, HistoryPanel drops its private
restore wrapper and its copy of move() (which stepped over unfiltered rows), and move()
now treats an empty selection as starting past the near end.
rowImage translated the graphics by the body's offset inside the cell before
calling wrap.paint(). paint() already maps the graphics origin to the component's
own top-left, so the offset was applied twice. On rows that do not open a section
the offset is zero and nothing looked wrong, but on a row that carries the section
header band the copy was shifted up by the band height, leaving the dragged row
sliced off at the top and the rest of the image blank.
A row that opens a section paints the section header band inside its own cell, so
capturing the drag placeholder height from getCellBounds folded that band in. The
gap then rendered a band taller than the row, and because the equal-height pass
measures the gap like any other row, every row stretched to that inflated height.
Measure the row body instead, which is also what the floating drag image paints.
ActiveList rebuilt its model with CollectionListModel.replaceAll, which fires an
intervalRemoved with an empty model. The old sync() then fell back to selecting
row 0, and that spurious selection fired onSelect, clobbering the caller's
remembered row. Dropping a dragged worktree and switching away from the Agents
tab both hit this path, so the selection jumped to the first row.
Give ActiveList one capture -> refresh -> restore path: rows expose a stable
identity, the view remembers the selected identities as a sticky anchor written
only by user intent, and restores them after every rebuild with an explicit
absent-row policy. This removes the four hand-rolled restore blocks in
AgentManagerPanel, HistoryPanel, and SettingsInlineListPanel, along with the
AgentManagerPanel.selected mirror and the settings syncing guard.
Route ChangesCardView.Header's open-in-diff button through the shared HeaderOpenAction, matching the edit/patch and task cards, so the toolbarButton + hoverPlaceholder wiring lives in one place. Add the new sub-agent bundle keys (openSubagent, subagent.title, subagent.path) to all locale files.