feat: right-click menus everywhere, workspace/session cost, long-term DB safety

Right-click menus: one portal shell (components/ContextMenu.tsx) for every
item with actions - sidebar/Recents/archived session rows (SessionMenu),
console tabs, pane headers, the Changes panel's files/tree/commits, the
workspace home, sessions outside a workspace (SessionRowMenu: Sessions
table, Dashboard, Activity, Workflow runs), prompt images, every Settings
list and every Claude Code/Codex config list (FileItemMenu). Buttons stay;
a delete keeps its button's confirm; the shell stops portal clicks from
reaching the card that opened it.

Status bar: Workspace $ and Session $ on the Workspace page, from
?lane= on GET /api/pricing/cost and the focused/replayed session's cost.

Database, no data dropped: synchronous=NORMAL, journal_size_limit 64 MB,
hourly PASSIVE checkpoint + PRAGMA optimize, idx_events_session_created
(replacing the redundant idx_events_session), watchdog reads
sessions.transcript_path, transcript parsed before the hook's write
transaction, GET /api/sessions/:id?events=0 for the web page (reloads only
on its own session, debounced), streamed export, transactional clear-data
and chunked cleanup, subagent import reads tool_use_ids once, runtime logs
rotated past 5 MB.
This commit is contained in:
2026-10-05 09:37:48 +07:00
parent 65fe680d38
commit ad6e80192d
80 changed files with 2914 additions and 489 deletions
+21 -4
View File
@@ -62,7 +62,7 @@ import { keepIfSame } from "../lib/keepIfSame";
import { useLanes } from "../hooks/useLanes";
import { LaneConsolePane } from "../components/run/LaneConsolePane";
import { ConsoleTabs } from "../components/run/ConsoleTabs";
import { type LayoutUpdate, PaneGrid, usePaneLayout } from "../components/run/PaneGrid";
import { type LayoutUpdate, PaneGrid, SplitMenus, usePaneLayout } from "../components/run/PaneGrid";
import {
type Edge,
type PaneLayout,
@@ -298,7 +298,7 @@ function ConsoleArea({
onRunsChanged();
if (!r.run_id || laneRef.current !== laneId) return;
pending.current.set(slot, activeRuns);
change((l) => moveInLayout(l, slot, target, edge));
change((l) => moveInLayout(l.root ? l : view, slot, target, edge));
} catch (err) {
setSplitError(err instanceof Error ? err.message : String(err));
}
@@ -327,8 +327,25 @@ function ConsoleArea({
}}
onClosed={(runId) => ended(slotOf(runId))}
onHome={onNewSession}
// Per-workspace actions (mcp sync, sync-base check, readme).
actions={<LaneToolsMenu lane={lane} />}
// Per-workspace actions (mcp sync, sync-base check, readme), and
// while one pane is shown its split menus (a split pane has its own).
actions={
<div className="flex items-center gap-1">
{shown.length === 1 && shown[0] !== undefined && (
<SplitMenus
slot={shown[0]}
shown={shown}
runs={tabs}
label={label}
// A layout never saved is the one-pane `view`: split that.
onChange={(fn) => change((l) => fn(l.root ? l : view))}
canStart={free !== null}
onNewSplit={(target, edge) => void newSplit(target, edge)}
/>
)}
<LaneToolsMenu lane={lane} />
</div>
}
/>
)}
{splitError && (