Files
fusion/packages/dashboard/app/utils/projectStorage.ts
Fusion Agent eb3eeb887a FN-074: remove task splitting and parent deletion
Remove complex-task splitting and parent-deletion behavior across core, dashboard, and engine.

- Remove subtask splitting, breakdown UI, and related API routes and lifecycle handling.
- Add the migration and prompt/documentation updates that enforce the simplified task model.
- Update affected tests and integrations for the removed behavior.

Files changed:
 .changeset/fn-074-removal.md                       |    7 +
 docs/dashboard-guide.md                            |   17 +-
 docs/gitlab-parity-inventory.md                    |    7 +-
 docs/mcp.md                                        |    6 +-
 docs/settings-reference.md                         |   16 +-
 docs/task-management.md                            |   31 +-
 docs/workflow-steps.md                             |   10 +-
 .../cli/skill/fusion/references/task-structure.md  |    2 +-
 .../cli/skill/fusion/workflows/specifications.md   |    4 +-
 packages/core/src/__tests__/agent-prompts.test.ts  |   19 +-
 .../builtin-workflow-settings-triage.test.ts       |   32 +-
 .../src/__tests__/postgres/schema-applier.test.ts  |    2 +-
 .../core/src/__tests__/prompt-overrides.test.ts    |    3 +-
 ...-deleted-observed-dispatch-side-effects.test.ts |    3 -
 .../task-deleted-outbox-consumer-backoff.test.ts   |    1 -
 packages/core/src/agents/agent-prompts.ts          |   56 +-
 packages/core/src/index.ts                         |    2 +-
 .../0062_remove_task_subtask_splitting.sql         |    2 +
 packages/core/src/postgres/schema-applier.ts       |   14 +-
 packages/core/src/postgres/schema/project.ts       |    1 -
 packages/core/src/store.ts                         |   12 +-
 packages/core/src/task-delete-attribution.ts       |   20 +-
 packages/core/src/task-delete-notice.ts            |    7 +-
 .../__tests__/lifecycle-outbox-writer.test.ts      |    4 +-
 .../core/src/task-store/archive-lifecycle-2.ts     |   13 +-
 packages/core/src/task-store/archive-lifecycle.ts  |    6 +-
 packages/core/src/task-store/lifecycle-outbox.ts   |    2 -
 packages/core/src/task-store/persistence.ts        |    2 -
 packages/core/src/task-store/serialization.ts      |    2 -
 packages/core/src/task-store/task-creation.ts      |    2 -
 .../src/task-store/task-deleted-outbox-consumer.ts |    4 -
 packages/core/src/task-store/task-mutation-ops.ts  |    2 +-
 packages/core/src/task-store/task-row-mappers.ts   |    2 +-
 packages/core/src/tasks/prompt-overrides.ts        |   35 -
 packages/core/src/types.ts                         |   12 -
 packages/core/src/types/mesh/archive-planning.ts   |    1 -
 packages/core/src/types/task/task-core.ts          |    3 -
 .../src/workflows/builtin-workflow-settings.ts     |   86 --
 packages/dashboard/app/App.tsx                     |   15 -
 .../mission-planning-modals-mobile.test.ts         |   15 -
 packages/dashboard/app/api/legacy.ts               |   18 +-
 packages/dashboard/app/api/planning/ai-text.ts     |  171 +---
 packages/dashboard/app/api/planning/planning.ts    |   46 -
 packages/dashboard/app/api/tasks/tasks.ts          |    4 +-
 packages/dashboard/app/components/AppModals.tsx    |   39 +-
 packages/dashboard/app/components/Board.tsx        |   10 +-
 packages/dashboard/app/components/Column.tsx       |    9 +-
 .../dashboard/app/components/InlineCreateCard.tsx  |   63 +-
 packages/dashboard/app/components/Lane.tsx         |    3 +-
 packages/dashboard/app/components/ListView.tsx     |   10 +-
 packages/dashboard/app/components/NewTaskModal.tsx |    8 +-
 .../dashboard/app/components/PlanningModeModal.css |  128 ---
 .../dashboard/app/components/PlanningModeModal.tsx |  566 +----------
 .../dashboard/app/components/QuickEntryBox.tsx     |   43 +-
 .../dashboard/app/components/SettingsModal.tsx     |    2 -
 .../app/components/SubtaskBreakdownModal.tsx       |  932 -----------------
 packages/dashboard/app/components/TaskCard.tsx     |    1 -
 packages/dashboard/app/components/TaskForm.tsx     |   23 +-
 .../__tests__/AgentPromptsManager.test.tsx         |    1 -
 .../app/components/__tests__/AppModals.test.tsx    |   92 +-
 .../app/components/__tests__/Board.test.tsx        |   69 +-
 .../components/__tests__/InlineCreateCard.test.tsx |  170 ----
 .../app/components/__tests__/ListView.test.tsx     |   38 -
 .../app/components/__tests__/NewTaskModal.test.tsx |   48 -
 .../components/__tests__/QuickEntryBox.test.tsx    |  293 +-----
 .../__tests__/SubtaskBreakdownModal.test.tsx       |  933 -----------------
 ...skDetail.mobile-transition.board-panel.test.tsx |    1 -
 .../__tests__/TaskDetail.swipe-back.test.tsx       |    1 -
 .../app/components/__tests__/TaskForm.test.tsx     |   67 --
 .../__tests__/WorkflowSettingsPanel.test.tsx       |   51 -
 .../app/components/__tests__/board-mobile.test.tsx |   20 -
 .../composer-settings-read-isolation.test.tsx      |   30 -
 .../components/__tests__/migratedModalFixtures.tsx |    2 -
 .../app/components/dashboard/MainContent.tsx       |    5 -
 .../dashboard/__tests__/DashboardBanners.test.tsx  |    1 -
 .../__tests__/MainContent.graph-popout.test.tsx    |    1 -
 .../dashboard/app/components/dashboard/types.ts    |    2 -
 .../app/components/workflow-setting-display.ts     |   10 -
 ...ckgroundSessions.resume-instrumentation.test.ts |    1 -
 .../hooks/__tests__/useBackgroundSessions.test.ts  |   25 +-
 .../app/hooks/__tests__/useModalManager.test.ts    |    3 -
 packages/dashboard/app/hooks/modalPersistence.ts   |   14 -
 .../dashboard/app/hooks/useBackgroundSessions.ts   |    9 +-
 packages/dashboard/app/hooks/useModalManager.ts    |   58 +-
 packages/dashboard/app/hooks/useProjectActions.ts  |    2 +-
 packages/dashboard/app/hooks/useTaskHandlers.ts    |    9 -
 packages/dashboard/app/utils/builtinPrompts.ts     |    7 -
 packages/dashboard/app/utils/projectStorage.ts     |    1 -
 .../src/__tests__/ai-session-diagnostics.test.ts   |    5 +-
 .../src/__tests__/github-tracking-state.test.ts    |   97 --
 .../src/__tests__/gitlab-delete-close.test.ts      |    6 +-
 .../gitlab-parity-inventory-documentation.test.ts  |    1 -
 .../src/__tests__/gitlab-split-close.test.ts       |  125 ---
 .../planning-flow-diagnostics-guardrail.test.ts    |    1 -
 .../src/__tests__/process-lifecycle.test.ts        |    1 -
 .../dashboard/src/__tests__/routes-auth.test.ts    |    2 -
 .../src/__tests__/routes-automation.test.ts        |    2 -
 .../dashboard/src/__tests__/routes-git.test.ts     |    2 -
 .../dashboard/src/__tests__/routes-github.test.ts  |   92 --
 .../src/__tests__/routes-planning-tracking.test.ts |  130 ---
 .../src/__tests__/routes-planning.test.ts          | 1062 +-------------------
 .../dashboard/src/__tests__/routes-system.test.ts  |    2 -
 .../dashboard/src/__tests__/routes-tasks.test.ts   |  902 -----------------
 .../shared-branch-group-entry-points.test.ts       |   93 --
 packages/dashboard/src/ai-session-diagnostics.ts   |    3 +-
 packages/dashboard/src/ai-session-store.ts         |    6 +-
 packages/dashboard/src/ai-session-timeout.ts       |    3 +-
 packages/dashboard/src/github-tracking-state.ts    |   43 +-
 packages/dashboard/src/gitlab-delete-close.ts      |    7 +-
 packages/dashboard/src/gitlab-lifecycle.ts         |    4 +-
 packages/dashboard/src/gitlab-split-close.ts       |  103 --
 packages/dashboard/src/gitlab-tracking-state.ts    |    2 +-
 packages/dashboard/src/index.ts                    |    1 -
 packages/dashboard/src/planning.ts                 |  171 ----
 packages/dashboard/src/routes.ts                   |    6 -
 ...er-planning-subtask-routes.parent-close.test.ts |  120 ---
 .../dashboard/src/routes/register-git-github.ts    |    6 -
 .../src/routes/register-planning-subtask-routes.ts |  773 +-------------
 .../src/routes/register-task-workflow-routes.ts    |    6 +-
 packages/dashboard/src/server.ts                   |    8 -
 packages/dashboard/src/subtask-breakdown.ts        |  833 ---------------
 packages/dashboard/src/triage-trait.ts             |   93 +-
 .../src/__tests__/agent-task-read-tools.test.ts    |    1 -
 .../triage-duplicate-search-regression.test.ts     |    3 -
 .../triage-split-into-subtasks-delete.test.ts      |  222 ----
 packages/engine/src/__tests__/triage.test.ts       |  519 +---------
 packages/engine/src/triage.ts                      |  239 +----
 127 files changed, 214 insertions(+), 10001 deletions(-)

