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>
222 lines
6.5 KiB
TypeScript
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();
|
|
});
|
|
});
|