Files
fusion/packages/dashboard/app/utils/__tests__/appLifecycle.test.ts
gsxdsm 9152d8fa61 FN-8229: remove background AI tasks pill
Replace the redundant footer AI session control with progress and action visibility in the session notification banner.

- Delete the BackgroundTasksIndicator component, styles, footer wiring, and obsolete translations.
- Surface non-planning generating and retained error sessions through the notification banner while leaving Planning in its dedicated view.
- Update session lifecycle, localization, documentation, and regression coverage.

Files changed:
 .../fn-8229-remove-ai-background-tasks-pill.md     |   7 +
 docs/dashboard-guide.md                            |   4 +-
 packages/dashboard/README.md                       |   1 -
 packages/dashboard/app/App.tsx                     |  28 +--
 .../app/__tests__/css-hygiene-scan.test.ts         |   2 -
 .../dashboard-component-color-tokenization.test.ts |   1 -
 packages/dashboard/app/api/legacy.ts               |   2 +-
 .../app/components/BackgroundTasksIndicator.css    | 130 -------------
 .../app/components/BackgroundTasksIndicator.tsx    | 210 ---------------------
 .../dashboard/app/components/ExecutorStatusBar.css |   3 -
 .../dashboard/app/components/ExecutorStatusBar.tsx |  30 +--
 .../app/components/SessionNotificationBanner.css   |  10 +
 .../app/components/SessionNotificationBanner.tsx   |  52 ++++-
 .../app/components/SubtaskBreakdownModal.tsx       |   7 +-
 .../app/components/__tests__/App.test.tsx          |  11 +-
 .../__tests__/ExecutorStatusBar.test.tsx           |   1 -
 .../__tests__/SessionNotificationBanner.test.tsx   |  88 ++++-----
 .../components/__tests__/utility-mobile.test.tsx   | 191 +------------------
 .../app/components/dashboard/DashboardBanners.tsx  |   8 +-
 .../dashboard/__tests__/DashboardBanners.test.tsx  |  30 ++-
 .../hooks/__tests__/useBackgroundSessions.test.ts  |  21 ++-
 .../dashboard/app/hooks/useBackgroundSessions.ts   |  21 ++-
 .../app/utils/__tests__/appLifecycle.test.ts       |  61 ++----
 packages/dashboard/app/utils/appLifecycle.ts       |  16 +-
 packages/dashboard/src/routes.ts                   |   2 +-
 packages/i18n/locales/en/app.json                  |  33 +---
 packages/i18n/locales/es/app.json                  |  44 ++---
 packages/i18n/locales/fr/app.json                  |  44 ++---
 packages/i18n/locales/ko/app.json                  |  44 ++---
 packages/i18n/locales/zh-CN/app.json               |  44 ++---
 packages/i18n/locales/zh-TW/app.json               |  44 ++---
 packages/i18n/src/resources.d.ts                   |  48 ++---
 32 files changed, 316 insertions(+), 922 deletions(-)

Fusion-Task-Id: FN-8229

Fusion-Task-Lineage: a232fb04-08ff-4f0a-a440-e94545243e61

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-17 14:25:30 -07:00