Fusion-Task-Id: FN-074

Fusion-Task-Lineage: 23207799-4ec4-4ec7-bcd7-83cba4d98f53

Co-authored-by: Fusion <noreply@runfusion.ai>
2026-08-21 04:32:53 +00:00

198 lines
5.2 KiB
TypeScript

import { pruneStaleCacheEntries } from "./swrCache";
export const MAX_PERSISTED_DRAFT_BYTES = 64_000;
export const VOLATILE_DRAFT_STORAGE_KEYS = [
"kb-quick-entry-text",
"kb-inline-create-text",
"kb-planning-last-description",
"kb-mission-last-goal",
] as const;
export const GLOBAL_STORAGE_KEYS: string[] = [
"kb-dashboard-theme-mode",
"kb-dashboard-color-theme",
"kb-dashboard-view-mode",
"kb-dashboard-current-project",
"kb-dashboard-recent-projects",
"fn-agent-log-markdown",
"fn-agent-log-tool-output",
];
export const PROJECT_STORAGE_KEYS: string[] = [
"kb-dashboard-task-view",
"kb-dashboard-list-columns",
"kb-dashboard-hide-done",
"kb-dashboard-todo-hide-done",
"kb-dashboard-list-collapsed",
"kb-dashboard-selected-tasks",
"kb-dashboard-list-selected-task",
"kb-dashboard-list-sidebar-width",
"kb-dashboard-mailbox-sidebar-width",
"kb-dashboard-agents-sidebar-width",
"kb-dashboard-github-import-list-width",
"kb-dashboard-github-import-state",
"kb-quick-entry-text",
"kb-inline-create-text",
"fn-agent-view",
"kb-terminal-tabs",
"kb-planning-last-description",
"kb-planning-active-session",
"kb-subtask-last-description",
"kb-mission-last-goal",
"kb-usage-view-mode",
"kb-usage-hidden-windows",
"kb-usage-modal-size",
"kb-usage-provider-order",
"kb-chat-active-session",
"kb-dashboard-working-branch-filter",
"kb-dashboard-base-branch-filter",
"kb-capacity-risk-banner-dismissed",
"kb-github-setup-warning-missing-since",
"kb-files-line-numbers",
"kb-dashboard-dock-files-current",
"kb-dashboard-board-workflow-selection",
"fusion-plugin-dependency-graph:positions",
];
export function scopedKey(baseKey: string, projectId?: string): string {
if (typeof projectId !== "string" || projectId.length === 0) {
return baseKey;
}
return `kb:${projectId}:${baseKey}`;
}
export function getScopedItem(baseKey: string, projectId?: string): string | null {
if (typeof window === "undefined") {
return null;
}
const getItem = window.localStorage?.getItem;
if (typeof getItem !== "function") {
return null;
}
try {
return getItem.call(window.localStorage, scopedKey(baseKey, projectId));
} catch {
return null;
}
}
function getStorage(): Storage | null {
if (typeof window === "undefined") {
return null;
}
try {
return window.localStorage ?? null;
} catch {
return null;
}
}
function removeStorageKey(storage: Storage, key: string): boolean {
try {
storage.removeItem(key);
return true;
} catch {
return false;
}
}
/**
* Reclaims optional restoration state only after a scoped write hits storage quota.
* The sweep is deliberately bounded and preserves the active project's drafts.
*/
export function reclaimScopedStorageQuota(options?: { keepProjectId?: string }): number {
const storage = getStorage();
if (!storage) {
return 0;
}
let removed = 0;
try {
const keys: string[] = [];
const maxEntries = Math.min(storage.length, 1_000);
for (let index = 0; index < maxEntries; index += 1) {
const key = storage.key(index);
if (key !== null) keys.push(key);
}
for (const key of keys) {
const isOtherProjectDraft = VOLATILE_DRAFT_STORAGE_KEYS.some((draftKey) => (
key.startsWith("kb:")
&& key.endsWith(`:${draftKey}`)
&& key !== scopedKey(draftKey, options?.keepProjectId)
));
if (isOtherProjectDraft && removeStorageKey(storage, key)) {
removed += 1;
}
}
} catch {
// Storage enumeration is best-effort; stale SWR pruning can still reclaim space.
}
try {
removed += pruneStaleCacheEntries();
} catch {
// A blocked storage implementation must not make draft persistence throw.
}
return removed;
}
/*
FNXC:ProjectStorage 2026-08-20-00:43:
Issue #3477 reported `kb:<projectId>:kb-quick-entry-text` exhausting localStorage. Draft persistence is optional restoration state, so writes must never throw or saturate the origin: capped callers skip oversized values and quota failures evict, reclaim stale volatile state, then fail closed.
*/
export function setScopedItem(
baseKey: string,
value: string,
projectId?: string,
options?: { maxBytes?: number },
): boolean {
const storage = getStorage();
if (!storage || typeof storage.setItem !== "function") {
return false;
}
const key = scopedKey(baseKey, projectId);
if (typeof options?.maxBytes === "number" && new TextEncoder().encode(value).length > options.maxBytes) {
removeStorageKey(storage, key);
return false;
}
const tryWrite = (): boolean => {
try {
storage.setItem(key, value);
return true;
} catch {
return false;
}
};
if (tryWrite()) return true;
removeStorageKey(storage, key);
if (tryWrite()) return true;
reclaimScopedStorageQuota({ keepProjectId: projectId });
return tryWrite();
}
export function removeScopedItem(baseKey: string, projectId?: string): void {
if (typeof window === "undefined") {
return;
}
const removeItem = window.localStorage?.removeItem;
if (typeof removeItem !== "function") {
return;
}
try {
removeItem.call(window.localStorage, scopedKey(baseKey, projectId));
} catch {
// Storage removal is also optional restoration cleanup.
}
}