feat(housekeeping): compose operations workspace
CI / check (pull_request) Successful in 37s
CI / release (pull_request) Skipped
CI / deploy (pull_request) Skipped

This commit is contained in:
Simo committed 2026-08-30 17:23:55 +02:00
1 parent 5574e601bb
commit 85ad0452d3
22 files changed
+1240 -87

No files matched your search

@@ -0,0 +1,78 @@
"use client";
import type {
HousekeepingRouteDefinition,
HousekeepingSurface,
HousekeepingWidgetDefinition,
} from "../../../foundation/contracts";
import type { HousekeepingPreferences } from "../../../foundation/preferences/schema";
import type { HousekeepingWidgetLoadResult } from "../../../foundation/registry";
import { Favorites } from "../../../foundation/shell/favorites";
import { WidgetGrid } from "../../../foundation/shell/widget-grid";
import {
type HousekeepingPreferenceSaver,
WidgetSettings,
} from "../../../foundation/shell/widget-settings";
const persistPreferences: HousekeepingPreferenceSaver = async (preferences) => {
const { saveHousekeepingPreferences } = await import(
"@/actions/housekeeping-preferences"
);
return saveHousekeepingPreferences(preferences);
};
export function OperationsPersonalization({
preferences,
routes,
widgets,
widgetResult,
surface,
savePreferences = persistPreferences,
}: {
readonly preferences: HousekeepingPreferences;
readonly routes: readonly HousekeepingRouteDefinition[];
readonly widgets: readonly HousekeepingWidgetDefinition[];
readonly widgetResult: HousekeepingWidgetLoadResult;
readonly surface: HousekeepingSurface;
readonly savePreferences?: HousekeepingPreferenceSaver;
}) {
return (
<>
<div className="grid gap-6 xl:grid-cols-2">
<Favorites
preferences={preferences}
routes={routes}
surface={surface}
savePreferences={savePreferences}
labels={{
title: "Favorites",
pin: "Pin",
unpin: "Unpin",
error: "Preferences could not be saved",
}}
/>
<WidgetSettings
preferences={preferences}
widgets={widgets}
savePreferences={savePreferences}
labels={{
title: "Widget settings",
mandatory: "Mandatory",
error: "Preferences could not be saved",
}}
/>
</div>
<WidgetGrid
preferences={preferences}
result={widgetResult}
savePreferences={savePreferences}
labels={{
title: "Widgets",
partial: "Some widgets are unavailable",
error: "Widget order could not be saved",
}}
/>
</>
);
}
@@ -0,0 +1,118 @@
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
type HousekeepingRouteDefinition,
type HousekeepingWidgetDefinition,
ok,
} from "../../../foundation/contracts";
import { defaultHousekeepingPreferences } from "../../../foundation/preferences/schema";
import type { OperationsWorkspaceLoadResult } from "../queries";
import { OperationsWorkspacePage } from "./workspace";
const capability = anyCapability(PERMS.USERS_VIEW);
const routes: readonly HousekeepingRouteDefinition[] = [
{
id: "people.users",
labelKey: "Users",
href: "/ase/people/users",
capability,
},
];
const widgets: readonly HousekeepingWidgetDefinition[] = [
{
id: "people.queue",
owner: "people",
capability,
kind: "mandatory",
load: async () => ok({}, "widget"),
},
];
const preferences = {
...defaultHousekeepingPreferences(),
pinnedRouteIds: ["people.users"],
shortcutOrder: ["people.users"],
widgetOrder: ["people.queue"],
};
const result: OperationsWorkspaceLoadResult = {
model: {
inbox: {
items: [
{
sourceId: "people.queue",
itemId: "ticket-1",
deduplicationKey: "people.queue:ticket-1",
domain: "people",
capability,
severity: "warning",
priority: "high",
ageMs: 1_000,
state: "open",
occurredAt: "2026-08-30T10:00:00.000Z",
titleKey: "Ticket",
href: "/ase/people/users",
freshness: "fresh",
actions: [],
},
],
errors: [{ sourceId: "system.alerts", code: "TIMEOUT" }],
correlationId: "inbox",
},
recent: [
{
routeId: "people.users",
canonicalHref: "/ase/people/users",
labelKey: "Users",
occurredAt: "2026-08-30T09:00:00.000Z",
source: "route-visit",
},
],
favorites: preferences,
widgets: {
widgets: [{ id: "people.queue", data: { count: 1 } }],
errors: [{ widgetId: "system.health", code: "TIMEOUT" }],
},
},
errors: [{ section: "recent", code: "DEPENDENCY_UNAVAILABLE" }],
correlationId: "workspace",
};
const savePreferences = vi.fn(async (value) => ok(value, "saved"));
describe("OperationsWorkspacePage", () => {
it("renders capability-filtered sections, partial failures, and preview links", () => {
const html = renderToStaticMarkup(
<OperationsWorkspacePage
result={result}
routes={routes}
widgetDefinitions={widgets}
surface="preview"
savePreferences={savePreferences}
/>,
);
expect(html).toContain("Operations workspace");
expect(html).toContain("Operational inbox");
expect(html).toContain("Recent work");
expect(html).toContain("Favorites");
expect(html).toContain("Widget settings");
expect(html).toContain("Some workspace sections are unavailable");
expect(html).toContain("system.alerts");
expect(html).toContain("system.health");
expect(html).toContain('href="/ase-next/people/users"');
});
it("renders an explicit state when no operational domain is accessible", () => {
const html = renderToStaticMarkup(
<OperationsWorkspacePage
result={result}
routes={[]}
widgetDefinitions={[]}
surface="preview"
savePreferences={savePreferences}
/>,
);
expect(html).toContain("No operational domains are available");
expect(html).not.toContain("Operational inbox");
});
});
@@ -0,0 +1,106 @@
import type {
HousekeepingRouteDefinition,
HousekeepingSurface,
HousekeepingWidgetDefinition,
} from "../../../foundation/contracts";
import { HousekeepingPageShell } from "../../../foundation/page/housekeeping-page-shell";
import { OperationalInbox } from "../../../foundation/shell/operational-inbox";
import { RecentWork } from "../../../foundation/shell/recent-work";
import type { HousekeepingPreferenceSaver } from "../../../foundation/shell/widget-settings";
import type { OperationsWorkspaceLoadResult } from "../queries";
import { OperationsPersonalization } from "./personalization";
export function OperationsWorkspacePage({
result,
routes,
widgetDefinitions,
surface,
savePreferences,
}: {
readonly result: OperationsWorkspaceLoadResult;
readonly routes: readonly HousekeepingRouteDefinition[];
readonly widgetDefinitions: readonly HousekeepingWidgetDefinition[];
readonly surface: HousekeepingSurface;
readonly savePreferences?: HousekeepingPreferenceSaver;
}) {
if (routes.length === 0) {
return (
<HousekeepingPageShell
title="Operations workspace"
description="Your capability-derived operational home."
>
<div className="rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4">
<h2 className="font-semibold">
No operational domains are available
</h2>
<p className="mt-1 text-sm text-[var(--admin-text-muted)]">
Your account can open Housekeeping, but no operational domain is
currently authorized.
</p>
</div>
</HousekeepingPageShell>
);
}
return (
<HousekeepingPageShell
title="Operations workspace"
description="Inbox, recent work, favorites, and widgets for the domains you can operate."
>
<div className="space-y-6">
{result.errors.length > 0 ? (
<div
role="status"
className="rounded-md border border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)] p-3"
>
<p className="text-[var(--admin-warning)]">
Some workspace sections are unavailable
</p>
<ul className="mt-2 list-disc pl-5 text-sm">
{result.errors.map((error) => (
<li key={error.section}>
{error.section}: {error.code}
</li>
))}
</ul>
</div>
) : null}
<div className="grid gap-6 xl:grid-cols-2">
<OperationalInbox
response={result.model.inbox}
surface={surface}
labels={{
title: "Operational inbox",
domainFilter: "Filter by domain",
stateFilter: "Filter by state",
priorityFilter: "Filter by priority",
all: "All",
loading: "Loading operational inbox",
empty: "No operational work is waiting",
partial: "Some inbox sources are unavailable",
open: "Open",
}}
/>
<RecentWork
items={result.model.recent}
surface={surface}
labels={{
title: "Recent work",
empty: "No recent Housekeeping work",
}}
/>
</div>
<OperationsPersonalization
preferences={result.model.favorites}
routes={routes}
widgets={widgetDefinitions}
widgetResult={result.model.widgets}
surface={surface}
{...(savePreferences ? { savePreferences } : {})}
/>
</div>
</HousekeepingPageShell>
);
}