From 48f0353b7fc1b9a51067fa3b0b62f4a8d703351b Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 10 May 2026 12:05:59 -0700 Subject: [PATCH] feat(FN-3943): align tracking resolution to honor task-level override seman Aligned GitHub tracking resolution to resolve task-level overrides, with regression tests covering the create-flow path and documentation of the override semantics. Fusion-Task-Id: FN-3943 --- docs/missions.md | 1 + docs/task-management.md | 1 + .../app/components/MissionManager.css | 21 +++ .../app/components/MissionManager.tsx | 101 ++++++++++---- .../__tests__/MissionManager.test.tsx | 126 ++++++++++++++++++ .../src/__tests__/github-tracking.test.ts | 13 ++ .../src/__tests__/routes-tasks.test.ts | 37 +++++ packages/dashboard/src/github-tracking.ts | 26 ++-- 8 files changed, 282 insertions(+), 44 deletions(-) diff --git a/docs/missions.md b/docs/missions.md index e850e6fc7..1877f84b9 100644 --- a/docs/missions.md +++ b/docs/missions.md @@ -49,6 +49,7 @@ The dashboard supports mission planning workflows where you can: - Break work into milestones/slices/features - Associate features to executable tasks - Track progress at each layer +- Resume in-progress mission interview sessions directly from the main missions list (generating, awaiting input, or error) before a mission record is created ### Auto-Generated Assertions diff --git a/docs/task-management.md b/docs/task-management.md index 92ea2402b..0e4bd98ae 100644 --- a/docs/task-management.md +++ b/docs/task-management.md @@ -389,6 +389,7 @@ Tracking behavior is controlled per task: - `task.githubTracking.repoOverride` optionally forces a specific target repo (`owner/repo`). - In the dashboard **Task Detail** modal, eligible existing tasks (`triage`, `todo`, `in-progress`, `in-review`) always show GitHub tracking controls so tracking can be enabled, disabled, or retargeted without reopening the task in a creation flow. - Clearing the Task Detail repo override stores `null`, which reverts repo resolution to project/global defaults. +- Explicit task-level enablement is honored even when project/global GitHub tracking defaults are unset. If `enabled: true` and the repo resolves at task scope (for example via `repoOverride`), Fusion still attempts create-time tracking-issue creation. Repository resolution order: diff --git a/packages/dashboard/app/components/MissionManager.css b/packages/dashboard/app/components/MissionManager.css index 3a74d5a28..526cd826e 100644 --- a/packages/dashboard/app/components/MissionManager.css +++ b/packages/dashboard/app/components/MissionManager.css @@ -722,6 +722,27 @@ box-shadow: inset 0 0 0 1px var(--todo); } +.mission-list__item--interview { + border-style: dashed; +} + +.mission-interview-status { + background: color-mix(in srgb, var(--text-muted) 16%, transparent); + color: var(--text); +} + +.mission-interview-status--generating { + background: color-mix(in srgb, var(--color-warning) 14%, transparent); +} + +.mission-interview-status--awaiting_input { + background: color-mix(in srgb, var(--todo) 16%, transparent); +} + +.mission-interview-status--error { + background: color-mix(in srgb, var(--color-error) 12%, transparent); +} + /* Desktop split-only selected-state background keeps focus contrast in sidebar cards. */ .mission-manager__sidebar .mission-list__item--selected { background: var(--card); diff --git a/packages/dashboard/app/components/MissionManager.tsx b/packages/dashboard/app/components/MissionManager.tsx index 68cae54ce..051d01742 100644 --- a/packages/dashboard/app/components/MissionManager.tsx +++ b/packages/dashboard/app/components/MissionManager.tsx @@ -24,7 +24,6 @@ import { Activity, FileText, RefreshCw, - AlertCircle, } from "lucide-react"; import type { ToastType } from "../hooks/useToast"; import { useViewportMode } from "../hooks/useViewportMode"; @@ -174,6 +173,28 @@ const validationStateColors: Record = { }; const featureRetryBudgetMax = 3; +const missionInterviewListStatuses: ReadonlySet = new Set([ + "generating", + "awaiting_input", + "error", +]); + +function getInterviewStatusLabel(status: AiSessionSummary["status"]): string { + switch (status) { + case "generating": + return "Generating plan"; + case "awaiting_input": + return "Awaiting input"; + case "error": + return "Needs retry"; + default: + return status; + } +} + +function getInterviewActionLabel(status: AiSessionSummary["status"]): string { + return status === "error" ? "Retry interview" : "Resume interview"; +} /** Get the plan state for a milestone (derived from interviewState) */ function getMilestonePlanState(interviewState?: string): "not_started" | "planned" | "needs_update" { @@ -576,7 +597,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr fetchAiSessions(projectId).then((sessions) => { if (cancelled) return; const pending = sessions.filter( - (s) => s.type === "mission_interview" && (s.status === "awaiting_input" || s.status === "error"), + (s) => s.type === "mission_interview" && missionInterviewListStatuses.has(s.status), ); setPendingInterviewSessions(pending); }).catch((err) => { @@ -3472,6 +3493,53 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr ); }; + const handleResumeInterviewSession = (sessionId: string) => { + setLocalResumeSessionId(sessionId); + setShowInterviewModal(true); + }; + + const renderInterviewSessionItems = () => pendingInterviewSessions.map((session) => { + const isErrored = session.status === "error"; + const isGenerating = session.status === "generating"; + + return ( +
handleResumeInterviewSession(session.id)} + > +
+
+ + {session.title || "Mission interview"} +
+
+ + {getInterviewStatusLabel(session.status)} + +
+

+ {isGenerating + ? "Generating mission hierarchy from interview context." + : isErrored + ? "Interview hit an error. Retry from this list item." + : "Interview is waiting for your next response."} +

+
+
event.stopPropagation()}> + +
+
+ ); + }); + const renderMissionListItems = () => missions.map((mission) => { const m = mission; const isSelected = selectedMission?.id === m.id; @@ -3657,8 +3725,9 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr )} - {/* Mission items */} + {/* Mission and interview items */} {renderMissionListItems()} + {renderInterviewSessionItems()} {/* Edit mission form */} {editingMissionId && ( @@ -3730,32 +3799,6 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr {!isCreatingMission && (
- {pendingInterviewSessions.length > 0 && ( -
- - - {pendingInterviewSessions.length === 1 - ? `Resume "${pendingInterviewSessions[0].title}"?` - : `${pendingInterviewSessions.length} interview sessions pending`} - -
- {pendingInterviewSessions.map((s) => ( - - ))} -
-
- )} {showBottomPlanButton && (