Files
fusion/packages/dashboard/app/components/MailboxRelatedWorkLink.tsx
gsxdsm b621f29747 FN-8428: add mailbox links to related work
Add direct mailbox navigation to related tasks and planning sessions.

- Extend mailbox message metadata with task and planning-session references.
- Render accessible related-work actions in mailbox views and avoid duplicate artifact task links.
- Route planning clarification actions to the associated Planning session and cover the flows with tests.

Files changed:
 packages/core/src/types/messages.ts                |  6 +++
 .../app/components/MailboxArtifactAttachment.tsx   |  5 +-
 packages/dashboard/app/components/MailboxModal.tsx | 15 ++++++
 .../app/components/MailboxRelatedWorkLink.tsx      | 63 ++++++++++++++++++++++
 packages/dashboard/app/components/MailboxView.tsx  | 15 ++++++
 .../app/components/__tests__/MailboxModal.test.tsx | 31 +++++++++++
 .../__tests__/MailboxRelatedWorkLink.test.tsx      | 54 +++++++++++++++++++
 .../app/components/__tests__/MailboxView.test.tsx  | 41 ++++++++++++--
 .../app/components/dashboard/MainContent.tsx       | 10 ++++
 .../MainContent.mailbox-view-task.test.tsx         | 13 +++++
 .../__tests__/planning-infinite-interview.test.ts  | 35 ++++++++++++
 packages/dashboard/src/planning.ts                 |  5 ++
 12 files changed, 288 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-8428

Fusion-Task-Lineage: d0c6bf0e-7fb4-4bff-8cb2-3c75045a43da

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-20 09:41:02 -07:00

64 lines
2.1 KiB
TypeScript

import { useTranslation } from "react-i18next";
import type { MessageMetadata } from "@fusion/core";
export interface MailboxRelatedWorkLinkProps {
metadata?: MessageMetadata;
onOpenTask?: (taskId: string) => void;
onOpenPlanningSession?: (sessionId: string) => void;
}
function readNonEmptyString(value: unknown): string | undefined {
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
}
export function hasRelatedTaskLink(metadata: MessageMetadata | undefined, onOpenTask: MailboxRelatedWorkLinkProps["onOpenTask"]): boolean {
return Boolean(readNonEmptyString(metadata?.taskId) && onOpenTask);
}
/**
* FNXC:MailboxRelatedWork 2026-07-20-09:30:
* FN-8428 requires every response-needed mailbox message to provide a first-class route back to
* its task or planning session. Task metadata wins when both targets are present, and missing
* metadata or navigation handlers deliberately render no button so the detail never contains a
* dead affordance.
*/
export function MailboxRelatedWorkLink({
metadata,
onOpenTask,
onOpenPlanningSession,
}: MailboxRelatedWorkLinkProps) {
const { t } = useTranslation("app");
const taskId = readNonEmptyString(metadata?.taskId);
const sessionId = readNonEmptyString(metadata?.sessionId);
if (taskId && onOpenTask) {
return (
<button
type="button"
className="btn mailbox-related-work-link"
aria-label={t("mailbox.viewTaskAria", "View task: {{taskId}}", { taskId })}
data-testid="mailbox-view-task"
onClick={() => onOpenTask(taskId)}
>
{t("mailbox.viewTask", "View task")}
</button>
);
}
if (metadata?.kind === "planning-clarification" && sessionId && onOpenPlanningSession) {
return (
<button
type="button"
className="btn mailbox-related-work-link"
aria-label={t("mailbox.openPlanningSessionAria", "Open planning session: {{sessionId}}", { sessionId })}
data-testid="mailbox-open-planning-session"
onClick={() => onOpenPlanningSession(sessionId)}
>
{t("mailbox.openPlanningSession", "Open planning session")}
</button>
);
}
return null;
}