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:
@@ -463,15 +463,31 @@ router.delete("/:pattern", (req, res) => {
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// GET /api/pricing/cost - Get total cost across all sessions.
|
||||
// GET /api/pricing/cost - Get total cost across all sessions (`?lane=<id>`: the
|
||||
// sessions that workspace owns).
|
||||
// Honors the global data-scope: `?sources=<csv>` narrows the aggregate to those
|
||||
// origin machines (see server/lib/source-filter.js), matching the sessions /
|
||||
// stats / analytics endpoints so the Dashboard "total cost" tracks the selector.
|
||||
router.get("/cost", (req, res) => {
|
||||
const rawOffset = parseInt(req.query.tz_offset, 10);
|
||||
const tzModifier = Number.isFinite(rawOffset) ? `${-rawOffset} minutes` : "+0 minutes";
|
||||
const { clause, params } = sourceColumnClause(parseSources(req), "s.source");
|
||||
const whereClause = clause ? `WHERE ${clause}` : "";
|
||||
const lane = req.query.lane;
|
||||
if (lane !== undefined && !/^\d+$/.test(String(lane))) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: { code: "INVALID_INPUT", message: "lane must be a lane id" } });
|
||||
}
|
||||
const scope = sourceColumnClause(parseSources(req), "s.source");
|
||||
const params = [...scope.params];
|
||||
const where = scope.clause ? [scope.clause] : [];
|
||||
if (lane !== undefined) {
|
||||
// The sessions the workspace owns — the same rule as GET /api/sessions?lane=.
|
||||
where.push(
|
||||
"(s.lane_id = ? OR (s.lane_id IS NULL AND s.id IN (SELECT session_id FROM dashboard_runs WHERE lane_id = ?)))"
|
||||
);
|
||||
params.push(Number(lane), Number(lane));
|
||||
}
|
||||
const whereClause = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
||||
|
||||
const dailyTokens = dailyTokenRows(tzModifier, whereClause, params);
|
||||
const rules = stmts.listPricing.all();
|
||||
|
||||
Reference in New Issue
Block a user