feat(desktop): split-pane multi-session, kanban live events + attachments, full locale parity - #58338
Open
rod-nxtlevel wants to merge 6 commits into
Open
Conversation
Board overlay (CronView pattern) over the existing /api/plugins/kanban backend: 8 dnd-kit columns in BOARD_COLUMNS order, card drawer with comments/events/runs/worker-log/attachments/DAG links, new-card dialog, board switcher, dispatcher nudge. - HOME-scoped by design: no profileScoped()/?profile= on kanban routes. - Server-authoritative mutations: optimistic moves reconcile from the server response (rule bounces like done→review render where the server put them, with an info toast); 409s revert with the backend's detail. - 'running' is never a drag target (backend 400s direct PATCH; ready→ running only via dispatcher claim) — Nudge dispatcher instead. - 4s visibility-gated polling with latest_event_id cursor (atom identity preserved when unchanged); overlay wrapped in its own ErrorBoundary. - parseKanbanApiError unwraps the Electron IPC error prefix so HTTP status/detail survive the bridge. - i18n en+zh (+types); ja/zh-hant fall back to English. - 22 vitest cases (store rules, move/revert/bounce, cursor identity, component smoke via mocked hermes:api). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- resolveGatewayWsUrl maps mint failures to reauth only when auth-shaped (401/403/needsOauthLogin); transport errors stay retryable. - Failed initial boot retries on 5s/15s/30s/60s jittered backoff and on wake/online/visibility nudges; genuine auth failures latch with the sign-in overlay instead of thrashing. - After a socket reconnect (primary or secondary), reconcile cached busy sessions against session.active_list — clears composers stuck on 'Thinking…' when message.complete died with the socket. - Coalesce refreshSessions (1.5s trailing + single-flight, ancillary cron/messaging fan-out throttled to 30s) and debounce session.info config refetches (5s, gated to config-bearing events). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ross-profile overview, scoped query caches - Per-profile last-session memo: switching profiles restores each profile's last open session (existence-checked, probe fallback, token-guarded against races; deleted/archived sessions evicted). - Profile rail badges: working spinner dot + amber needs-input count per profile with session tooltips; click opens the neediest session in that profile. dnd-kit sortable behavior preserved. - Command center gains an 'Overview' landing section: per-profile working/attention/total/last-activity rows with quick-switch, plus a kanban summary strip (per-status counts + hot review/blocked cards) that degrades gracefully when the backend lacks the kanban plugin. - react-query keys are profile-scoped (config, config-schema, skills, toolsets, hub sources/search, model-options); the blanket invalidateQueries() on profile change is gone — profile hopping hits warm caches with no cross-profile bleed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The overlay X floats absolutely at right-3 (w = --titlebar-control-size) and the panel header rides up into the titlebar band, so right-aligned PanelHeader actions rendered underneath it (first visible in the kanban view — 'New card' overlapped the close button). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt coverage - Live /api/plugins/kanban/events WebSocket: main-process mints authed WS URLs (loopback ?token= / OAuth single-use ?ticket=) with ?since= cursor and ?board= pinned at handshake; renderer singleton connection with 250ms-coalesced refetch on events, catch-up on open, 1s..30s backoff; healthy socket stretches the 4s poll to a 60s safety net, any drop resumes fast polling. Stale preload degrades to polling. - Attachments: download to ~/Downloads with collision resolution and a Reveal toast; multipart upload with a 25MB pre-check mirroring the backend cap; binary-capable authed fetch in main (token + OAuth modes); errors surface backend detail via parseKanbanApiError. - ja.ts and zh-hant.ts gain all 360 keys missing relative to zh coverage (kanban, badges, overview, and long-standing gaps). - PanelHeader actions now clear the floating overlay close button. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two chat sessions side-by-side in one window, each with independent composer, streaming, busy state, and profile. - Singletons keep their meaning as 'the active pane's view'; the split pane runs a factory-created PaneSessionView bundle fed by the per-session state cache's new multi-view fan-out. Single-pane behavior is unchanged (pinned suites pass unedited). - Sends/resumes execute only from the active pane; the split's request wrapper awaits the gateway-profile swap (closes the Enter-beats-swap race); background main-pane RPCs ride a profile-pinned socket; boot restore uses ensureProfileSocketOpen and never steals the active swap. - Open via ⌘⇧\ (rebindable), command palette, or 'Open in split' on any session row; same session never opens in both panes; split state and width persist; narrow windows collapse the split and restore it. - Socket keep-set and keepalive include the split session's profile; native notifications treat both visible panes as foreground. - 50+ new tests (split store, cache fan-out, mirror, dispatcher, profile pinning, composer bus gating, keybind, keep-set, notifications). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
teknium1
reviewed
Jul 15, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the substantial desktop implementation. The feature premise remains valid on current main: apps/desktop/src/app/desktop-controller.tsx:1112 constructs one ChatView, and both chat routes use that element at :1289-1291.
Problems
apps/desktop/src/store/kanban.ts:115-117has a board-switch race. It reads$kanbanActiveBoardfor the request, but unconditionally publishes the response afterawait. If the user selects another board meanwhile,setKanbanActiveBoard()clears the view (:58-70), while the new refresh returns early because$kanbanRefreshingis already true (:107-110). The old response can then repopulate the new selection with the previous board until polling retries.
Suggested changes
- Add a board-selection generation/token around
refreshKanbanBoard()and discard stale responses; schedule the latest board's refresh when an earlier request settles. Cover a deferred old-board response followed by a board switch.
Automated hermes-sweeper review.
| try { | ||
| const next = await getKanbanBoard($kanbanActiveBoard.get()) | ||
|
|
||
| $kanbanBoard.set(reconcileKanbanBoard($kanbanBoard.get(), next)) |
Contributor
There was a problem hiding this comment.
This commits a response even if setKanbanActiveBoard() changed the selected board while line 115 was awaiting. The replacement refresh is suppressed by the single-flight guard, so the old board can repopulate the new selection until polling retries. Please make this request generation/board-scoped and add a deferred-response board-switch test.
5 tasks
This was referenced Jul 29, 2026
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.
What
Three additions, stacked on #58138 (which stacks on #58109 + #58137):
Split-pane multi-session (2 panes)
Two chat sessions side-by-side in one window — independent composer, streaming, busy state, and profile per pane.
PaneSessionViewbundle; the per-session state cache gained a multi-view fan-out (line-for-line legacy behavior with one registered view — its existing test passes unedited).ensureProfileSocketOpenso it can never steal the active gateway.Kanban live events + attachment transfer (extends #58137)
/api/plugins/kanban/eventsWS with the dashboard's canonical auth (loopback ?token= / gated single-use ?ticket=),?since=cursor,?board=pinned at handshake; 250ms-coalesced refetch; healthy socket stretches the 4s poll to a 60s safety net; any drop resumes fast polling; stale preload degrades to polling.Locale parity
ja.ts and zh-hant.ts gain every key missing relative to zh coverage (360 keys each) — kanban, badges, overview, split-pane strings, and long-standing gaps. All four locales now move together.
Tests
100+ new cases across split store/mirror/dispatcher/profile-pinning/composer-bus-gating/keep-set/notifications, live-events lifecycle, attachment transfer, and i18n. Typecheck + lint clean; full-sweep failure set byte-identical to the pre-existing baseline (stash-verified).
🤖 Generated with Claude Code