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:
@@ -11,7 +11,8 @@
|
||||
* bar is the console's only header: Home (the workspace's home) at its left
|
||||
* end, `actions` (the workspace's Tools menu) at its right end. A click shows a tab in the focused pane, Ctrl/Cmd+click
|
||||
* (on a tab or a `+` item) opens it in a split to the right, and a tab drags
|
||||
* onto the panes (`PaneGrid.tsx`); a tab another pane shows is marked.
|
||||
* onto the panes (`PaneGrid.tsx`); a tab another pane shows is marked. A
|
||||
* right-click on a tab opens the same actions as a menu (`ContextMenu`).
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -26,6 +27,7 @@ import { useAiTools } from "../../hooks/useAiTools";
|
||||
import { AiToolIcon } from "../AiToolIcon";
|
||||
import { freeSlot } from "../lanes/WorkspaceHome";
|
||||
import { RunIcon, startPaneDrag } from "./PaneGrid";
|
||||
import { ContextMenu, MenuItem, MenuSeparator, useContextMenu } from "../ContextMenu";
|
||||
|
||||
export function ConsoleTabs({
|
||||
lane,
|
||||
@@ -59,6 +61,7 @@ export function ConsoleTabs({
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
const tabCtx = useContextMenu<RunHandle>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!menuOpen) return;
|
||||
@@ -106,6 +109,13 @@ export function ConsoleTabs({
|
||||
}
|
||||
};
|
||||
|
||||
// A tab-menu item: close the menu, then act on the tab it was opened on.
|
||||
const pickTab = (fn: (r: RunHandle) => unknown) => () => {
|
||||
const r = tabCtx.menu?.item;
|
||||
tabCtx.close();
|
||||
if (r) fn(r);
|
||||
};
|
||||
|
||||
const item =
|
||||
"flex w-full items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left text-sm text-fg-secondary hover:bg-surface-4 hover:text-fg-primary";
|
||||
|
||||
@@ -132,6 +142,7 @@ export function ConsoleTabs({
|
||||
key={r.id}
|
||||
draggable
|
||||
onDragStart={(e) => startPaneDrag(e, r.slot ?? 0)}
|
||||
onContextMenu={(e) => tabCtx.open(e, r)}
|
||||
data-shown={shown ? "true" : undefined}
|
||||
className={`group relative flex min-w-0 max-w-[15rem] items-center rounded-t-md ${
|
||||
active
|
||||
@@ -252,6 +263,35 @@ export function ConsoleTabs({
|
||||
</span>
|
||||
)}
|
||||
{actions && <div className="ml-auto flex items-center pr-2">{actions}</div>}
|
||||
{tabCtx.menu && (
|
||||
<ContextMenu
|
||||
x={tabCtx.menu.x}
|
||||
y={tabCtx.menu.y}
|
||||
onClose={tabCtx.close}
|
||||
testId={`console-tab-menu-${tabCtx.menu.item.slot ?? 0}`}
|
||||
>
|
||||
<MenuItem onSelect={pickTab((r) => onOpen(r.id))}>{t("sessionMenu.open")}</MenuItem>
|
||||
<MenuItem onSelect={pickTab((r) => onOpen(r.id, true))}>
|
||||
{t("sessionMenu.openSplit")}
|
||||
</MenuItem>
|
||||
{tabCtx.menu.item.sessionId && (
|
||||
<>
|
||||
<MenuItem onSelect={pickTab((r) => navigate(`/sessions/${r.sessionId}`))}>
|
||||
{t("sessionMenu.details")}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onSelect={pickTab((r) => void navigator.clipboard.writeText(r.sessionId ?? ""))}
|
||||
>
|
||||
{t("sessionMenu.copyId")}
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
<MenuSeparator />
|
||||
<MenuItem danger onSelect={pickTab((r) => void close(r.id))}>
|
||||
{t("tabs.close")}
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user