From 8657ac56695e05b34ba5e6e5fbc6ea3310609284 Mon Sep 17 00:00:00 2001 From: dtoro Date: Sun, 19 Jul 2026 17:00:41 +0200 Subject: [PATCH] feat(web): open tasks/sessions as floating windows with independent live chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicking a task now opens it as a wmkit floating window (like entity windows already do) instead of navigating away from wherever you were. Several task windows can be open and actively streaming at once, each fully independent — no "which one's on screen" guard needed, since each window owns its own store bundle: - chat.ts: chatFor(sessionId)/loadSessionChat/sendSessionMessage give each window its own messages/streaming/connectionState, alongside the existing singleton path the main Chat page still uses unchanged. - workspace.ts: same split for plan/questions/touched/health-diffs (workspaceFor/startSessionWorkspace), each with its own live-event watermark since several windows can watch the same event stream. - activity.ts: activityLogFor(sessionId) mirrors the global derivation. SessionGraph.svelte, OperatorQuestion.svelte, and ActivityTimeline.svelte were converted from store-importing to prop-driven (matching the new ChatThread.svelte, extracted from Chat.svelte's transcript/input so both the main page and task windows share one implementation instead of duplicating markup/styling) so each can render either the global "current session" or a specific window's session. Also: minimized-window taskbar chips now cap at a max width with middle-ellipsis truncation instead of growing unbounded, and the window header's title/action-button row is fixed to genuinely match heights (not just share a center point) for more robust alignment. Co-Authored-By: Claude Sonnet 5 --- web/src/App.svelte | 2 + .../lib/components/ActivityTimeline.svelte | 11 +- web/src/lib/components/ChatThread.svelte | 377 ++++++++++++++++++ web/src/lib/components/EntityDesktop.svelte | 20 +- .../lib/components/MinimizedWindowsBar.svelte | 36 ++ .../lib/components/OperatorQuestion.svelte | 15 +- .../lib/components/SessionChatWindow.svelte | 91 +++++ .../lib/components/TaskContextPanel.svelte | 64 ++- web/src/lib/stores/activity.ts | 25 +- web/src/lib/stores/chat.ts | 171 +++++++- web/src/lib/stores/windows.ts | 17 + web/src/lib/stores/workspace.ts | 190 ++++++--- web/src/pages/Chat.svelte | 362 +---------------- web/src/pages/Overview.svelte | 12 +- 14 files changed, 945 insertions(+), 448 deletions(-) create mode 100644 web/src/lib/components/ChatThread.svelte create mode 100644 web/src/lib/components/MinimizedWindowsBar.svelte create mode 100644 web/src/lib/components/SessionChatWindow.svelte diff --git a/web/src/App.svelte b/web/src/App.svelte index 9b3b40e..c15e32f 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -9,6 +9,7 @@ import Learning from './pages/Learning.svelte' import Config from './pages/Config.svelte' import EntityDesktop from '$lib/components/EntityDesktop.svelte' + import MinimizedWindowsBar from '$lib/components/MinimizedWindowsBar.svelte' import { newChat } from '$lib/stores/chat' import { summary, subscribeContext, openSignalCount } from '$lib/stores/context' import { currentTask } from '$lib/stores/workspace' @@ -235,6 +236,7 @@ {/if} + diff --git a/web/src/lib/components/ActivityTimeline.svelte b/web/src/lib/components/ActivityTimeline.svelte index 663d4d8..996853e 100644 --- a/web/src/lib/components/ActivityTimeline.svelte +++ b/web/src/lib/components/ActivityTimeline.svelte @@ -1,5 +1,5 @@ + +
+
+
+ {#if messages.length === 0} +
+
+

Nomos

+

Your resident operator. Ask about the fleet, or tell it to act.

+
+ {#if suggestions.length} +
+ {#each suggestions as q} + + {/each} +
+ {/if} +
+ {/if} + + {#each messages as msg (msg.id)} +
+ {#if msg.role === 'user'} +
{msg.text}
+ {:else} +
+ {#if msg.text} +
+ + {@html render(msg.text)} +
+ {/if} +
+ {/if} +
+ {/each} + e.status === 'running')} + lastActivity={$activityLog.find((e) => e.status === 'running') ?? null} + {error} + /> +
+
+
+ + {#if connectionState === 'disconnected'} +
+
+
+
+ {:else if connectionState === 'reconnecting'} +
+
+
+
+ {/if} + + {#if error} +
+
+ {error} +
+
+ {/if} + + {#each chatErrors as err (err.id)} +
+
+ {err.message} + {#if err.action} + + {/if} + +
+
+ {/each} + +
+
{ + e.preventDefault() + submit() + }} + > +