Skip to content

feat: add application Dock menu#284

Open
melonamin wants to merge 4 commits into
umputun:masterfrom
melonamin:feat/dock-menu
Open

feat: add application Dock menu#284
melonamin wants to merge 4 commits into
umputun:masterfrom
melonamin:feat/dock-menu

Conversation

@melonamin

@melonamin melonamin commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add an AppKit Dock menu with New Session, Quick Terminal, and Dashboard actions
  • include recent sessions and sessions needing attention
  • retain dynamic menu targets and keep session actions scoped to the captured window

Why

Expose common actions and session navigation from the Dock without requiring the app window to be frontmost.

Mostly remove the need in menu bar extra #140

Screenshot

Screenshot 2026-07-23 at 13 57 24

@melonamin melonamin changed the title Add application Dock menu feat: add application Dock menu Jul 23, 2026
@melonamin
melonamin marked this pull request as ready for review July 23, 2026 18:03
@melonamin
melonamin requested a review from umputun as a code owner July 23, 2026 18:03

@umputun umputun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two blocking issues before this is ready:

  1. agterm/AppDelegate+DockMenu.swift:181 - a non-idle session can appear in both Recent Sessions and Sessions Needing Attention, but the Recent path calls only focusActiveSession(). It won't reveal a tagged right or scratch pane, so the same session behaves differently depending on which submenu selected it. Call revealActiveBlockedPane() for every session selection and remove the needsAttention branch. That helper already falls back to ordinary focus for idle sessions.

  2. agterm/AppDelegate+DockMenu.swift:44-185 - this new AppKit UI has no test coverage. Pls add focused Dock-menu UI tests for menu contents and enabled states, MRU/current-session filtering, attention ordering, nil-sender target invocation, and the captured-window case where window B becomes frontmost before a window A session is selected. The dashboard/zoom stale-action checks should be covered too.

minor:

  • agterm/AppDelegate+DockMenu.swift:95 hardcodes the MRU limit as 10, while the existing recent-session surfaces use SessionSwitcher.maxCandidates. Use the shared value here too.
  • update README.md and site/docs.html. The attention docs still say there are "two more ways," and neither public surface mentions the Dock menu or its last-active-window scope.
  • update .claude/rules/menu-actions.md and .claude/rules/windows.md for the new action surface. Add AppDelegate+DockMenu.swift to the applicable paths and document retained targets, captured store/window scope, the per-window modal check, and synchronous frontmost-window publication.

@umputun umputun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two blocking issues and one question remain:

  1. project.yml:182-215 - the new agtermTests target is application-hosted, but the Test action sets neither AGTERM_STATE_DIR nor AGTERM_CONTROL_SOCKET. Running the focused test launched agterm against the real config and app-support paths and started login shells before DockMenuTests.setUp() created its temporary library. It also killed my live control socket. Pls isolate the host process before agtermApp.init() runs. The current CI test job runs only agtermCore, so add a CI invocation for this target after it is safe to run.

  2. agterm/AppDelegate+DockMenu.swift:50-77 - should New Session, Quick Terminal, and Dashboard use the window captured when the menu opened, or whichever window is frontmost when the item is selected? The current code uses the latter, while README/site promise that all lists and actions stay tied to the captured window. If invocation-time frontmost is intended, pls narrow the docs to session lists/actions. If captured scope is intended, capture A's window/store and add the B-frontmost case for all three actions.

  3. agterm/AppDelegate+DockMenu.swift:178-180 - pane-aware selection is still wrong for an explicit left status or an omitted pane. revealActiveBlockedPane() falls through to focusActiveSession(), which follows the existing splitFocused; if the right pane was focused, the Dock jump stays on the right. Set the primary-pane target explicitly for .left/.none and add cases with the right pane initially focused.

@umputun

umputun commented Jul 23, 2026

Copy link
Copy Markdown
Owner

FYI: I have run out of weekly credits/limits for my daily-driver claude code and adopted skill, which is helping me review things, for codex. This is the reason why those reviews sound so categorical and demanding, as if codex writes to other models and not to humans. ;) nothing personal.

@melonamin

Copy link
Copy Markdown
Contributor Author

nothing personal

No worries, I'm used to Codex.

Quick question: have you considered including a review skill or a review-instructions.md file? There's a lot of churn in reviews because our agents are looking for different things.

@umputun umputun left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the three from last round check out. Test-action isolation holds on every route (I ran the target and my state dir, config and socket came back byte-identical), captured window/store scope is right, and .left/omitted targets the primary explicitly now. The round-1 items are in too, except the test coverage below.

agtermTests/DockMenuTests.swift - coverage gaps. the 9 tests cover menu contents, enabled states, MRU order and cap, attention ordering, nil-sender dispatch, and the window-B-frontmost captured scope for all three top-level items plus session selection. Not covered:

  • stale dashboard/zoom inertness for the three TOP-LEVEL items. makeCapturedSessionAction() only builds a session row, so tests 8 and 9 exercise session selection only. Test 6 opens the dashboard on window B, never on captured window A after the menu is built. Nothing proves New Session / Quick Terminal / Dashboard go inert when A enters a modal post-build, which is the case windows.md now documents.
  • the .right reveal branch. Only .scratch and .left/nil appear anywhere in the file, so AppActions+Focus.swift:83-85 never runs. The Dock is the fifth caller of that branch.
  • captured window CLOSED (not just modal) between build and invoke. Nothing calls library.closeWindow, so the library.store(for:) === store identity guard at AppDelegate+DockMenu.swift:169 and the weak store capture are both unexercised.
  • applicationDockMenu with library == nil, i.e. a Dock right-click before the scene .task wires it. The store = library?.activeStore nil path and the store == nil submenu branch are never hit.
  • target invalidation across a menu rebuild. No test builds a second menu then invokes an item from the first, which is the one path where a stale item's target is already gone.

separately, testSessionSelectionForcesPrimaryForExplicitAndOmittedLeftPane doesn't prove what the name says. DockMenuTestSurface isn't a GhosttySurfaceView, so focusSplitPane's as? GhosttySurfaceView cast never matches and first responder never moves. Both assertions check only that splitFocused was cleared, which happens at AppActions+Focus.swift:90, before focusSplitPane is called at all. Reasonable limit for a windowless hosted test, but rename it or add a comment so it doesn't read as focus coverage.

question on revealActiveBlockedPane. the fix went into the shared helper, so .left/omitted now clears splitFocused and forces the primary for every caller: sidebar click, ⌥⌘↑↓, first/last, both palettes, auto-follow. The agent-status hook sends --pane left from the primary pane and emits active on ordinary tool use, not only on a block. So a session whose agent is merely working, split hidden with the right pane zoomed, now un-zooms and jumps to the left pane on a plain sidebar click. Should the forced primary be gated on needsAttention (blocked/completed) rather than any non-idle status, or is pulling to the tagged pane on active what you want as well? No strong view here, it's a semantics call.

fyi the branch is 5 commits behind master and conflicting right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants