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,97 @@
import "server-only";
import { PERMS } from "@/lib/permission-slugs";
import { authorizeHousekeeping } from "../../foundation/authorization";
import { satisfiesCapability } from "../../foundation/capability-context";
import {
anyCapability,
fail,
type HousekeepingCapabilityContext,
type HousekeepingInboxSource,
ok,
} from "../../foundation/contracts";
const capability = anyCapability(PERMS.ADMIN_DASHBOARD);
export interface OperationsAccessSummary {
readonly accessibleDomains: number;
}
export interface OperationsInboxAdapters {
accessSummary(
context: HousekeepingCapabilityContext,
signal: AbortSignal,
): Promise<OperationsAccessSummary>;
}
export function createOperationsInboxSources(
adapters: OperationsInboxAdapters,
): readonly HousekeepingInboxSource[] {
return [
{
id: "operations.access-summary",
owner: "operations",
capability,
async getItems(context, signal) {
const authorization = authorizeHousekeeping(context, capability);
if (!authorization.ok) return authorization;
try {
const summary = await adapters.accessSummary(context, signal);
const occurredAt = new Date().toISOString();
return ok(
{
availability: "available" as const,
items:
summary.accessibleDomains === 0
? [
{
sourceId: "operations.access-summary",
itemId: "limited-access",
deduplicationKey:
"operations.access-summary:limited-access",
domain: "operations" as const,
capability,
severity: "warning" as const,
priority: "high" as const,
ageMs: 0,
state: "limited-access",
occurredAt,
titleKey:
"pages.housekeeping.items.operationsLimitedAccess",
href: "/ase" as const,
freshness: "fresh" as const,
actions: [],
},
]
: [],
},
authorization.correlationId,
);
} catch {
return fail(
"DEPENDENCY_UNAVAILABLE",
"errors.housekeeping.dependencyUnavailable",
authorization.correlationId,
);
}
},
},
];
}
export const OPERATIONS_INBOX_SOURCES = createOperationsInboxSources({
async accessSummary(context, signal) {
if (signal.aborted) throw new Error("aborted operations access summary");
const { HOUSEKEEPING_MANIFESTS } = await import("../../manifests");
return {
accessibleDomains: HOUSEKEEPING_MANIFESTS.filter(
(domain) =>
domain.id !== "operations" &&
satisfiesCapability(context, domain.capability) &&
domain.routes.some((route) =>
satisfiesCapability(context, route.capability),
),
).length,
};
},
});
@@ -3,6 +3,10 @@ import {
anyCapability,
type HousekeepingDomainManifest,
} from "../../foundation/contracts";
import { OPERATIONS_INBOX_SOURCES } from "./inbox";
import { OPERATIONS_ROUTES } from "./routes";
import { OPERATIONS_SEARCH_PROVIDERS } from "./search";
import { OPERATIONS_WIDGETS } from "./widgets";
export const operationsManifest = {
id: "operations",
@@ -11,8 +15,8 @@ export const operationsManifest = {
iconId: "inbox",
canonicalHref: "/ase",
capability: anyCapability(PERMS.ADMIN_DASHBOARD),
routes: [],
searchProviders: [],
inboxSources: [],
widgets: [],
routes: OPERATIONS_ROUTES,
searchProviders: OPERATIONS_SEARCH_PROVIDERS,
inboxSources: OPERATIONS_INBOX_SOURCES,
widgets: OPERATIONS_WIDGETS,
} satisfies HousekeepingDomainManifest;
@@ -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>
);
}
@@ -0,0 +1,107 @@
import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
fail,
type HousekeepingCapabilityContext,
ok,
} from "../../foundation/contracts";
import { defaultHousekeepingPreferences } from "../../foundation/preferences/schema";
import type { HousekeepingRegistry } from "../../foundation/registry";
import { createOperationsWorkspaceQuery } from "./queries";
const capability = anyCapability(PERMS.ADMIN_DASHBOARD);
const registry: HousekeepingRegistry = {
domains: [
{
id: "operations",
labelKey: "operations",
descriptionKey: "operations.description",
iconId: "inbox",
canonicalHref: "/ase",
capability,
routes: [],
searchProviders: [],
inboxSources: [],
widgets: [],
},
],
};
const context: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: () => true,
hasAny: () => true,
hasAll: () => true,
};
describe("Operations workspace query", () => {
it("retains successful sections while mapping every dependency failure independently", async () => {
const widgets = vi.fn().mockRejectedValue(new Error("widgets down"));
const query = createOperationsWorkspaceQuery({
registry,
loadInbox: vi.fn().mockRejectedValue(new Error("inbox down")),
loadRecent: vi
.fn()
.mockResolvedValue(
fail("DEPENDENCY_UNAVAILABLE", "recent down", "recent"),
),
loadPreferences: vi
.fn()
.mockResolvedValue(fail("INTERNAL", "preferences down", "preferences")),
loadWidgets: widgets,
createCorrelationId: () => "operations-partial",
});
const result = await query(context);
expect(result.model.inbox).toMatchObject({
items: [],
errors: [{ sourceId: "operations.inbox", code: "INTERNAL" }],
});
expect(result.model.recent).toEqual([]);
expect(result.model.favorites).toEqual(defaultHousekeepingPreferences());
expect(result.model.widgets).toEqual({
widgets: [],
errors: [{ widgetId: "operations.workspace", code: "INTERNAL" }],
});
expect(result.errors.map((error) => error.section)).toEqual([
"inbox",
"recent",
"favorites",
"widgets",
]);
expect(widgets).toHaveBeenCalledWith(
registry,
defaultHousekeepingPreferences(),
context,
);
});
it("loads all four sections and passes reconciled preferences to widgets", async () => {
const preferences = {
...defaultHousekeepingPreferences(),
pinnedRouteIds: ["people.users"],
};
const loadWidgets = vi.fn().mockResolvedValue({ widgets: [], errors: [] });
const query = createOperationsWorkspaceQuery({
registry,
loadInbox: vi.fn().mockResolvedValue({
items: [],
errors: [],
correlationId: "inbox",
}),
loadRecent: vi.fn().mockResolvedValue(ok([], "recent")),
loadPreferences: vi
.fn()
.mockResolvedValue(ok(preferences, "preferences")),
loadWidgets,
});
const result = await query(context);
expect(result.errors).toEqual([]);
expect(result.model.favorites).toEqual(preferences);
expect(loadWidgets).toHaveBeenCalledWith(registry, preferences, context);
});
});
@@ -0,0 +1,211 @@
import "server-only";
import {
createCorrelationId,
fail,
type HousekeepingCapabilityContext,
type HousekeepingErrorCode,
type HousekeepingResult,
ok,
} from "../../foundation/contracts";
import type { HousekeepingInboxResponse } from "../../foundation/inbox/inbox-service";
import { loadHousekeepingInbox } from "../../foundation/inbox/inbox-service";
import { reconcilePreferences } from "../../foundation/preferences/reconcile";
import {
defaultHousekeepingPreferences,
type HousekeepingPreferences,
} from "../../foundation/preferences/schema";
import type { HousekeepingRecentItem } from "../../foundation/recent/recent-work";
import {
createHousekeepingRegistry,
type HousekeepingRegistry,
type HousekeepingWidgetLoadResult,
loadHousekeepingWidgets,
} from "../../foundation/registry";
import { HOUSEKEEPING_MANIFESTS } from "../../manifests";
export interface OperationsWorkspaceModel {
readonly inbox: HousekeepingInboxResponse;
readonly recent: readonly HousekeepingRecentItem[];
readonly favorites: HousekeepingPreferences;
readonly widgets: HousekeepingWidgetLoadResult;
}
export interface OperationsWorkspaceLoadResult {
readonly model: OperationsWorkspaceModel;
readonly errors: readonly {
readonly section: "inbox" | "recent" | "favorites" | "widgets";
readonly code: HousekeepingErrorCode;
}[];
readonly correlationId: string;
}
export interface OperationsWorkspaceDependencies {
readonly registry: HousekeepingRegistry;
readonly loadInbox: (
context: HousekeepingCapabilityContext,
) => Promise<HousekeepingInboxResponse>;
readonly loadRecent: (
context: HousekeepingCapabilityContext,
) => Promise<HousekeepingResult<readonly HousekeepingRecentItem[]>>;
readonly loadPreferences: (
context: HousekeepingCapabilityContext,
) => Promise<HousekeepingResult<HousekeepingPreferences>>;
readonly loadWidgets: (
registry: HousekeepingRegistry,
preferences: HousekeepingPreferences,
context: HousekeepingCapabilityContext,
) => Promise<HousekeepingWidgetLoadResult>;
readonly createCorrelationId?: () => string;
}
interface SectionOutcome<T> {
readonly value: T;
readonly error?: HousekeepingErrorCode;
}
function internalInbox(correlationId: string): HousekeepingInboxResponse {
return {
items: [],
errors: [{ sourceId: "operations.inbox", code: "INTERNAL" }],
correlationId,
};
}
function internalWidgets(): HousekeepingWidgetLoadResult {
return {
widgets: [],
errors: [{ widgetId: "operations.workspace", code: "INTERNAL" }],
};
}
export function createOperationsWorkspaceQuery({
registry,
loadInbox,
loadRecent,
loadPreferences,
loadWidgets,
createCorrelationId: correlationIdFactory = createCorrelationId,
}: OperationsWorkspaceDependencies) {
return async function operationsWorkspaceQuery(
context: HousekeepingCapabilityContext,
): Promise<OperationsWorkspaceLoadResult> {
const correlationId = correlationIdFactory();
const inboxPromise: Promise<SectionOutcome<HousekeepingInboxResponse>> =
loadInbox(context)
.then((value) => ({ value }))
.catch(() => ({
value: internalInbox(correlationId),
error: "INTERNAL" as const,
}));
const recentPromise: Promise<
SectionOutcome<readonly HousekeepingRecentItem[]>
> = loadRecent(context)
.then((result) =>
result.ok
? { value: result.data }
: { value: [], error: result.error.code },
)
.catch(() => ({ value: [], error: "INTERNAL" as const }));
const preferencesPromise: Promise<SectionOutcome<HousekeepingPreferences>> =
loadPreferences(context)
.then((result) =>
result.ok
? { value: result.data }
: {
value: defaultHousekeepingPreferences(),
error: result.error.code,
},
)
.catch(() => ({
value: defaultHousekeepingPreferences(),
error: "INTERNAL" as const,
}));
const widgetsPromise: Promise<
SectionOutcome<HousekeepingWidgetLoadResult>
> = preferencesPromise.then(({ value }) =>
loadWidgets(registry, value, context)
.then((widgets) => ({ value: widgets }))
.catch(() => ({
value: internalWidgets(),
error: "INTERNAL" as const,
})),
);
const [inbox, recent, favorites, widgets] = await Promise.all([
inboxPromise,
recentPromise,
preferencesPromise,
widgetsPromise,
]);
const errors = [
...(inbox.error
? [{ section: "inbox" as const, code: inbox.error }]
: []),
...(recent.error
? [{ section: "recent" as const, code: recent.error }]
: []),
...(favorites.error
? [{ section: "favorites" as const, code: favorites.error }]
: []),
...(widgets.error
? [{ section: "widgets" as const, code: widgets.error }]
: []),
];
return {
model: {
inbox: inbox.value,
recent: recent.value,
favorites: favorites.value,
widgets: widgets.value,
},
errors,
correlationId,
};
};
}
const registry = createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS);
async function loadPreferences(
context: HousekeepingCapabilityContext,
): Promise<HousekeepingResult<HousekeepingPreferences>> {
const correlationId = createCorrelationId();
try {
const { housekeepingPreferencesRepository } = await import(
"@/lib/housekeeping-preferences-repository"
);
return ok(
reconcilePreferences(
await housekeepingPreferencesRepository.read(context.actor.id),
registry,
context,
),
correlationId,
);
} catch {
return fail(
"INTERNAL",
"errors.housekeeping.preferences.read",
correlationId,
);
}
}
async function loadRecent(
context: HousekeepingCapabilityContext,
): Promise<HousekeepingResult<readonly HousekeepingRecentItem[]>> {
const { loadHousekeepingRecentWork } = await import(
"@/lib/housekeeping-recent-work"
);
return loadHousekeepingRecentWork(context);
}
export const operationsWorkspaceQuery = createOperationsWorkspaceQuery({
registry,
loadInbox: loadHousekeepingInbox,
loadRecent,
loadPreferences,
loadWidgets: loadHousekeepingWidgets,
});
@@ -0,0 +1,47 @@
import { createElement } from "react";
import { satisfiesCapability } from "../../foundation/capability-context";
import { createHousekeepingRegistry } from "../../foundation/registry";
import { HOUSEKEEPING_MANIFESTS } from "../../manifests";
import type { HousekeepingRouteHandler } from "../../route-handlers";
import { OperationsWorkspacePage } from "./pages/workspace";
import { operationsWorkspaceQuery } from "./queries";
export async function renderOperationsWorkspacePage(
input: Parameters<HousekeepingRouteHandler["render"]>[0],
) {
const registry = createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS);
const accessibleDomains = registry.domains.filter(
(domain) =>
domain.id !== "operations" &&
satisfiesCapability(input.context, domain.capability),
);
const routes = accessibleDomains.flatMap((domain) =>
domain.routes.filter((route) =>
satisfiesCapability(input.context, route.capability),
),
);
const widgetDefinitions = registry.domains
.filter((domain) => satisfiesCapability(input.context, domain.capability))
.flatMap((domain) =>
domain.widgets.filter((widget) =>
satisfiesCapability(input.context, widget.capability),
),
);
return createElement(OperationsWorkspacePage, {
result: await operationsWorkspaceQuery(input.context),
routes,
widgetDefinitions,
surface: input.surface ?? "preview",
...(input.savePreferences
? { savePreferences: input.savePreferences }
: {}),
});
}
export const OPERATIONS_ROUTE_HANDLERS: readonly HousekeepingRouteHandler[] =
Object.freeze([
Object.freeze({
routeId: "operations.workspace",
render: renderOperationsWorkspacePage,
}),
]);
@@ -0,0 +1,91 @@
import { describe, expect, it } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import type { HousekeepingCapabilityContext } from "../../foundation/contracts";
import { operationsMigrationEntries } from "../../migration/operations";
import { OPERATIONS_INBOX_SOURCES } from "./inbox";
import { operationsManifest } from "./manifest";
import { OPERATIONS_ROUTES } from "./routes";
import { OPERATIONS_SEARCH_PROVIDERS } from "./search";
import { OPERATIONS_WIDGETS } from "./widgets";
const context: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => slug === PERMS.ADMIN_DASHBOARD,
hasAny: (...slugs) => slugs.includes(PERMS.ADMIN_DASHBOARD),
hasAll: (...slugs) => slugs.every((slug) => slug === PERMS.ADMIN_DASHBOARD),
};
describe("Operations workspace registration", () => {
it("maps the one legacy dashboard row to operations.workspace at canonical /ase", () => {
expect(OPERATIONS_ROUTES).toEqual([
expect.objectContaining({
id: "operations.workspace",
href: "/ase",
capability: { mode: "any", slugs: [PERMS.ADMIN_DASHBOARD] },
}),
]);
expect(operationsMigrationEntries).toEqual([
expect.objectContaining({
legacyPath: "/admin",
targetDomain: "operations",
targetPath: OPERATIONS_ROUTES[0]?.href,
}),
]);
});
it("publishes a real route, safe navigation search, inbox summary, and mandatory widget", async () => {
expect(operationsManifest.routes).toEqual(OPERATIONS_ROUTES);
expect(operationsManifest.searchProviders).toEqual(
OPERATIONS_SEARCH_PROVIDERS,
);
expect(operationsManifest.inboxSources).toEqual(OPERATIONS_INBOX_SOURCES);
expect(operationsManifest.widgets).toEqual(OPERATIONS_WIDGETS);
const search = await OPERATIONS_SEARCH_PROVIDERS[0]?.search(context, {
term: "operations",
limit: 25,
});
expect(search).toMatchObject({
ok: true,
data: [
expect.objectContaining({
id: "operations.workspace",
type: "navigation",
href: "/ase",
}),
],
});
const inbox = await OPERATIONS_INBOX_SOURCES[0]?.getItems(
context,
new AbortController().signal,
);
expect(inbox).toMatchObject({
ok: true,
data: {
availability: "available",
items: [
expect.objectContaining({
sourceId: "operations.access-summary",
href: "/ase",
}),
],
},
});
expect(OPERATIONS_WIDGETS[0]).toMatchObject({
id: "operations.workspace-summary",
kind: "mandatory",
owner: "operations",
});
const widget = await OPERATIONS_WIDGETS[0]?.load(
context,
new AbortController().signal,
);
expect(widget).toMatchObject({
ok: true,
data: { accessibleDomains: 0, accessibleRoutes: 0 },
});
});
});
@@ -0,0 +1,14 @@
import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
type HousekeepingRouteDefinition,
} from "../../foundation/contracts";
export const OPERATIONS_ROUTES = [
{
id: "operations.workspace",
labelKey: "pages.housekeeping.routes.operations.workspace",
href: "/ase",
capability: anyCapability(PERMS.ADMIN_DASHBOARD),
},
] as const satisfies readonly HousekeepingRouteDefinition[];
@@ -0,0 +1,43 @@
import "server-only";
import { PERMS } from "@/lib/permission-slugs";
import { authorizeHousekeeping } from "../../foundation/authorization";
import {
anyCapability,
type HousekeepingSearchProvider,
ok,
} from "../../foundation/contracts";
const capability = anyCapability(PERMS.ADMIN_DASHBOARD);
export const OPERATIONS_SEARCH_PROVIDERS = [
{
id: "operations.workspace",
owner: "operations",
capability,
async search(context, input) {
const authorization = authorizeHousekeeping(context, capability);
if (!authorization.ok) return authorization;
const term = input.term.normalize("NFC").trim().toLocaleLowerCase();
const matches = ["operations", "workspace", "dashboard"].some((value) =>
value.includes(term),
);
return ok(
matches
? [
{
id: "operations.workspace",
domain: "operations" as const,
type: "navigation" as const,
title: "Operations workspace",
description: "Open the operational home",
href: "/ase" as const,
capability,
},
].slice(0, Math.max(0, Math.trunc(input.limit)))
: [],
authorization.correlationId,
);
},
},
] as const satisfies readonly HousekeepingSearchProvider[];
@@ -0,0 +1,82 @@
import "server-only";
import { PERMS } from "@/lib/permission-slugs";
import { authorizeHousekeeping } from "../../foundation/authorization";
import { satisfiesCapability } from "../../foundation/capability-context";
import {
anyCapability,
fail,
type HousekeepingCapabilityContext,
type HousekeepingWidgetDefinition,
ok,
} from "../../foundation/contracts";
const capability = anyCapability(PERMS.ADMIN_DASHBOARD);
export interface OperationsWorkspaceSummary {
readonly accessibleDomains: number;
readonly accessibleRoutes: number;
}
export interface OperationsWidgetAdapters {
workspaceSummary(
context: HousekeepingCapabilityContext,
signal: AbortSignal,
): Promise<OperationsWorkspaceSummary>;
}
export function createOperationsWidgets(
adapters: OperationsWidgetAdapters,
): readonly HousekeepingWidgetDefinition[] {
return [
{
id: "operations.workspace-summary",
owner: "operations",
capability,
kind: "mandatory",
async load(context, signal) {
const authorization = authorizeHousekeeping(context, capability);
if (!authorization.ok) return authorization;
try {
return ok(
await adapters.workspaceSummary(context, signal),
authorization.correlationId,
);
} catch {
return fail(
"DEPENDENCY_UNAVAILABLE",
"errors.housekeeping.dependencyUnavailable",
authorization.correlationId,
);
}
},
},
];
}
export const OPERATIONS_WIDGETS = createOperationsWidgets({
async workspaceSummary(context, signal) {
if (signal.aborted) throw new Error("aborted operations workspace summary");
const { HOUSEKEEPING_MANIFESTS } = await import("../../manifests");
const domains = HOUSEKEEPING_MANIFESTS.filter(
(domain) =>
domain.id !== "operations" &&
satisfiesCapability(context, domain.capability),
);
return {
accessibleDomains: domains.filter((domain) =>
domain.routes.some((route) =>
satisfiesCapability(context, route.capability),
),
).length,
accessibleRoutes: domains.reduce(
(total, domain) =>
total +
domain.routes.filter((route) =>
satisfiesCapability(context, route.capability),
).length,
0,
),
};
},
});