diff --git a/packages/core/src/tasks/workspace-lease-types.ts b/packages/core/src/tasks/workspace-lease-types.ts index 69539cc3fe..610de1611b 100644 --- a/packages/core/src/tasks/workspace-lease-types.ts +++ b/packages/core/src/tasks/workspace-lease-types.ts @@ -18,6 +18,12 @@ export interface WorkspaceLandIntent { taskId: string; repoRelPath: string; remo * FNXC:Workspace 2026-08-15-08:23: * Store reclaim and workspace self-healing share this deliberately narrow * terminal rule so either cannot reclaim a task the other considers live. + * + * DELIBERATE-LITERAL — narrow terminal-owner rule (FN-9059). A lease-owner row is + * read without its workflow context, so resolving the complete lane per-workflow + * here would let a resolver failure make a live owner read as terminal and allow a + * competing reclaim. The legacy `done` literal is the intentionally conservative + * shared floor for both reclaim paths. */ export function isTerminalWorkspaceLeaseOwner(row: Pick | null | undefined): boolean { return row != null && (row.column === "done" || row.status === "failed"); diff --git a/packages/dashboard/app/components/MailboxModal.tsx b/packages/dashboard/app/components/MailboxModal.tsx index bdf53191fe..736482e2c6 100644 --- a/packages/dashboard/app/components/MailboxModal.tsx +++ b/packages/dashboard/app/components/MailboxModal.tsx @@ -57,6 +57,19 @@ import { getRelativeTimeBucket } from "../utils/relativeTimeAgo"; type MailboxTab = "inbox" | "outbox" | "archived" | "agents"; +/* +FNXC:LifecycleColumnCensus 2026-08-13-21:58: +DELIBERATE-LITERAL — mailbox folder tab, not a board column. + +FN-9014 named a folder `archived`. The tab comparison is that folder switch. Converting it to +resolveLifecycleColumns would ask a workflow which lane a mailbox folder is in. Keep the +comparison inside this helper so a real board guard that happens to use the name `activeTab` +still counts in the census. +*/ +function isMailboxArchivedTab(tab: MailboxTab): boolean { + return tab === "archived"; +} + const ALL_AGENTS_MAILBOX_ID = "__all_agents__"; interface MailboxModalProps { @@ -488,7 +501,7 @@ export function MailboxModal({ if (!isOpen) return; if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); }, [isOpen, activeTab, loadInbox, loadOutbox, loadArchivedInbox]); // Load agent mailbox when selected @@ -701,7 +714,7 @@ export function MailboxModal({ handleCloseMessage(); if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox(); else if (selectedAgentId) loadAgentMailbox(selectedAgentId); void refreshUnreadCount(); @@ -905,7 +918,7 @@ export function MailboxModal({ {t("mailbox.outboxTab", "Outbox")} - + )} diff --git a/packages/dashboard/app/components/MailboxView.tsx b/packages/dashboard/app/components/MailboxView.tsx index 57e2d3bedf..6abb501b22 100644 --- a/packages/dashboard/app/components/MailboxView.tsx +++ b/packages/dashboard/app/components/MailboxView.tsx @@ -62,6 +62,19 @@ import { getRelativeTimeBucket } from "../utils/relativeTimeAgo"; type MailboxTab = "inbox" | "outbox" | "archived" | "agents" | "approvals"; +/* +FNXC:LifecycleColumnCensus 2026-08-13-21:58: +DELIBERATE-LITERAL — mailbox folder tab, not a board column. + +FN-9014 named a folder `archived`. The tab comparison is that folder switch. Converting it to +resolveLifecycleColumns would ask a workflow which lane a mailbox folder is in. Keep the +comparison inside this helper so a real board guard that happens to use the name `activeTab` +still counts in the census. +*/ +function isMailboxArchivedTab(tab: MailboxTab): boolean { + return tab === "archived"; +} + interface MailboxViewProps { projectId?: string; addToast?: (msg: string, type?: "success" | "error") => void; @@ -573,7 +586,7 @@ export function MailboxView({ useEffect(() => { if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (activeTab === "agents") loadAgents(); else if (activeTab === "approvals") { void loadApprovals(approvalSubTab); @@ -783,7 +796,7 @@ export function MailboxView({ try { await archiveMessage(id, projectId); dismissMessage(); - if (activeTab === "archived") loadArchivedInbox(); + if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (activeTab === "outbox") loadOutbox(); else if (activeTab === "inbox") loadInbox(); else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox(); @@ -812,7 +825,7 @@ export function MailboxView({ // Refresh current tab if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox(); else if (selectedAgentId) loadAgentMailbox(selectedAgentId); addToast?.("Message deleted", "success"); @@ -1134,7 +1147,7 @@ export function MailboxView({ const renderListPane = () => ( <> - {activeTab === "archived" && ( + {isMailboxArchivedTab(activeTab) && (
{isLoading && !archivedInbox && } {archivedInbox?.messages.length === 0 &&
No archived messages
} @@ -1576,7 +1589,7 @@ export function MailboxView({ onClick={() => { if (activeTab === "inbox") loadInbox(); else if (activeTab === "outbox") loadOutbox(); - else if (activeTab === "archived") loadArchivedInbox(); + else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox(); else if (activeTab === "approvals") loadApprovals(approvalSubTab); else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox(); else if (selectedAgentId) loadAgentMailbox(selectedAgentId); @@ -1610,7 +1623,7 @@ export function MailboxView({ {t("mailbox.outbox", "Outbox")} - +