feat(housekeeping): personalize command deck
This commit is contained in:
1 parent
300ac0ef95
commit
5574e601bb
12 files changed
+1549
-1
No files matched your search
@@ -1,9 +1,20 @@
|
||||
import { PERMS } from "@/lib/permission-slugs";
|
||||
import { HOUSEKEEPING_DOMAIN_IDS } from "../migration/types";
|
||||
import { satisfiesCapability } from "./capability-context";
|
||||
import type {
|
||||
CapabilityRequirement,
|
||||
HousekeepingCapabilityContext,
|
||||
HousekeepingDomainManifest,
|
||||
HousekeepingErrorCode,
|
||||
HousekeepingWidgetDefinition,
|
||||
} from "./contracts";
|
||||
import { ok } from "./contracts";
|
||||
import { reconcilePreferences } from "./preferences/reconcile";
|
||||
import type { HousekeepingPreferences } from "./preferences/schema";
|
||||
import {
|
||||
type HousekeepingProvider,
|
||||
runProvider,
|
||||
} from "./providers/run-provider";
|
||||
|
||||
const approvedDomainIds = new Set<string>(HOUSEKEEPING_DOMAIN_IDS);
|
||||
const knownCapabilitySlugs = new Set<string>(Object.values(PERMS));
|
||||
@@ -17,6 +28,76 @@ export interface HousekeepingRegistry {
|
||||
domains: readonly HousekeepingDomainManifest[];
|
||||
}
|
||||
|
||||
export const WIDGET_POLICY = Object.freeze({ timeoutMs: 2_000 } as const);
|
||||
|
||||
export interface HousekeepingWidgetLoadResult {
|
||||
readonly widgets: readonly { readonly id: string; readonly data: unknown }[];
|
||||
readonly errors: readonly {
|
||||
readonly widgetId: string;
|
||||
readonly code: HousekeepingErrorCode;
|
||||
}[];
|
||||
}
|
||||
|
||||
function widgetProvider(
|
||||
widget: HousekeepingWidgetDefinition,
|
||||
): HousekeepingProvider<unknown> {
|
||||
return {
|
||||
id: widget.id,
|
||||
capability: widget.capability,
|
||||
async run(context, signal) {
|
||||
const result = await widget.load(context, signal);
|
||||
return result.ok ? ok([result.data], result.correlationId) : result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadHousekeepingWidgets(
|
||||
registry: HousekeepingRegistry,
|
||||
preferences: HousekeepingPreferences,
|
||||
context: HousekeepingCapabilityContext,
|
||||
timeoutMs: number = WIDGET_POLICY.timeoutMs,
|
||||
): Promise<HousekeepingWidgetLoadResult> {
|
||||
const reconciled = reconcilePreferences(preferences, registry, context);
|
||||
const enabledOptionalIds = new Set(reconciled.enabledOptionalWidgetIds);
|
||||
const selected = registry.domains
|
||||
.filter((domain) => satisfiesCapability(context, domain.capability))
|
||||
.flatMap((domain) => domain.widgets)
|
||||
.filter(
|
||||
(widget) =>
|
||||
satisfiesCapability(context, widget.capability) &&
|
||||
(widget.kind === "mandatory" || enabledOptionalIds.has(widget.id)),
|
||||
);
|
||||
const selectedById = new Map(selected.map((widget) => [widget.id, widget]));
|
||||
const orderedIds = reconciled.widgetOrder.filter((id) =>
|
||||
selectedById.has(id),
|
||||
);
|
||||
for (const widget of selected) {
|
||||
if (!orderedIds.includes(widget.id)) orderedIds.push(widget.id);
|
||||
}
|
||||
const ordered = orderedIds.flatMap((id) => {
|
||||
const widget = selectedById.get(id);
|
||||
return widget ? [widget] : [];
|
||||
});
|
||||
const outcomes = await Promise.all(
|
||||
ordered.map((widget) =>
|
||||
runProvider(widgetProvider(widget), context, timeoutMs),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
widgets: outcomes.flatMap((outcome) =>
|
||||
outcome.error === undefined
|
||||
? [{ id: outcome.providerId, data: outcome.items[0] }]
|
||||
: [],
|
||||
),
|
||||
errors: outcomes.flatMap((outcome) =>
|
||||
outcome.error === undefined
|
||||
? []
|
||||
: [{ widgetId: outcome.providerId, code: outcome.error }],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function createHousekeepingRegistry(
|
||||
manifests: readonly HousekeepingDomainManifest[],
|
||||
): HousekeepingRegistry {
|
||||
|
||||
Reference in new issue
Block a user