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:
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file SessionRowMenu — the right-click menu of a session anywhere outside a
|
||||
* workspace's own lists (the Sessions table, the Dashboard's session and agent
|
||||
* cards): open its analysis page, open it in the workspace that owns it
|
||||
* (`sessions.lane_id` — an ended session opens its conversation there, never
|
||||
* a resume), copy its id or folder. Inside a workspace, `lanes/SessionMenu`
|
||||
* is used instead.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ContextMenu, MenuItem, MenuSeparator, type MenuPoint } from "./ContextMenu";
|
||||
|
||||
export type SessionRowRef = { id: string; lane_id?: number | null; cwd?: string | null };
|
||||
|
||||
export function SessionRowMenu({
|
||||
session,
|
||||
x,
|
||||
y,
|
||||
onClose,
|
||||
}: MenuPoint & { session: SessionRowRef; onClose: () => void }) {
|
||||
const { t } = useTranslation("sessions");
|
||||
const navigate = useNavigate();
|
||||
const pick = (fn: () => unknown) => () => {
|
||||
onClose();
|
||||
fn();
|
||||
};
|
||||
const copy = (text: string) => pick(() => void navigator.clipboard.writeText(text));
|
||||
|
||||
return (
|
||||
<ContextMenu x={x} y={y} onClose={onClose} testId={`session-row-menu-${session.id}`}>
|
||||
<MenuItem onSelect={pick(() => navigate(`/sessions/${session.id}`))}>
|
||||
{t("menu.details")}
|
||||
</MenuItem>
|
||||
{session.lane_id != null && (
|
||||
<MenuItem
|
||||
onSelect={pick(() =>
|
||||
navigate(`/workspace?lane=${session.lane_id}&session=${encodeURIComponent(session.id)}`)
|
||||
)}
|
||||
>
|
||||
{t("menu.inWorkspace")}
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuSeparator />
|
||||
<MenuItem onSelect={copy(session.id)}>{t("menu.copyId")}</MenuItem>
|
||||
{session.cwd && <MenuItem onSelect={copy(session.cwd)}>{t("menu.copyFolder")}</MenuItem>}
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user