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
+51 -28
View File
@@ -10,7 +10,8 @@
* next `title=` someone writes would be back to the browser's grey box.
* {@link GlobalTooltip} instead listens once at the document: on hover or
* keyboard focus (`:focus-visible`, not the focus a click gives) it takes the element's `title`, suppresses the native tooltip
* by removing the attribute for as long as the bubble is up, and restores it
* by removing the attribute — its ancestors' too, or the browser falls back
* to the nearest one's — for as long as the bubble is up, and restores them
* on the way out. Nothing else in the app has to know it exists, and a control
* keeps its native tooltip (and its accessible name) when JS has not run.
* While the title is parked, a control with no other name (icon-only) carries
@@ -50,6 +51,40 @@ const SELECTOR = "[title]:not([data-tip-off]), [data-tip]";
type Anchored = { text: string; rect: DOMRect };
type Placed = { left: number; top: number; arrow: number; above: boolean };
/**
* Takes `el`'s title into `data-tip`. React removing `title` while it is
* parked calls removeAttribute on an attribute that is already gone: no
* MutationRecord, nothing to see — and putting the parked copy back would
* write stale text for good. So, on THIS element and only while parked, that
* call is taken as React's intent: the parked copy is dropped and `onDrop`
* runs. (React DOM calls `node.removeAttribute`, the instance method; `unpark`
* deletes the shim.) No global prototype is touched.
*/
function park(el: HTMLElement, text: string, onDrop?: () => void) {
el.dataset.tip = text;
el.removeAttribute("title");
el.removeAttribute = (name: string) => {
if (name.toLowerCase() === "title" && el.dataset.tip !== undefined) {
delete el.dataset.tip;
onDrop?.();
}
Element.prototype.removeAttribute.call(el, name);
};
}
/** Undoes {@link park}. */
function unpark(el: HTMLElement) {
// Drop the instance shim; the prototype method is back.
delete (el as { removeAttribute?: unknown }).removeAttribute;
const text = el.dataset.tip;
if (text === undefined) return;
// React re-set `title` while it was parked (a poll flipped the hint): that
// value is current, the parked copy is stale. React never re-applies an
// unchanged prop, so writing the old one back would win for good.
if (!el.hasAttribute("title")) el.setAttribute("title", text);
delete el.dataset.tip;
}
/**
* Mount ONCE, near the router root. Every `title` in the app becomes a
* designed tooltip; nothing else has to change.
@@ -62,6 +97,8 @@ export function GlobalTooltip() {
// put back even if the pointer never leaves it (a route change, a re-render
// that unmounts it, Escape).
const parked = useRef<HTMLElement | null>(null);
// Ancestors of `parked` whose titles are parked with it (see `open`).
const silenced = useRef<HTMLElement[]>([]);
const timer = useRef<number | undefined>(undefined);
// The element whose delay is running, so re-entering it does not restart it.
const pending = useRef<HTMLElement | null>(null);
@@ -69,19 +106,11 @@ export function GlobalTooltip() {
const restore = useCallback(() => {
window.clearTimeout(timer.current);
pending.current = null;
silenced.current.forEach(unpark);
silenced.current = [];
const el = parked.current;
if (el) {
// Drop the instance shim `open` installed; the prototype method is back.
delete (el as { removeAttribute?: unknown }).removeAttribute;
const text = el.dataset.tip;
if (text !== undefined) {
// React re-set `title` while it was parked (a poll flipped the hint):
// that value is current, the parked copy is stale. React never
// re-applies an unchanged prop, so writing the old one back would win
// for good.
if (!el.hasAttribute("title")) el.setAttribute("title", text);
delete el.dataset.tip;
}
unpark(el);
if (el.dataset.tipAria !== undefined) {
el.removeAttribute("aria-label");
delete el.dataset.tipAria;
@@ -105,22 +134,16 @@ export function GlobalTooltip() {
if (!el.isConnected) return;
const current = (el.getAttribute("title") || "").trim();
if (!current) return;
el.dataset.tip = current;
el.removeAttribute("title");
// React removing `title` while it is parked calls removeAttribute on an
// attribute that is already gone: no MutationRecord, nothing to see —
// and `restore` would write the stale text back for good. So, on THIS
// element and only while parked, that call is taken as React's intent:
// the parked copy is dropped and the bubble hides. (React DOM calls
// `node.removeAttribute`, the instance method; `restore` deletes the
// shim.) No global prototype is touched.
el.removeAttribute = (name: string) => {
if (name.toLowerCase() === "title" && el.dataset.tip !== undefined) {
delete el.dataset.tip;
restore();
}
Element.prototype.removeAttribute.call(el, name);
};
park(el, current, restore);
// With its own title gone the browser shows the nearest ANCESTOR's
// native one instead (a pane header's drag hint over its Close
// button's bubble), so every ancestor title is parked too.
for (let a = el.parentElement; a; a = a.parentElement) {
const t = a.getAttribute("title");
if (t === null) continue;
park(a, t);
silenced.current.push(a);
}
// The title was an icon-only control's ONLY name; keep it named.
if (
!el.hasAttribute("aria-label") &&