Add Agent Island for Active Agents - #756
Merged
Merged
Conversation
Hovering the compact island pauses the carousel timer. When the last entry is removed the compact view unmounts, so SwiftUI never delivers the trailing hover-exit and `isIslandHovered` stayed true. Any later pair of Working agents then saw the stale flag and never rotated. Clear the hover flag alongside the roster-expanded flag whenever the entries become empty, and add a TestClock regression for hover -> remove last entry -> two Working entries -> tick at four seconds. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The island commits were formatted by a bare `swift-format` run, which does not discover `.swift-format.json` and falls back to the default 100-column limit. Because the project config sets `respectsExistingLineBreaks`, lint accepted the narrower wrapping, so `AppFeature.swift` carried 78 lines of pure reflow next to 12 lines of real change. Restore every hunk whose only difference is whitespace so the hot files diff against main by their functional changes alone. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The picker tagged rows with the full `.display(id:name:)` value, but `name` comes from `NSScreen.localizedName`, which changes with the system language or a display rename. A stored preference then matched no tag, so the picker showed an empty selection and logged an invalid selection warning even though placement still resolved by id. Introduce `AgentIslandDisplaySelection`, an id-only picker identity, and map it back to the stored preference using the connected catalog's current name, keeping the last-known name for a disconnected display. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
`docs-ai/066-agent-island/` had grown into a working-note archive: nine amendment files, four of them marked superseded by the next, recording design directions that were tried and dropped inside a single PR. Fold them into the plan (rejected directions under Alternatives & decisions) and the action log (one timeline row per wave), and rewrite the user manual page to describe current behavior without the iteration history. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The notched compact bar was `max(40, cutout + 8)` points tall, so on a 14-inch display with a 32pt inset it hung 8pt below the menu bar into the application area. Use the measured cutout height directly, which already tracks the display's scaling mode, keeping only a 28pt floor so the 27pt icon cluster never clips on a short inset. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
With the compact bar now only as tall as the cutout, the 17pt bottom corners read rounder than the notch itself and the 21pt icons sat 2.5pt from the edge. Use a 12pt radius and, in the notched layout only, 20pt icons in a 24pt cluster; the floating pill keeps 21pt. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The panel is resized to fit after SwiftUI lays out new content, and the hosting view centers content vertically, so for one pass a taller or shorter island sat offset from the top edge. The icon cluster keyed its swap animation on the full entry values, which change on every refresh, so that offset was interpolated with the spring: the cluster appeared to vanish and drop back in from above whenever an attention cell came or went. Pin the root to the top so height changes never move the bar, and key the cluster animation on membership and order only. The floating pill also wrapped the cluster in the carousel's per-entry identity, so each rotation removed and re-inserted the icons through the text transition. Scope the identity to the text alone. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
Three parallel cases (islandEntryTapped, islandHandOffTapped, islandRunWorkflowTapped) plus a context-menu router all expressed the same rule: collapse the roster, surface Prowl, then run the sidebar action. Replace them with `island(Action)`. The reducer forwards the wrapped action and collapses only when it presents Prowl UI (`surfacesProwl`); AppFeature surfaces the window in one case instead of four, and Open Prowl now calls the lifecycle client synchronously like its siblings. The island navigation tests move out of the quit test file into their own. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The controller created its panel, hosting view, and screen observers at launch for every user, including the default-off majority. Observe the enabled flag on the store and start or stop the panel as it changes; `refreshLifecycle()` is the synchronous core the observer and the test share. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
AgentIslandDisplayCatalog observes screen-parameter notifications and owns the live NSScreen mapping, so it belongs with the controller in BusinessLogic rather than beside the pure geometry in Models. Drop the unused AgentIslandNotchLayout.rootWidth. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The panel is sized to its content, so every shadow was clipped at the window edge. Remove them from the compact bar, the attention collection, and the roster. The roster also used a fixed 420pt while the notched bar is 425pt on a 14-inch display, leaving a 2.5pt step on each side; it now takes the root width. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The leading wing showed "N Agents" or one carousel name, neither of which says what needs attention. Replace it with counts per state in attention order (blocked, done, working, idle), each as a state-colored SF Symbol plus a number, omitting empty states so the 108pt wing only spends width on what exists. The symbols follow the Shelf spine markers with an added Idle glyph. The floating pill keeps its name carousel. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The floating pill now shows the same per-state counts as the notched wing, one size up, so both placements read the same way. That leaves the Working-name carousel with no consumer: remove its reducer state (current entry, hover flag), the four-second clock effect, the hover action, and the derived Working projections, along with their tests. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The sidebar shows one or the other because it is narrow; the island roster has room for both, so its subtitle is now "pane title · branch", skipping the repeat when they match. A live Workflow badge still takes the whole line, as in the sidebar. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The controller's default argument evaluated AgentIslandDisplayCatalog .shared at construction, so every launch enumerated displays and installed a screen-parameter observer even with the island off. Keep the injection point for tests but resolve the shared catalog lazily. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The roster dimmed rows against the reducer's focusedSurfaceID, which is a keyboard-navigation anchor fed by per-worktree deduplicated focusChanged events: select worktree A, then B, then A again and the anchor still says B, so the island highlighted the wrong pane. Use the same source as the sidebar overlay, the terminal manager's active surface for the selected worktree. The lifecycle test now drives the setting through the store and waits for the observation to react instead of calling refreshLifecycle() itself, so a broken re-registration fails it. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
The roster (19pt) and attention collection (14pt) now use the same 12pt corners as the notched bar. Runtime icons keep their glyph size but the ring circle grows by one point of radius so the glyph and the rotating ring no longer touch; the cluster grows with it. Claude-Session: https://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Continues #753 from @SunChJ on a fork-owned branch. This branch contains every commit from the original PR, so merging it with a merge commit also marks #753 as merged and keeps the author's history and attribution intact.
Agent Island is an opt-in, notch-aware projection of the Active Agents roster at the top of a selected display. It adds no agent state or acknowledgement model: Working stays a compact carousel, Blocked and unviewed Done entries surface as a compact attention collection, and the expanded roster composes the same row presentation and context menu as the sidebar. See
docs/components/agent-island.mdanddocs-ai/066-agent-island/for the design record.Changes on top of #753
isIslandHoveredwhen the roster empties. Hovering pauses the carousel timer; when the last entry left, the compact island unmounted without a hover-exit, so the stale flag kept later Working pairs from rotating. Covered by aTestClockregression. Closes the remaining blocker from the Add Agent Island for Active Agents #753 review..display(id:name:)value, butnamecomes fromNSScreen.localizedNameand changes with the system language or a rename, leaving the picker with no matching tag.AgentIslandDisplaySelectionis an id-only picker identity mapped back to the stored preference with the catalog's current name. Covered by unit tests.swift-formatrun, which does not discover.swift-format.jsonand defaults to 100 columns.AppFeature.swiftcarried 78 lines of pure reflow next to 12 lines of real change; every whitespace-only hunk is restored so hot files diff by their functional changes alone.max(40, cutout + 8)pt tall and hung 8pt below the menu bar; it now uses the measured cutout height (which already follows the display scaling mode) with a 28pt floor for the icon cluster..animationkeyed on every entry refresh) interpolated from the transient offset and looked like it vanished and dropped in from above. The root is now pinned to the top and the cluster animation is keyed on membership/order only. The floating pill also no longer tears the cluster down on each carousel rotation.island(Action)replaces three parallel cases and a context-menu router; the reducer forwards the wrapped sidebar action and collapses only when it presents Prowl UI,AppFeaturesurfaces the window in one place. Island navigation tests move out of the quit test file.agentIslandEnabledand starts or stops the panel, so a disabled island costs nothing at launch.Models/toBusinessLogic/; the unusedrootWidthis gone.focusedSurfaceID, a keyboard anchor fed by per-worktree deduplicated focus events that goes stale when a worktree is re-selected; it now uses the same source as the sidebar overlay. The display catalog is resolved on first use so a disabled island installs nothing at launch, and the lifecycle test now goes through the real observation instead of callingrefreshLifecycle()itself. (Adversarial review, 2026-09-03.)docs-ai/066-agent-island/(four marked superseded by the next) are folded into000-plan.mdand001-action.md;docs/components/agent-island.mdnow describes current behavior only.Known limitation
On a notched display the bar sits on top of the menu bar, so while agents are running the ~120pt wing on each side of the cutout covers that part of the menu bar band; a menu title or status item under a wing is not clickable until the island disappears. Documented in
docs/components/agent-island.md; narrowing the wings or offering floating placement on notched displays is an open product decision.Testing
ActiveAgentsFeatureTests,AgentIslandScreenTests,SettingsFeatureTests,SettingsFilePersistenceTests,BaguaWorkingIndicatorTests,AppFeatureQuitTests,AppFeatureSettingsChangedTests,AgentIslandIsolationTests,AppFeatureAgentIslandTests,AgentIslandStateSummaryTests,AgentIslandRosterContentTests,AgentIslandIconClusterTests: 152 tests pass on the latest head;make build-appzero warningsmake checkpassesc4606b06) was greenhttps://claude.ai/code/session_01PuwHwiuyozSN88w7EgTPAL