- Add InboxItem types and TriageAction/DeferDuration types - Create Inbox component with: - Accept/Defer/Archive actions for each item - Keyboard shortcuts (A/D/X) for fast triage - Defer duration picker popup - Inbox Zero celebration state - Type-specific badges with colors - Add comprehensive tests for all functionality Closes bd-qvc Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
166 lines
4.3 KiB
TypeScript
166 lines
4.3 KiB
TypeScript
/**
|
|
* TypeScript types mirroring the Rust backend data structures.
|
|
*
|
|
* These are used by the IPC layer and components to maintain
|
|
* type safety across the Tauri boundary.
|
|
*/
|
|
|
|
// -- Backend response types (match Rust structs in commands/mod.rs) --
|
|
|
|
export interface LoreStatus {
|
|
last_sync: string | null;
|
|
is_healthy: boolean;
|
|
message: string;
|
|
summary: LoreSummaryStatus | null;
|
|
}
|
|
|
|
export interface LoreSummaryStatus {
|
|
open_issues: number;
|
|
authored_mrs: number;
|
|
reviewing_mrs: number;
|
|
}
|
|
|
|
export interface BridgeStatus {
|
|
mapping_count: number;
|
|
pending_count: number;
|
|
suspect_count: number;
|
|
last_sync: string | null;
|
|
last_reconciliation: string | null;
|
|
}
|
|
|
|
export interface SyncResult {
|
|
created: number;
|
|
closed: number;
|
|
skipped: number;
|
|
/** Number of suspect_orphan flags cleared (item reappeared) */
|
|
healed: number;
|
|
/** Error messages from non-fatal errors during sync */
|
|
errors: string[];
|
|
}
|
|
|
|
// -- Structured error types (match Rust error.rs) --
|
|
|
|
/** Error codes for programmatic handling */
|
|
export type McErrorCode =
|
|
| "LORE_UNAVAILABLE"
|
|
| "LORE_UNHEALTHY"
|
|
| "LORE_FETCH_FAILED"
|
|
| "BRIDGE_LOCKED"
|
|
| "BRIDGE_MAP_CORRUPTED"
|
|
| "BRIDGE_SYNC_FAILED"
|
|
| "BEADS_UNAVAILABLE"
|
|
| "BEADS_CREATE_FAILED"
|
|
| "BEADS_CLOSE_FAILED"
|
|
| "IO_ERROR"
|
|
| "INTERNAL_ERROR";
|
|
|
|
/** Structured error from Tauri IPC commands */
|
|
export interface McError {
|
|
code: McErrorCode;
|
|
message: string;
|
|
recoverable: boolean;
|
|
}
|
|
|
|
/** Type guard to check if an error is a structured McError */
|
|
export function isMcError(err: unknown): err is McError {
|
|
return (
|
|
typeof err === "object" &&
|
|
err !== null &&
|
|
"code" in err &&
|
|
"message" in err &&
|
|
"recoverable" in err
|
|
);
|
|
}
|
|
|
|
/** Result from the quick_capture command */
|
|
export interface CaptureResult {
|
|
bead_id: string;
|
|
}
|
|
|
|
// -- 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;
|
|
}
|
|
|
|
/** Action the user takes on a focused item */
|
|
export type FocusAction = "start" | "defer_1h" | "defer_tomorrow" | "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;
|
|
}
|
|
|
|
/** 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();
|
|
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
|
|
if (ageDays < 1) return "fresh";
|
|
if (ageDays < 3) return "normal";
|
|
if (ageDays < 7) return "amber";
|
|
return "urgent";
|
|
}
|