fix(housekeeping): serialize workspace client props

This commit is contained in:
Simo committed 2026-08-30 19:38:19 +02:00
1 parent 8a31556d51
commit cb77b2d3b9
7 files changed
+59 -15

No files matched your search

@@ -3,7 +3,6 @@
import type {
HousekeepingRouteDefinition,
HousekeepingSurface,
HousekeepingWidgetDefinition,
} from "../../../foundation/contracts";
import type { HousekeepingPreferences } from "../../../foundation/preferences/schema";
import type { HousekeepingWidgetLoadResult } from "../../../foundation/registry";
@@ -13,6 +12,7 @@ import {
type HousekeepingPreferenceSaver,
WidgetSettings,
} from "../../../foundation/shell/widget-settings";
import type { OperationsWidgetOption } from "../personalization-model";
export interface OperationsPersonalizationLabels {
readonly favorites: {
@@ -55,7 +55,7 @@ export function OperationsPersonalization({
}: {
readonly preferences: HousekeepingPreferences;
readonly routes: readonly HousekeepingRouteDefinition[];
readonly widgets: readonly HousekeepingWidgetDefinition[];
readonly widgets: readonly OperationsWidgetOption[];
readonly widgetResult: HousekeepingWidgetLoadResult;
readonly surface: HousekeepingSurface;
readonly labels: OperationsPersonalizationLabels;
@@ -4,10 +4,10 @@ import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
type HousekeepingRouteDefinition,
type HousekeepingWidgetDefinition,
ok,
} from "../../../foundation/contracts";
import { defaultHousekeepingPreferences } from "../../../foundation/preferences/schema";
import type { OperationsWidgetOption } from "../personalization-model";
import type { OperationsWorkspaceLoadResult } from "../queries";
import { OperationsWorkspacePage } from "./workspace";
@@ -20,13 +20,10 @@ const routes: readonly HousekeepingRouteDefinition[] = [
capability,
},
];
const widgets: readonly HousekeepingWidgetDefinition[] = [
const widgets: readonly OperationsWidgetOption[] = [
{
id: "people.queue",
owner: "people",
capability,
kind: "mandatory",
load: async () => ok({}, "widget"),
},
];
const preferences = {
@@ -1,12 +1,12 @@
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 { OperationsWidgetOption } from "../personalization-model";
import type { OperationsWorkspaceLoadResult } from "../queries";
import {
OperationsPersonalization,
@@ -50,7 +50,7 @@ export function OperationsWorkspacePage({
}: {
readonly result: OperationsWorkspaceLoadResult;
readonly routes: readonly HousekeepingRouteDefinition[];
readonly widgetDefinitions: readonly HousekeepingWidgetDefinition[];
readonly widgetDefinitions: readonly OperationsWidgetOption[];
readonly surface: HousekeepingSurface;
readonly labels: OperationsWorkspaceLabels;
readonly routeTitles: Readonly<Record<string, string>>;
@@ -0,0 +1,29 @@
import { describe, expect, it, vi } from "vitest";
import {
anyCapability,
type HousekeepingWidgetDefinition,
ok,
} from "../../foundation/contracts";
import { toOperationsWidgetOptions } from "./personalization-model";
describe("toOperationsWidgetOptions", () => {
it("projects provider definitions to a serializable client view model", () => {
const load = vi.fn(async () => ok({ count: 1 }, "widget"));
const definitions: readonly HousekeepingWidgetDefinition[] = [
{
id: "people.queue",
owner: "people",
capability: anyCapability("admin.users.view"),
kind: "mandatory",
load,
},
];
const options = toOperationsWidgetOptions(definitions);
expect(options).toEqual([{ id: "people.queue", kind: "mandatory" }]);
expect(structuredClone(options)).toEqual(options);
expect(options[0]).not.toHaveProperty("load");
expect(load).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,15 @@
import type {
HousekeepingWidgetDefinition,
HousekeepingWidgetKind,
} from "../../foundation/contracts";
export interface OperationsWidgetOption {
readonly id: string;
readonly kind: HousekeepingWidgetKind;
}
export function toOperationsWidgetOptions(
definitions: readonly HousekeepingWidgetDefinition[],
): readonly OperationsWidgetOption[] {
return definitions.map(({ id, kind }) => ({ id, kind }));
}
@@ -7,6 +7,7 @@ import {
type OperationsWorkspaceLabels,
OperationsWorkspacePage,
} from "./pages/workspace";
import { toOperationsWidgetOptions } from "./personalization-model";
import { operationsWorkspaceQuery } from "./queries";
export async function renderOperationsWorkspacePage(
@@ -138,7 +139,7 @@ export async function renderOperationsWorkspacePage(
return createElement(OperationsWorkspacePage, {
result: workspaceResult,
routes,
widgetDefinitions,
widgetDefinitions: toOperationsWidgetOptions(widgetDefinitions),
surface: input.surface ?? "preview",
labels,
routeTitles,
@@ -1,10 +1,7 @@
"use client";
import { useState } from "react";
import type {
HousekeepingResult,
HousekeepingWidgetDefinition,
} from "../contracts";
import type { HousekeepingResult, HousekeepingWidgetKind } from "../contracts";
import type { HousekeepingPreferences } from "../preferences/schema";
export type HousekeepingPreferenceSaver = (
@@ -44,6 +41,11 @@ export interface WidgetSettingsLabels {
readonly error: string;
}
export interface HousekeepingWidgetOption {
readonly id: string;
readonly kind: HousekeepingWidgetKind;
}
export function WidgetSettings({
preferences: initialPreferences,
widgets,
@@ -52,7 +54,7 @@ export function WidgetSettings({
titles = {},
}: {
readonly preferences: HousekeepingPreferences;
readonly widgets: readonly HousekeepingWidgetDefinition[];
readonly widgets: readonly HousekeepingWidgetOption[];
readonly savePreferences: HousekeepingPreferenceSaver;
readonly labels: WidgetSettingsLabels;
readonly titles?: Readonly<Record<string, string>>;