From 78e808bc5438e64673b9fe16cef5601856ab7f6c Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Mon, 1 Jun 2026 21:53:48 +0200 Subject: [PATCH] feat: sessions page workspace-first flow + workspace instance chips - Sessions page: replaced CreateSessionForm with workspace selector + ToolStarter - Fetches workspaces, shows dropdown, then renders ToolStarter for selected workspace - Removed old project/repo/tool-type/config-profile/clone-mode flow - Workspace cards: new WorkspaceInstanceChips component fetches and displays running instances per workspace with status-colored chips and open links - Styles: .instance-chip variants (running/starting/error), .tool-starter-header Quality gates: tsc --noEmit clean, pytest workspaces API (9 passed, 1 skipped) --- apps/web/src/components/tool-starter.tsx | 28 ++-- apps/web/src/components/workspace-card.tsx | 8 +- .../components/workspace-instance-chips.tsx | 57 +++++++ apps/web/src/pages/sessions.tsx | 150 ++++++++++-------- apps/web/src/styles.css | 64 ++++++++ 5 files changed, 219 insertions(+), 88 deletions(-) create mode 100644 apps/web/src/components/workspace-instance-chips.tsx diff --git a/apps/web/src/components/tool-starter.tsx b/apps/web/src/components/tool-starter.tsx index 9821790..80840f6 100644 --- a/apps/web/src/components/tool-starter.tsx +++ b/apps/web/src/components/tool-starter.tsx @@ -14,7 +14,11 @@ export interface ToolStarterProps { onCancel?: () => void; } -export function ToolStarter({ workspace, onStarted, onCancel }: ToolStarterProps) { +export function ToolStarter({ + workspace, + onStarted, + onCancel, +}: ToolStarterProps) { const [toolTypes, setToolTypes] = useState([]); const [toolTypesLoading, setToolTypesLoading] = useState(true); const [toolTypesError, setToolTypesError] = useState(null); @@ -105,9 +109,7 @@ export function ToolStarter({ workspace, onStarted, onCancel }: ToolStarterProps setStarting(true); setError(null); try { - const { createInstance, startInstance } = await import( - "../api/sessions" - ); + const { createInstance, startInstance } = await import("../api/sessions"); const instance = await createInstance( workspace.project_id, workspace.repo_id, @@ -132,12 +134,7 @@ export function ToolStarter({ workspace, onStarted, onCancel }: ToolStarterProps } finally { setStarting(false); } - }, [ - selectedToolTypeId, - selectedProfileId, - workspace, - onStarted, - ]); + }, [selectedToolTypeId, selectedProfileId, workspace, onStarted]); return (
@@ -180,12 +177,8 @@ export function ToolStarter({ workspace, onStarted, onCancel }: ToolStarterProps ))} - {toolTypesLoading && ( - Loading tools... - )} - {toolTypesError && ( - {toolTypesError} - )} + {toolTypesLoading && Loading tools...} + {toolTypesError && {toolTypesError}}
{/* Config Profile */} @@ -227,8 +220,7 @@ export function ToolStarter({ workspace, onStarted, onCancel }: ToolStarterProps ) : ( - {" "} - No SSH key assigned to repository + No SSH key assigned to repository )} diff --git a/apps/web/src/components/workspace-card.tsx b/apps/web/src/components/workspace-card.tsx index 47ebed4..79e86fe 100644 --- a/apps/web/src/components/workspace-card.tsx +++ b/apps/web/src/components/workspace-card.tsx @@ -2,6 +2,7 @@ import { Link } from "react-router-dom"; import { Icon } from "./icon"; +import { WorkspaceInstanceChips } from "./workspace-instance-chips"; import type { Workspace } from "../types/workspace"; export interface WorkspaceCardProps { @@ -46,12 +47,7 @@ export function WorkspaceCard({

{workspace.branch}

- {workspace.instance_count > 0 && ( -

- {workspace.instance_count} active tool - {workspace.instance_count > 1 ? "s" : ""} -

- )} +
- {/* Create Session */} + {/* Create Session — workspace-first */}
-

Create New Session

- { - setSelectedProject(projectId); - }} - onSuccess={handleCreateSuccess} - /> +

Start New Tool

+ {workspacesLoading ? ( +

Loading workspaces...

+ ) : workspaces.length === 0 ? ( +

+ No workspaces yet.{" "} + Create a workspace first. +

+ ) : ( +
+ {!selectedWorkspace ? ( +
+ + +
+ ) : ( +
+
+

+ {selectedWorkspace.project_name} /{" "} + {selectedWorkspace.repo_name} /{" "} + {selectedWorkspace.name} +

+ +
+ setSelectedWorkspace(null)} + /> +
+ )} +
+ )}
{/* Dirty Delete Confirmation Modal */} @@ -228,8 +250,8 @@ export const SessionsPage = () => {

Uncommitted Changes

The repository{" "} - {dirtyDeleteSession.repository_name} has - uncommitted changes. Deleting this session will permanently + {dirtyDeleteSession.repository_name}{" "} + has uncommitted changes. Deleting this session will permanently lose these changes.

diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index a123ee2..8428344 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -5551,3 +5551,67 @@ a:active, gap: var(--space-2); font-size: var(--font-size-sm); } + +/* ─── Instance Chips ─── */ +.instance-chips { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-top: var(--space-2); +} + +.instance-chip { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: 0.2rem 0.5rem; + border-radius: 999px; + font-size: var(--font-size-sm); + background: var(--bg); + border: 1px solid var(--border); +} + +.instance-chip.running { + background: var(--success-light); + border-color: var(--success); + color: var(--success); +} + +.instance-chip.starting, +.instance-chip.building, +.instance-chip.probing { + background: var(--warning-light); + border-color: var(--warning); + color: var(--warning); +} + +.instance-chip.error, +.instance-chip.unhealthy, +.instance-chip.stopped { + background: var(--danger-light); + border-color: var(--danger); + color: var(--danger); +} + +.instance-chip a { + font-size: 0.75rem; + opacity: 0.7; +} + +.instance-chip a:hover { + opacity: 1; +} + +/* ─── Tool Starter Header ─── */ +.tool-starter-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-3); + padding-bottom: var(--space-2); + border-bottom: 1px solid var(--border); +} + +.tool-starter-header h4 { + margin: 0; +}