import type { ContextMenuItem } from "@t3tools/contracts"; import type { SnoozePreset } from "@t3tools/client-runtime/state/thread-settled"; /** * Ids for the per-thread action menu. Snooze presets are dispatched as * `snooze:` so the union stays closed while the preset list * remains data-driven. */ export type ThreadActionMenuId = | "new-thread-on-branch" | "pin" | "unpin" | "settle" | "unsettle" | "snooze" | `snooze:${string}` | "unsnooze" | "rename" | "regenerate-title" | "mark-unread" | "copy-path" | "copy-branch" | "delete"; export interface ThreadActionMenuState { readonly branch: string | null; readonly isPinned: boolean; readonly isSettled: boolean; readonly isSnoozed: boolean; readonly canSnoozeNow: boolean; readonly isRegeneratingTitle: boolean; readonly supports: { readonly settlement: boolean; readonly snooze: boolean; readonly pinning: boolean; readonly titleRegeneration: boolean; }; readonly snoozePresets: ReadonlyArray; } /** * Single source for the per-thread action menu: the sidebar row's right-click * menu and the chat header menu both render exactly this list, so labels, * ordering, and capability gating cannot drift between the two surfaces. */ export function buildThreadActionMenuItems( state: ThreadActionMenuState, ): ReadonlyArray> { return [ ...(state.branch ? [ { id: "new-thread-on-branch" as const, label: `New thread on ${state.branch}`, }, ] : []), ...(state.supports.pinning ? [ state.isPinned ? { id: "unpin" as const, label: "Unpin thread" } : { id: "pin" as const, label: "Pin thread" }, ] : []), // Both lifecycle actions stay available on pinned threads: settling // clears the pin ("done" beats "keep on top"), and snoozing hides the // card until wake with the pin intact. ...(state.supports.settlement ? [ state.isSettled ? { id: "unsettle" as const, label: "Un-settle thread" } : { id: "settle" as const, label: "Settle thread" }, ] : []), ...(state.supports.snooze ? [ state.isSnoozed ? { id: "unsnooze" as const, label: "Wake thread" } : { id: "snooze" as const, label: "Snooze", disabled: !state.canSnoozeNow, children: state.snoozePresets.map((preset) => ({ id: `snooze:${preset.id}` as const, label: `${preset.label} (${preset.whenLabel})`, })), }, ] : []), { id: "rename", label: "Rename thread" }, ...(state.supports.titleRegeneration ? [ { id: "regenerate-title" as const, label: state.isRegeneratingTitle ? "Regenerating…" : "Regenerate title", disabled: state.isRegeneratingTitle, }, ] : []), { id: "mark-unread", label: "Mark unread" }, { id: "copy-path", label: "Copy path", icon: "copy" }, ...(state.branch ? [{ id: "copy-branch" as const, label: "Copy branch", icon: "copy" }] : []), { id: "delete", label: "Delete", destructive: true, icon: "trash" }, ]; }