Files
ThothII/frontend/src/shell/SessionMenu.tsx
T
marcopanandClaude Opus 4.8 0a13f71b4d feat(resume): Resume in the kebab menu (A1) + harden the resume kickoff (A2)
A1 — SessionMenu gains a Resume item, gated to status!=="finalized" && !archived
(matching the backend's 409 read-only guard), wired in AppShell to doResume ->
POST /sessions/:id/resume. SessionMenu.test.tsx (3 tests); frontend 96/96, tsc clean.

A2 — diagnosis-first clean-room repro driving `pi --mode rpc` with the backend's
exact resume handshake shows the cold-start stall NO LONGER reproduces on pi
0.79.4 (8/8 chained into `tht session show` + `read SKILL.md` in-turn, fresh and
partway sessions). The earlier narrate-and-stop predates the pi upgrade.
Defense-in-depth anyway: RIPRENDI_KICKOFF hardened to force the in-turn tool call
(gate_resume_kickoff.test.js + live regression 2/2). Gate JS 34/34.

PROJECT_STATE open-item #1 (resume stall) flipped to RESOLVED; cross-model resume
robustness folded into workstream G.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:19:10 +02:00

68 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Menu } from "@base-ui/react/menu";
import { MoreVertical } from "lucide-react";
import type { SessionSummary } from "../api/types";
interface Props {
session: SessionSummary;
groups: string[];
onResume: () => void;
onView: () => void;
onRename: () => void;
onMove: (group: string) => void;
onNewGroup: () => void;
onArchiveToggle: () => void;
onDelete: () => void;
}
const itemCls =
"flex w-full cursor-default select-none items-center rounded-md px-2 py-1.5 text-sm outline-none data-highlighted:bg-accent data-highlighted:text-accent-foreground";
export function SessionMenu({ session, groups, onResume, onView, onRename, onMove, onNewGroup, onArchiveToggle, onDelete }: Props) {
// Resume re-enters the workflow; only meaningful while the session is still live
// (the backend returns 409 for finalized/archived, matching this guard).
const canResume = session.status !== "finalized" && !session.archived;
return (
<Menu.Root>
<Menu.Trigger
aria-label="Session actions"
className="rounded-md p-1 text-muted-foreground opacity-0 transition-opacity hover:bg-accent group-hover:opacity-100 data-popup-open:opacity-100"
onClick={(e) => e.stopPropagation()}
>
<MoreVertical className="size-4" />
</Menu.Trigger>
<Menu.Portal>
<Menu.Positioner side="bottom" align="end" sideOffset={4}>
<Menu.Popup className="z-50 min-w-44 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-none">
{canResume && (
<Menu.Item className={itemCls} onClick={onResume}>Resume</Menu.Item>
)}
<Menu.Item className={itemCls} onClick={onView}>Split view</Menu.Item>
<Menu.Item className={itemCls} onClick={onRename}>Rename</Menu.Item>
<Menu.SubmenuRoot>
<Menu.SubmenuTrigger className={itemCls}>Move to group ›</Menu.SubmenuTrigger>
<Menu.Portal>
<Menu.Positioner side="right" align="start">
<Menu.Popup className="z-50 min-w-44 rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-none">
{groups.filter((g) => g !== session.group).map((g) => (
<Menu.Item key={g} className={itemCls} onClick={() => onMove(g)}>{g}</Menu.Item>
))}
{session.group && (
<Menu.Item className={itemCls} onClick={() => onMove("")}>No group</Menu.Item>
)}
<Menu.Item className={itemCls} onClick={onNewGroup}>New group…</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.SubmenuRoot>
<Menu.Item className={itemCls} onClick={onArchiveToggle}>
{session.archived ? "Restore" : "Archive"}
</Menu.Item>
<Menu.Separator className="my-1 h-px bg-border" />
<Menu.Item className={`${itemCls} text-destructive`} onClick={onDelete}>Delete</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>
);
}