Skip to content

feat(desktop): unify assistant dock — kimi web + companion tabs, retire surface mounts - #483

Merged
physercoe merged 2 commits into
physercoe:mainfrom
agentfleets:agent/agentfleet/assistant-dock-companion
Jul 30, 2026
Merged

feat(desktop): unify assistant dock — kimi web + companion tabs, retire surface mounts#483
physercoe merged 2 commits into
physercoe:mainfrom
agentfleets:agent/agentfleet/assistant-dock-companion

Conversation

@agentfleets

Copy link
Copy Markdown
Collaborator

Motivation

User-approved redesign: the dock IS the companion. The app-level assistant dock (kimi web) and the per-surface AgentCompanion panels (Read's aside + reader tab, Author's aside) were two homes for "talk to an agent" — the companion even duplicated the dock's panel shape. This unifies them: the assistant dock becomes a tabbed panel — kimi web | Companion — hosting the ONE AgentCompanion, and the per-surface mounts retire. The kimi-web lifecycle (daemon hold, detach/attach, confirmed close) is untouched and stays scoped to the kimi tab.

This is also the SurfaceContext contract the design review asked for (docs/discussions/desktop-design-review.md §4): surfaces declare "what am I looking at" + "what can you insert" through a small registry instead of hand-rolled per-mount props.

Design

1. Tabbed dock. state/assistant.ts gains view: 'kimi' | 'companion', persisted like dockSide (termipod.assistant.view). AssistantDock.tsx's header leads with a segmented tab strip (the existing .seg/.seg-btn idiom). The body renders BOTH the kimi WebPanel and one AgentCompanion, each CSS-hidden by view (visibility, never unmounted once started — the webview white-screen hazard applies, so no display:none). Detach/attach, the placeholder, and the confirmed close behave exactly as before, on the kimi tab only; the Companion tab stays usable while kimi is detached.

2. Context-provider registry — new state/companionContext.ts (zustand). Surfaces register { label, build, insert? } keyed by job; registration marks the job most-recently-focused (a simple focus-order stack), unregistration removes it. The dock companion reads the ACTIVE provider for its context chip + onInsert.

  • ReadSurface registers the selected paper: label = title, build = the Paper/Authors/Year/Abstract block the aside passed, insert = append to the ref's notes.
  • AuthorSurface registers the active document: the exact three-branch build() the aside passed (markdown draft / figure spec / other-kind one-liner), insert = append into the markdown draft, or replace the figure body with the unfenced reply (today's semantics preserved for both kinds; structured bodies register no insert).
  • Focus-tracking approximation (sanctioned by the design): Read/Author have no focus event, so a surface registers on mount + selection/active-document change and unregisters on unmount/no-context. build/insert read their stores LIVE at call time, so registration only refires on label-affecting changes, not per keystroke.

3. Dock companion instance. storageKey: 'termipod.dock.agent' with a one-time, read-only fallback to the retired mounts' keys (termipod.read.agent, then termipod.author.agent) so existing agent bindings survive — state/companionBinding.ts. Everything else the component already does (agent picker, hub/local toggle, @-mention, annotate button, D2.1 annotationTargets self-registration, image handoff) is unchanged.

4. Retired. The Read aside + embedded reader assistant tab, the Author aside, both "✦ Assistant" toolbar buttons, showAgent/agentW state + resize handles, dead CSS (.read-agent, .author-agent, .ref-assistant-body), and the author.assistant / author.assistantHint / read.tabAssistant i18n keys (en+zh). New keys: assistant.tabKimi, assistant.tabCompanion (en+zh).

5. Amendment — kimi attach availability + reveal. The annotation target row's kimi availability moves from open && !detached to started && !detached (guest mounted = attachable — a hidden dock's guest never unmounts). On a successful kimi attach — injected OR clipboard fallback — the dock auto-opens on the kimi tab (reveal('kimi')) so the user lands in kimi's composer to review and send.

6. Amendment — dock steps aside during selection. While the overlay is armed (any origin: companion compose button, status-bar chip, palette, hotkey — all funnel through arm()), the dock hides via an annotating CSS class driven by dockHiddenForPhase(phase)open is never flipped, so Esc/cancel restores it exactly as it was, and it stays out of the captured pixels. On target pick the dock returns revealed: kimi tab after a kimi attach, Companion tab after a companion handoff — including the compose-box-armed case (the dock companion's own annotate button).

7. Handoff reveal. handOffToCompanion reveals the dock on the Companion tab when the handoff targets the dock companion's key (handoffRevealsDock) — and since reveal sets started, this also mounts the dock+companion on a never-opened dock, so the crop chip has somewhere to land.

The pure decisions (dock-hide flag, handoff routing, reveal predicate) live in bridge-free state/annotationTargets.ts per the established pattern (the store imports the shell bridge, which node ESM can't resolve).

Tests

  • New unit (node --test, desktop): assistant.test.ts (view persistence/switching, reveal, kimiAttachable truth table), companionContext.test.ts (register/replace/focus-order/unregister/insert passthrough), companionBinding.test.ts (the dock-key fallback migration incl. priority + non-dock no-fallback), annotation.test.ts (annotating-hide flag, handoff routing/reveal contract). annotationTargets.test.ts fixtures moved off the retired storage keys.
  • E2E (desktop/electron/e2e/annotation.spec.ts): the D2 test now opens the dock from the status-bar chip and drives the Companion tab (the old flow clicked the retired "✦ Assistant" button); both tests additionally assert the dock hides while armed and reveals on the Companion tab after the handoff. CI is the e2e gate; the spec stays hermetic (TERMIPOD_E2E, throwaway user-data-dir + workspace).

Verification

  • desktop: npm run build (tsc --noEmit + vite) — green
  • desktop/electron: npx tsc --noEmit — green; npm test — 262/262 pass
  • desktop: node --test 'src/**/*.test.ts' — 445/445 pass (32 in the new/updated files)
  • scripts/lint-desktop-tokens.sh — clean (semantic tokens only, no raw hex, no phantom var(--*))
  • scripts/lint-docs.sh — clean; no doc changes (docs referencing AgentCompanion are design-history plan/discussion docs, and the design-review doc already advocates this contract)

Notes

  • kimiweb (the embedded kimi web panel, its server lifecycle, detach window) is untouched.
  • The termipod.read.reader.agent reader-tab binding is deliberately not migrated — it was a second, reader-scoped binding, not a primary one.

agentfleet and others added 2 commits July 30, 2026 06:35
…e (kimi) hint

Review fixes on the unified dock (physercoe#483): close() now also unmounts the
Companion tab — a staged compose draft (crop chip, note) is discarded —
but the confirm copy only spoke of the kimi panel. Both locales now say
so. The toggle hint's '(kimi)' predates the tabbed dock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@physercoe
physercoe merged commit a8e6d4b into physercoe:main Jul 30, 2026
8 checks passed
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