feat(desktop): unify assistant dock — kimi web + companion tabs, retire surface mounts - #483
Merged
physercoe merged 2 commits intoJul 30, 2026
Conversation
…re surface mounts
…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>
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.
Motivation
User-approved redesign: the dock IS the companion. The app-level assistant dock (kimi web) and the per-surface
AgentCompanionpanels (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 ONEAgentCompanion, 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
SurfaceContextcontract 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.tsgainsview: 'kimi' | 'companion', persisted likedockSide(termipod.assistant.view).AssistantDock.tsx's header leads with a segmented tab strip (the existing.seg/.seg-btnidiom). The body renders BOTH the kimiWebPaneland oneAgentCompanion, each CSS-hidden byview(visibility, never unmounted oncestarted— the webview white-screen hazard applies, so nodisplay: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.build/insertread 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, thentermipod.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
assistanttab, the Author aside, both "✦ Assistant" toolbar buttons,showAgent/agentWstate + resize handles, dead CSS (.read-agent,.author-agent,.ref-assistant-body), and theauthor.assistant/author.assistantHint/read.tabAssistanti18n 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 && !detachedtostarted && !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 anannotatingCSS class driven bydockHiddenForPhase(phase)—openis 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.
handOffToCompanionreveals the dock on the Companion tab when the handoff targets the dock companion's key (handoffRevealsDock) — and sincerevealsetsstarted, 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.tsper the established pattern (the store imports the shell bridge, which node ESM can't resolve).Tests
node --test, desktop):assistant.test.ts(view persistence/switching,reveal,kimiAttachabletruth 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.tsfixtures moved off the retired storage keys.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) — greendesktop/electron:npx tsc --noEmit— green;npm test— 262/262 passdesktop: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 phantomvar(--*))scripts/lint-docs.sh— clean; no doc changes (docs referencingAgentCompanionare design-history plan/discussion docs, and the design-review doc already advocates this contract)Notes
termipod.read.reader.agentreader-tab binding is deliberately not migrated — it was a second, reader-scoped binding, not a primary one.