fix(workspace): a new tab shows in its workspace; a row opens its tabs
A tab opened bare from + had no session row until its first hook, and the sidebar re-read its live panes only when a workspace's status or session moved, which a tab in slots 1-3 never does: the new tab showed nowhere. The sidebar now lists a live AI tab whose session has no row yet as "New session" straight from the run (its session id is known from the start) and follows the Workspace page's own run-list poll (LIVE_RUNS_EVENT). A sidebar workspace row now opens the workspace's live tabs on the pane it last focused, and its home only when it has none; a Home button at the left end of the tab bar reaches the home of a workspace whose tabs keep running.
This commit is contained in:
@@ -3,10 +3,12 @@
|
||||
* Desktop's project list: every workspace (lane) on one line in the stored drag
|
||||
* order (`laneOrderStorage.ts`), each expanding to ITS sessions
|
||||
* (`WorkspaceSessionList.tsx`) — no repo grouping, no worktree rows. Picking a
|
||||
* workspace opens `/workspace?lane=<id>`, the one place a workspace is
|
||||
* selected; `+` on a row opens it for a new session. Live status comes from
|
||||
* the shared lane list (`useLanes`), live panes from `GET /api/run`, re-read
|
||||
* whenever a lane's status or session moves. Owns its own Add-workspace and
|
||||
* workspace opens its tabs (`/workspace?lane=<id>&run=<runId>`, the pane it
|
||||
* last focused) when it has live ones, else its home (`?lane=<id>`, also what
|
||||
* `+` on a row opens for a new session). Live status comes from the shared
|
||||
* lane list (`useLanes`), live panes from `GET /api/run`, re-read whenever a
|
||||
* lane's status or session moves and handed over by the Workspace page's own
|
||||
* poll (`LIVE_RUNS_EVENT`). Owns its own Add-workspace and
|
||||
* destructive modals so the row menu works from any page. Which rows are
|
||||
* expanded lives in `projectTreePrefs.ts` (`expanded`).
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
@@ -18,6 +20,7 @@ import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { Plus, Search } from "lucide-react";
|
||||
import { api, type CwdSuggestion, type RunHandle } from "../lib/api";
|
||||
import type { Lane } from "../lib/types";
|
||||
import { loadLayout } from "../lib/paneLayout";
|
||||
import { useLanes } from "../hooks/useLanes";
|
||||
import { useAiTools } from "../hooks/useAiTools";
|
||||
import {
|
||||
@@ -41,6 +44,9 @@ function selectedFrom(pathname: string, search: string) {
|
||||
return { lane: v === null ? null : Number(v), run: p.get("run"), session: p.get("session") };
|
||||
}
|
||||
|
||||
/** The Workspace page's polled run list, handed to the sidebar as it lands. */
|
||||
export const LIVE_RUNS_EVENT = "agenthub:live-runs";
|
||||
|
||||
export function WorkspaceTree() {
|
||||
const { t } = useTranslation("lanes");
|
||||
const navigate = useNavigate();
|
||||
@@ -62,8 +68,10 @@ export function WorkspaceTree() {
|
||||
const [cwdSuggestions, setCwdSuggestions] = useState<CwdSuggestion[]>([]);
|
||||
const [orderTick, setOrderTick] = useState(0);
|
||||
|
||||
// A pane starts, ends or learns its session id exactly when a lane's status
|
||||
// or session moves, so that is when the live-pane list is re-read.
|
||||
// A slot-0 pane starts, ends or learns its session id exactly when a lane's
|
||||
// status or session moves, so that is when the live-pane list is re-read;
|
||||
// the Workspace page also hands over every run list it polls (a tab in
|
||||
// slots 1–3 moves no lane column).
|
||||
const runKey = lanes.map((l) => `${l.id}:${l.status}:${l.session_id}:${l.run_id}`).join(",");
|
||||
useEffect(() => {
|
||||
let live = true;
|
||||
@@ -75,6 +83,11 @@ export function WorkspaceTree() {
|
||||
live = false;
|
||||
};
|
||||
}, [runKey]);
|
||||
useEffect(() => {
|
||||
const take = (e: Event) => setLiveRuns((e as CustomEvent<RunHandle[]>).detail);
|
||||
window.addEventListener(LIVE_RUNS_EVENT, take);
|
||||
return () => window.removeEventListener(LIVE_RUNS_EVENT, take);
|
||||
}, []);
|
||||
|
||||
const openAdd = (sourceRepo?: string) => {
|
||||
setAddPrefill(sourceRepo ? { sourceRepo } : null);
|
||||
@@ -86,6 +99,16 @@ export function WorkspaceTree() {
|
||||
};
|
||||
|
||||
const select = useCallback((id: number) => navigate(`/workspace?lane=${id}`), [navigate]);
|
||||
// A row click: a workspace with live tabs opens on them — on the pane it
|
||||
// last focused — and one with none on its home (the tab bar's Home button
|
||||
// reaches the home of the other kind).
|
||||
const open = (id: number) => {
|
||||
const runs = liveRuns.filter((r) => r.laneId === id && r.status === "running");
|
||||
const focus = loadLayout(id).focus;
|
||||
const run = runs.find((r) => (r.slot ?? 0) === focus) ?? runs[0];
|
||||
if (run) navigate(`/workspace?lane=${id}&run=${encodeURIComponent(run.id)}`);
|
||||
else select(id);
|
||||
};
|
||||
const expanded = (id: number) => !!prefs.expanded[id] || !!query.trim();
|
||||
const setExpanded = (id: number, open: boolean) =>
|
||||
writeProjectTreePrefs({ expanded: { ...prefs.expanded, [id]: open } });
|
||||
@@ -244,7 +267,7 @@ export function WorkspaceTree() {
|
||||
expanded={expanded(lane.id)}
|
||||
renaming={renamingId === lane.id}
|
||||
onSelect={() => {
|
||||
select(lane.id);
|
||||
open(lane.id);
|
||||
if (!expanded(lane.id)) setExpanded(lane.id, true);
|
||||
}}
|
||||
onToggle={() => setExpanded(lane.id, !expanded(lane.id))}
|
||||
|
||||
Reference in New Issue
Block a user