/** * TypeScript types for Mission Control. * * IPC types are auto-generated by tauri-specta and re-exported from bindings. * Frontend-only types are defined here. */ // -- Re-export IPC types from generated bindings -- export type { BridgeStatus, CaptureResult, JsonValue, LoreStatus, LoreSummaryStatus, McError, McErrorCode, SyncResult, Result, } from "./bindings"; // -- Type guards for IPC types -- import type { McError } from "./bindings"; /** Type guard to check if an error is a structured McError */ export function isMcError(err: unknown): err is McError { if (typeof err !== "object" || err === null) return false; const obj = err as Record; return ( typeof obj.code === "string" && typeof obj.message === "string" && typeof obj.recoverable === "boolean" ); } // -- Frontend-only types -- /** The type of work item surfaced in the Focus View */ export type FocusItemType = "mr_review" | "issue" | "mr_authored" | "manual"; /** A single work item that can be THE ONE THING */ export interface FocusItem { /** Unique key matching bridge mapping (e.g., "mr_review:g/p:847") */ id: string; /** Human-readable title */ title: string; /** Type badge to display */ type: FocusItemType; /** Project path (e.g., "platform/core") */ project: string; /** URL to open in browser (GitLab link) */ url: string; /** Entity IID (e.g., MR !847, Issue #42) */ iid: number; /** ISO timestamp of last update */ updatedAt: string | null; /** Optional context quote (e.g., reviewer comment) */ contextQuote: string | null; /** Who is requesting attention */ requestedBy: string | null; /** ISO timestamp when snooze expires (item hidden until then) */ snoozedUntil: string | null; } /** Action the user takes on a focused item */ export type FocusAction = | "start" | "defer_1h" | "defer_3h" | "defer_tomorrow" | "defer_next_week" | "skip"; /** An entry in the decision log */ export interface DecisionEntry { timestamp: string; action: FocusAction; itemId: string; reason: string | null; } /** Staleness level derived from item age */ export type Staleness = "fresh" | "normal" | "amber" | "urgent"; // -- Inbox types -- /** Type of work item in the inbox */ export type InboxItemType = "mention" | "mr_feedback" | "review_request" | "assignment" | "manual"; /** A work item awaiting triage in the inbox */ export interface InboxItem { /** Unique identifier */ id: string; /** Human-readable title */ title: string; /** Type of inbox item */ type: InboxItemType; /** Whether this item has been triaged */ triaged: boolean; /** When the item was created/arrived */ createdAt: string; /** Optional snippet/preview */ snippet?: string; /** Source project */ project?: string; /** Web URL for opening in browser */ url?: string; /** Who triggered this item (e.g., commenter name) */ actor?: string; /** Whether this item has been archived */ archived?: boolean; /** ISO timestamp when snooze expires (item hidden until then) */ snoozedUntil?: string; } /** Triage action the user can take on an inbox item */ export type TriageAction = "accept" | "defer" | "archive"; /** Duration options for deferring an item */ export type DeferDuration = "1h" | "3h" | "tomorrow" | "next_week"; /** Compute staleness from an ISO timestamp */ export function computeStaleness(updatedAt: string | null): Staleness { if (!updatedAt) return "normal"; const ageMs = Date.now() - new Date(updatedAt).getTime(); // Guard against invalid date strings (NaN propagates through arithmetic) if (Number.isNaN(ageMs)) return "normal"; const ageDays = ageMs / (1000 * 60 * 60 * 24); if (ageDays < 1) return "fresh"; if (ageDays < 3) return "normal"; if (ageDays < 7) return "amber"; return "urgent"; }