Address PR review feedback (#1694)

- TaskChatTab: add FNXC:TaskDetailChat prefix/date to the ephemeral
  active-session comment, matching the file's comment convention.
- useModalManager: clear stale planning/subtask resume-session id (and
  initial plan) when opening a fresh planning/subtask flow, so the modal
  no longer reopens into a prior session.
- boardWorkflowsCache: validate cached workflow item shape (id/name/
  columns) and taskWorkflowIds value types so a malformed cache entry
  can't pass and later crash Board on render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-20 20:25:44 -07:00
parent 7b85fead54
commit 2e6cfa22ff
3 changed files with 28 additions and 1 deletions

View File

@@ -177,6 +177,7 @@ function isActiveAgentSession(task: Task | TaskDetail, opts: { sessionLive?: boo
const statusAllowsReviewSteering = !task.status || REVIEW_STEERABLE_STATUSES.has(task.status);
const columnAllowsSteering = (task.column === "in-progress" && statusAllowsProgressSteering)
|| (task.column === "in-review" && statusAllowsReviewSteering);
// FNXC:TaskDetailChat 2026-06-20-20:10:
// In the default ephemeral-agents mode the scheduler never writes
// `assignedAgentId`/`checkedOutBy` — those are only set when
// `ephemeralAgentsEnabled === false` (scheduler.ts). An actively-executing

View File

@@ -234,10 +234,19 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
}, []);
const openPlanning = useCallback(() => {
// FNXC:PlanningModals 2026-06-20-20:10:
// A fresh planning open must clear any resume-session id / initial plan left
// by a prior resumePlanning/openPlanningWith* flow; otherwise the modal reopens
// into the stale session or pre-fills an old plan instead of starting blank.
setPlanningResumeSessionId(undefined);
setPlanningInitialPlan(null);
setPlanningWorkflowId(undefined);
setIsPlanningOpen(true);
}, []);
const openPlanningWithInitialPlan = useCallback((initialPlan: string, workflowId?: string | null) => {
// FNXC:PlanningModals 2026-06-20-20:10: clear a stale resume-session id so the
// supplied initial plan is honored rather than being overridden by an old session.
setPlanningResumeSessionId(undefined);
setPlanningInitialPlan(initialPlan);
setPlanningWorkflowId(workflowId);
setIsPlanningOpen(true);
@@ -262,6 +271,9 @@ export function useModalManager(options: UseModalManagerOptions): ModalManager {
}, []);
const openSubtaskBreakdown = useCallback((description: string, workflowId?: string | null) => {
// FNXC:PlanningModals 2026-06-20-20:10: clear a stale subtask resume-session id
// so a new breakdown starts fresh rather than reopening a prior session.
setSubtaskResumeSessionId(undefined);
setSubtaskInitialDescription(description);
setSubtaskWorkflowId(workflowId);
setIsSubtaskOpen(true);

View File

@@ -1,4 +1,4 @@
import type { BoardWorkflowsPayload } from "../api";
import type { BoardWorkflowDefinition, BoardWorkflowsPayload } from "../api";
const BOARD_WORKFLOWS_CACHE_PREFIX = "fusion:board-workflows:";
const DEFAULT_PROJECT_CACHE_KEY = "default";
@@ -17,6 +17,20 @@ function isBoardWorkflowsPayload(value: unknown): value is BoardWorkflowsPayload
if (!Array.isArray(value.workflows)) return false;
if (typeof value.defaultWorkflowId !== "string") return false;
if (!isRecord(value.taskWorkflowIds)) return false;
// FNXC:BoardWorkflows 2026-06-20-20:10:
// Validate each cached workflow's shape and the taskWorkflowIds value types,
// not just the container types. A malformed entry (e.g. `[{}]` from a stale or
// partially-written cache) would otherwise pass and later throw in Board/ListView
// when accessing workflow.name/columns — re-introducing the legacy flash this
// cache exists to prevent. Mirrors the BoardWorkflowDefinition contract (id, name,
// columns) and the Record<string,string> taskWorkflowIds map.
if (!value.workflows.every((workflow): workflow is BoardWorkflowDefinition => (
isRecord(workflow)
&& typeof workflow.id === "string"
&& typeof workflow.name === "string"
&& Array.isArray(workflow.columns)
))) return false;
if (!Object.values(value.taskWorkflowIds).every((workflowId) => typeof workflowId === "string")) return false;
return true;
}