From addc9c7b1a7b6dfe4551db16741f0dc5470db783 Mon Sep 17 00:00:00 2001
From: simoleo89
Date: Tue, 25 Aug 2026 20:05:56 +0200
Subject: [PATCH] feat: build housekeeping command deck shell
---
.../page/housekeeping-page-shell.tsx | 35 ++++
.../page/housekeeping-page-state.test.tsx | 84 +++++++++
.../page/housekeeping-page-state.tsx | 47 +++++
.../foundation/shell/command-trigger.tsx | 15 ++
.../foundation/shell/context-nav.tsx | 25 +++
.../foundation/shell/domain-rail.tsx | 49 +++++
.../shell/housekeeping-shell.test.tsx | 174 ++++++++++++++++++
.../foundation/shell/housekeeping-shell.tsx | 80 ++++++++
.../foundation/shell/operator-summary.tsx | 19 ++
9 files changed, 528 insertions(+)
create mode 100644 src/features/housekeeping/foundation/page/housekeeping-page-shell.tsx
create mode 100644 src/features/housekeeping/foundation/page/housekeeping-page-state.test.tsx
create mode 100644 src/features/housekeeping/foundation/page/housekeeping-page-state.tsx
create mode 100644 src/features/housekeeping/foundation/shell/command-trigger.tsx
create mode 100644 src/features/housekeeping/foundation/shell/context-nav.tsx
create mode 100644 src/features/housekeeping/foundation/shell/domain-rail.tsx
create mode 100644 src/features/housekeeping/foundation/shell/housekeeping-shell.test.tsx
create mode 100644 src/features/housekeeping/foundation/shell/housekeeping-shell.tsx
create mode 100644 src/features/housekeeping/foundation/shell/operator-summary.tsx
diff --git a/src/features/housekeeping/foundation/page/housekeeping-page-shell.tsx b/src/features/housekeeping/foundation/page/housekeeping-page-shell.tsx
new file mode 100644
index 00000000..07a48921
--- /dev/null
+++ b/src/features/housekeeping/foundation/page/housekeeping-page-shell.tsx
@@ -0,0 +1,35 @@
+import type { ReactNode } from "react";
+
+interface HousekeepingPageShellProps {
+ title: string;
+ description: string;
+ primaryAction?: ReactNode;
+ context?: ReactNode;
+ children: ReactNode;
+}
+
+export function HousekeepingPageShell({
+ title,
+ description,
+ primaryAction,
+ context,
+ children,
+}: HousekeepingPageShellProps) {
+ return (
+
+
+
+
+ {title}
+
+
+ {description}
+
+ {context ?
{context}
: null}
+
+ {primaryAction ? {primaryAction}
: null}
+
+ {children}
+
+ );
+}
diff --git a/src/features/housekeeping/foundation/page/housekeeping-page-state.test.tsx b/src/features/housekeeping/foundation/page/housekeeping-page-state.test.tsx
new file mode 100644
index 00000000..b835b231
--- /dev/null
+++ b/src/features/housekeeping/foundation/page/housekeeping-page-state.test.tsx
@@ -0,0 +1,84 @@
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vitest";
+import { HousekeepingPageShell } from "./housekeeping-page-shell";
+import { HousekeepingPageState } from "./housekeeping-page-state";
+
+describe("HousekeepingPageState", () => {
+ it.each([
+ ["loading", "status"],
+ ["empty", "status"],
+ ["partial", "status"],
+ ["error", "alert"],
+ ] as const)("renders %s with the %s role", (state, role) => {
+ const html = renderToStaticMarkup(
+ ,
+ );
+
+ expect(html).toContain(`role="${role}"`);
+ expect(html).toContain(`>${state} title<`);
+ expect(html).toContain(`>${state} description<`);
+ });
+
+ it("announces loading politely and makes the partial warning visible", () => {
+ const loading = renderToStaticMarkup(
+ ,
+ );
+ const partial = renderToStaticMarkup(
+ ,
+ );
+
+ expect(loading).toContain('aria-live="polite"');
+ expect(partial).toContain('data-state-tone="warning"');
+ expect(partial).toContain(">Partial title<");
+ });
+
+ it("renders supplied retry content for an error without owning a callback", () => {
+ const html = renderToStaticMarkup(
+ Retry preview}
+ />,
+ );
+
+ expect(html).toContain('role="alert"');
+ expect(html).toContain('href="/admin-next/operations"');
+ expect(html).toContain(">Retry preview<");
+ });
+});
+
+describe("HousekeepingPageShell", () => {
+ it("renders supplied header, context, action, and body content", () => {
+ const html = renderToStaticMarkup(
+ Preview context}
+ primaryAction={Create preview}
+ >
+ Page body
+ ,
+ );
+
+ expect(html).toContain("People<");
+ expect(html).toContain(">Review operator-facing people data<");
+ expect(html).toContain(">Preview context<");
+ expect(html).toContain('href="/admin-next/people/new"');
+ expect(html).toContain(">Create preview<");
+ expect(html).toContain(">Page body
");
+ });
+});
diff --git a/src/features/housekeeping/foundation/page/housekeeping-page-state.tsx b/src/features/housekeeping/foundation/page/housekeeping-page-state.tsx
new file mode 100644
index 00000000..dc2e2a56
--- /dev/null
+++ b/src/features/housekeeping/foundation/page/housekeeping-page-state.tsx
@@ -0,0 +1,47 @@
+import type { ReactNode } from "react";
+
+interface HousekeepingPageStateProps {
+ state: "loading" | "empty" | "partial" | "error";
+ title: string;
+ description: string;
+ retryAction?: ReactNode;
+}
+
+export function HousekeepingPageState({
+ state,
+ title,
+ description,
+ retryAction,
+}: HousekeepingPageStateProps) {
+ const isError = state === "error";
+ const isPartial = state === "partial";
+
+ return (
+
+ {isPartial ? (
+
+ {title}
+
+ ) : (
+ {title}
+ )}
+
+ {description}
+
+ {retryAction ? {retryAction}
: null}
+
+ );
+}
diff --git a/src/features/housekeeping/foundation/shell/command-trigger.tsx b/src/features/housekeeping/foundation/shell/command-trigger.tsx
new file mode 100644
index 00000000..80065ae6
--- /dev/null
+++ b/src/features/housekeeping/foundation/shell/command-trigger.tsx
@@ -0,0 +1,15 @@
+interface CommandTriggerProps {
+ label: string;
+}
+
+export function CommandTrigger({ label }: CommandTriggerProps) {
+ return (
+
+ );
+}
diff --git a/src/features/housekeeping/foundation/shell/context-nav.tsx b/src/features/housekeeping/foundation/shell/context-nav.tsx
new file mode 100644
index 00000000..46926fb0
--- /dev/null
+++ b/src/features/housekeeping/foundation/shell/context-nav.tsx
@@ -0,0 +1,25 @@
+import Link from "next/link";
+
+interface ContextNavProps {
+ items: readonly { id: string; href: string; label: string }[];
+ ariaLabel: string;
+}
+
+export function ContextNav({ items, ariaLabel }: ContextNavProps) {
+ return (
+
+ );
+}
diff --git a/src/features/housekeeping/foundation/shell/domain-rail.tsx b/src/features/housekeeping/foundation/shell/domain-rail.tsx
new file mode 100644
index 00000000..9bcdc5a6
--- /dev/null
+++ b/src/features/housekeeping/foundation/shell/domain-rail.tsx
@@ -0,0 +1,49 @@
+import { FileText, Gem, Hotel, Inbox, Settings, Users } from "lucide-react";
+import Link from "next/link";
+import type { HousekeepingNavigationDomain } from "../navigation";
+
+interface DomainRailProps {
+ domains: readonly HousekeepingNavigationDomain[];
+ activeDomainId: HousekeepingNavigationDomain["id"];
+ ariaLabel: string;
+}
+
+function DomainIcon({ iconId }: Pick) {
+ const Icon = {
+ inbox: Inbox,
+ users: Users,
+ "file-text": FileText,
+ gem: Gem,
+ hotel: Hotel,
+ settings: Settings,
+ }[iconId];
+
+ return ;
+}
+
+export function DomainRail({
+ domains,
+ activeDomainId,
+ ariaLabel,
+}: DomainRailProps) {
+ return (
+
+ );
+}
diff --git a/src/features/housekeeping/foundation/shell/housekeeping-shell.test.tsx b/src/features/housekeeping/foundation/shell/housekeeping-shell.test.tsx
new file mode 100644
index 00000000..394a4043
--- /dev/null
+++ b/src/features/housekeeping/foundation/shell/housekeeping-shell.test.tsx
@@ -0,0 +1,174 @@
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+import { renderToStaticMarkup } from "react-dom/server";
+import { describe, expect, it } from "vitest";
+import type { HousekeepingNavigationDomain } from "../navigation";
+import { HousekeepingShell } from "./housekeeping-shell";
+
+const labels = {
+ skipToContent: "Skip to housekeeping content",
+ primaryNavigation: "Primary housekeeping navigation",
+ contextualNavigation: "Operations navigation",
+ command: "Commands are unavailable in preview",
+ preview: "Preview",
+ backToSite: "Back to site",
+};
+
+const domains: readonly HousekeepingNavigationDomain[] = [
+ {
+ id: "operations",
+ href: "/admin-next/operations",
+ iconId: "inbox",
+ label: "Operations",
+ description: "Manage operations",
+ items: [
+ { id: "queue", href: "/admin-next/operations/queue", label: "Queue" },
+ ],
+ },
+ {
+ id: "people",
+ href: "/admin-next/people",
+ iconId: "users",
+ label: "People",
+ description: "Manage people",
+ items: [],
+ },
+ {
+ id: "content",
+ href: "/admin-next/content",
+ iconId: "file-text",
+ label: "Content",
+ description: "Manage content",
+ items: [],
+ },
+ {
+ id: "economy",
+ href: "/admin-next/economy",
+ iconId: "gem",
+ label: "Economy",
+ description: "Manage economy",
+ items: [],
+ },
+ {
+ id: "hotel",
+ href: "/admin-next/hotel",
+ iconId: "hotel",
+ label: "Hotel",
+ description: "Manage hotel",
+ items: [],
+ },
+ {
+ id: "system",
+ href: "/admin-next/system",
+ iconId: "settings",
+ label: "System",
+ description: "Manage system",
+ items: [],
+ },
+];
+
+function renderShell(
+ activeDomainId: HousekeepingNavigationDomain["id"] = "operations",
+): string {
+ return renderToStaticMarkup(
+
+ Deck body
+ ,
+ );
+}
+
+describe("HousekeepingShell", () => {
+ it("renders the accessible command deck landmarks and supplied shell content", () => {
+ const html = renderShell();
+
+ expect(html).toContain('href="#housekeeping-content"');
+ expect(html).toContain(">Skip to housekeeping content<");
+ expect(html).toContain("