222 lines
6.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { AiSessionSummary } from "../../api";
import {
buildRemoteDashboardUrl,
shouldShowSessionInBanner,
isSessionNeedingInputForBanner,
resolveDesktopShellRedirectTarget,
} from "../appLifecycle";
function makeSession(overrides: Partial<AiSessionSummary> & Pick<AiSessionSummary, "id">): AiSessionSummary {
return {
id: overrides.id,
type: overrides.type ?? "planning",
status: overrides.status ?? "generating",
title: overrides.title ?? overrides.id,
projectId: overrides.projectId ?? null,
updatedAt: overrides.updatedAt ?? "2026-04-08T00:00:00.000Z",
};
}
/*
FNXC:SessionBanner 2026-07-16-20:55:
FN-8229 replaces the footer AI pill with the banner for non-planning progress
and actionable states. Planning remains visible only through its docked view
and navigation badge, including retained error sessions.
*/
describe("shouldShowSessionInBanner", () => {
it("includes non-planning generating, needs-input, and error sessions", () => {
for (const status of ["generating", "awaiting_input", "error"] as const) {
expect(shouldShowSessionInBanner(makeSession({ id: status, type: "subtask", status }))).toBe(true);
}
});
it("excludes planning sessions at every status", () => {
for (const status of ["generating", "awaiting_input", "error"] as const) {
expect(shouldShowSessionInBanner(makeSession({ id: `planning-${status}`, type: "planning", status }))).toBe(false);
}
});
it("keeps the needs-input predicate separate from generating progress", () => {
expect(isSessionNeedingInputForBanner(makeSession({ id: "generating", type: "subtask", status: "generating" }))).toBe(false);
});
});
describe("resolveDesktopShellRedirectTarget", () => {
const remoteProfile = {
id: "remote-1",
serverUrl: "https://fusionstudio:4040",
authToken: "tok-123",
};
it("returns null for non-desktop-shell hosts", () => {
expect(
resolveDesktopShellRedirectTarget(
{
host: "web",
desktopMode: "local",
activeProfileId: null,
profiles: [],
localRuntime: { state: "running", baseUrl: "http://127.0.0.1:50123" },
},
"https://fusionstudio:4040/",
),
).toBeNull();
expect(
resolveDesktopShellRedirectTarget(
{
host: "mobile-shell",
desktopMode: "local",
activeProfileId: null,
profiles: [],
localRuntime: { state: "running", baseUrl: "http://127.0.0.1:50123" },
},
"https://fusionstudio:4040/",
),
).toBeNull();
});
it("returns null when desktopMode is undefined", () => {
expect(
resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
activeProfileId: null,
profiles: [],
},
"https://fusionstudio:4040/",
),
).toBeNull();
});
it("resolves the local runtime origin (baseUrl) when switching remote -> local", () => {
const target = resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "local",
activeProfileId: "remote-1",
profiles: [remoteProfile],
localRuntime: { state: "running", baseUrl: "http://127.0.0.1:50123", port: 50123 },
},
"https://fusionstudio:4040/",
);
expect(target).toBe("http://127.0.0.1:50123");
});
it("falls back to localhost:<port> when localRuntime has no baseUrl", () => {
const target = resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "local",
activeProfileId: null,
profiles: [],
localRuntime: { state: "running", port: 50123 },
},
"https://fusionstudio:4040/",
);
expect(target).toBe("http://localhost:50123");
});
it("returns null when the local runtime is not running", () => {
for (const state of ["stopped", "starting", "error"] as const) {
expect(
resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "local",
activeProfileId: null,
profiles: [],
localRuntime: { state, baseUrl: "http://127.0.0.1:50123" },
},
"https://fusionstudio:4040/",
),
).toBeNull();
}
expect(
resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "local",
activeProfileId: null,
profiles: [],
localRuntime: undefined,
},
"https://fusionstudio:4040/",
),
).toBeNull();
});
it("returns null when already on the local runtime origin", () => {
expect(
resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "local",
activeProfileId: null,
profiles: [],
localRuntime: { state: "running", baseUrl: "http://127.0.0.1:50123" },
},
"http://127.0.0.1:50123/",
),
).toBeNull();
});
it("resolves buildRemoteDashboardUrl(...) when switching local -> a remote profile", () => {
const target = resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "remote",
activeProfileId: "remote-1",
profiles: [remoteProfile],
localRuntime: { state: "stopped" },
},
"http://127.0.0.1:50123/",
);
expect(target).toBe(buildRemoteDashboardUrl(remoteProfile.serverUrl, remoteProfile.authToken));
});
it("returns null when already on the target remote url", () => {
const nextUrl = buildRemoteDashboardUrl(remoteProfile.serverUrl, remoteProfile.authToken);
expect(
resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "remote",
activeProfileId: "remote-1",
profiles: [remoteProfile],
},
nextUrl,
),
).toBeNull();
});
it("returns null when there is no matching/active profile", () => {
expect(
resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "remote",
activeProfileId: null,
profiles: [remoteProfile],
},
"http://127.0.0.1:50123/",
),
).toBeNull();
expect(
resolveDesktopShellRedirectTarget(
{
host: "desktop-shell",
desktopMode: "remote",
activeProfileId: "missing",
profiles: [remoteProfile],
},
"http://127.0.0.1:50123/",
),
).toBeNull();
});
});