feat(FN-4076): tighten mobile agents header and popup, add compact planning

Merges FN-4076: tightens the mobile Agents header and aligns the controls popup on smaller screens, while also introducing compact planning breakdown task creation with preserved payload coverage — tests for scoped and empty-generated planning payloads were added alongside fixes to the legacy API an

Fusion-Task-Id: FN-4076
This commit is contained in:
Fusion
2026-05-12 03:54:10 -07:00
committed by gsxdsm
parent 0eae54d9b2
commit ca0cb2b52e
14 changed files with 959 additions and 202 deletions

View File

@@ -16,6 +16,7 @@ import type {
PlanningQuestion,
PlanningSummary,
PlanningResponse,
TaskPriority,
TaskStore,
NtfyNotificationEvent,
} from "@fusion/core";
@@ -2234,6 +2235,15 @@ function buildPlanningSubtaskDescription(input: {
return `${input.taskGuidance}\n\n${contextSections.join("\n\n")}`;
}
export interface PlanningSubtaskDraft {
id: string;
title?: string;
description?: string;
suggestedSize?: "S" | "M" | "L";
priority?: TaskPriority;
dependsOn?: string[];
}
export function generateSubtasksFromPlanning(sessionId: string): SubtaskItem[] {
const session = sessions.get(sessionId);
if (!session) return [];
@@ -2303,6 +2313,51 @@ export function generateSubtasksFromPlanning(sessionId: string): SubtaskItem[] {
];
}
export function mergePlanningSubtaskDrafts(
sessionId: string,
drafts: PlanningSubtaskDraft[],
): SubtaskItem[] {
const generatedSubtasks = generateSubtasksFromPlanning(sessionId);
const generatedById = new Map(generatedSubtasks.map((subtask) => [subtask.id, subtask]));
return drafts.map((draft) => {
const generated = generatedById.get(draft.id);
const normalizedDependsOn = Array.isArray(draft.dependsOn)
? draft.dependsOn.filter((dependency): dependency is string => typeof dependency === "string")
: undefined;
if (!generated) {
const title = typeof draft.title === "string" ? draft.title.trim() : "";
if (!title) {
throw new Error(`Client-added subtask must have a title: ${draft.id}`);
}
const description = typeof draft.description === "string" ? draft.description : title;
return {
id: draft.id,
title,
description,
suggestedSize: draft.suggestedSize === "S" || draft.suggestedSize === "M" || draft.suggestedSize === "L"
? draft.suggestedSize
: "M",
priority: draft.priority ?? DEFAULT_TASK_PRIORITY,
dependsOn: normalizedDependsOn ?? [],
};
}
return {
id: generated.id,
title: typeof draft.title === "string" ? draft.title : generated.title,
description: typeof draft.description === "string" ? draft.description : generated.description,
suggestedSize: draft.suggestedSize === "S" || draft.suggestedSize === "M" || draft.suggestedSize === "L"
? draft.suggestedSize
: generated.suggestedSize,
priority: draft.priority ?? generated.priority ?? DEFAULT_TASK_PRIORITY,
dependsOn: normalizedDependsOn ?? generated.dependsOn,
};
});
}
/**
* Cleanup a session (used after task creation).
*/