+
{
+ global.ResizeObserver = vi.fn().mockImplementation(() => ({
+ observe: vi.fn(),
+ unobserve: vi.fn(),
+ disconnect: vi.fn(),
+ }));
+});
+
// Mock react-grid-layout
vi.mock("react-grid-layout", () => ({
default: ({ children }: { children: React.ReactNode }): React.JSX.Element => (
diff --git a/apps/web/src/components/widgets/defaultLayout.ts b/apps/web/src/components/widgets/defaultLayout.ts
new file mode 100644
index 0000000..5bad5be
--- /dev/null
+++ b/apps/web/src/components/widgets/defaultLayout.ts
@@ -0,0 +1,25 @@
+/**
+ * Default dashboard layout — used when a user has no saved layout.
+ *
+ * Widget ID format: "WidgetType-default" where the prefix before the
+ * first "-" must match a key in WidgetRegistry.
+ *
+ * Grid: 12 columns, 100px row height.
+ */
+
+import type { WidgetPlacement } from "@mosaic/shared";
+
+export const DEFAULT_LAYOUT: WidgetPlacement[] = [
+ // Row 0 — top row (3 widgets, 4 cols each)
+ { i: "TasksWidget-default", x: 0, y: 0, w: 4, h: 2, minW: 1, minH: 2, maxW: 4 },
+ { i: "CalendarWidget-default", x: 4, y: 0, w: 4, h: 2, minW: 2, minH: 2, maxW: 4 },
+ { i: "AgentStatusWidget-default", x: 8, y: 0, w: 4, h: 2, minW: 1, minH: 2, maxW: 3 },
+
+ // Row 2 — middle row
+ { i: "ActiveProjectsWidget-default", x: 0, y: 2, w: 4, h: 3, minW: 2, minH: 2, maxW: 4 },
+ { i: "TaskProgressWidget-default", x: 4, y: 2, w: 4, h: 2, minW: 1, minH: 2, maxW: 3 },
+ { i: "OrchestratorEventsWidget-default", x: 8, y: 2, w: 4, h: 2, minW: 1, minH: 2, maxW: 4 },
+
+ // Row 4 — bottom
+ { i: "QuickCaptureWidget-default", x: 4, y: 4, w: 4, h: 1, minW: 2, minH: 1, maxW: 4, maxH: 2 },
+];
diff --git a/apps/web/src/lib/api/layouts.ts b/apps/web/src/lib/api/layouts.ts
new file mode 100644
index 0000000..58cf469
--- /dev/null
+++ b/apps/web/src/lib/api/layouts.ts
@@ -0,0 +1,54 @@
+/**
+ * Layout API client — CRUD for user dashboard layouts
+ */
+
+import type { UserLayout, WidgetPlacement } from "@mosaic/shared";
+import { apiGet, apiPost, apiPatch } from "./client";
+
+export interface CreateLayoutPayload {
+ name: string;
+ isDefault?: boolean;
+ layout: WidgetPlacement[];
+ metadata?: Record;
+}
+
+export interface UpdateLayoutPayload {
+ name?: string;
+ isDefault?: boolean;
+ layout?: WidgetPlacement[];
+ metadata?: Record;
+}
+
+/**
+ * Fetch the user's default layout for the active workspace.
+ * Returns null if no layout exists (404).
+ */
+export async function fetchDefaultLayout(workspaceId: string): Promise {
+ try {
+ return await apiGet("/api/layouts/default", workspaceId);
+ } catch {
+ // 404 = no layout yet — not an error
+ return null;
+ }
+}
+
+/**
+ * Create a new layout.
+ */
+export async function createLayout(
+ workspaceId: string,
+ payload: CreateLayoutPayload
+): Promise {
+ return apiPost("/api/layouts", payload, workspaceId);
+}
+
+/**
+ * Update an existing layout (partial patch).
+ */
+export async function updateLayout(
+ workspaceId: string,
+ layoutId: string,
+ payload: UpdateLayoutPayload
+): Promise {
+ return apiPatch(`/api/layouts/${layoutId}`, payload, workspaceId);
+}
diff --git a/docs/TASKS.md b/docs/TASKS.md
index 1d73d77..f1b0790 100644
--- a/docs/TASKS.md
+++ b/docs/TASKS.md
@@ -8,8 +8,8 @@
| TW-THM-001 | done | Theme architecture — Create theme definition interface, theme registry, and 5 built-in themes (Dark, Light, Nord, Dracula, Solarized) as TS files | #487 | web | feat/ms18-theme-architecture | TW-PLAN-001 | TW-THM-002,TW-THM-003 | worker | 2026-02-23 | 2026-02-23 | 30K | ~15K | PR #493 merged |
| TW-THM-002 | done | ThemeProvider upgrade — Load themes dynamically from registry, apply CSS variables, support instant theme switching without page reload | #487 | web | feat/ms18-theme-provider-upgrade | TW-THM-001 | TW-THM-003,TW-VER-002 | worker | 2026-02-23 | 2026-02-23 | 25K | ~12K | PR #494 merged |
| TW-THM-003 | done | Theme selection UI — Settings page section with theme browser, live preview swatches, persist selection to UserPreference.theme via API | #487 | web | feat/ms18-theme-selection-ui | TW-THM-001,TW-THM-002 | TW-VER-002 | worker | 2026-02-23 | 2026-02-23 | 25K | ~10K | PR #495 merged |
-| TW-WDG-001 | not-started | Widget definition seeding — Seed 7 existing widgets into widget_definitions table with correct sizing constraints and configSchema | #488 | api | TBD | TW-PLAN-001 | TW-WDG-002 | worker | — | — | 15K | — | |
-| TW-WDG-002 | not-started | Dashboard → WidgetGrid migration — Replace hardcoded dashboard layout with WidgetGrid, load/save layout via UserLayout API, default layout on first visit | #488 | web | TBD | TW-WDG-001 | TW-WDG-003,TW-WDG-004,TW-WDG-005 | worker | — | — | 40K | — | |
+| TW-WDG-001 | done | Widget definition seeding — Seed 7 existing widgets into widget_definitions table with correct sizing constraints and configSchema | #488 | api | feat/ms18-widget-seed | TW-PLAN-001 | TW-WDG-002 | worker | 2026-02-23 | 2026-02-23 | 15K | ~8K | PR #496 merged |
+| TW-WDG-002 | in-progress | Dashboard → WidgetGrid migration — Replace hardcoded dashboard layout with WidgetGrid, load/save layout via UserLayout API, default layout on first visit | #488 | web | feat/ms18-widget-grid-migration | TW-WDG-001 | TW-WDG-003,TW-WDG-004,TW-WDG-005 | worker | 2026-02-23 | — | 40K | — | |
| TW-WDG-003 | not-started | Widget picker UI — Drawer/dialog to browse available widgets from registry, preview size/description, add to dashboard | #488 | web | TBD | TW-WDG-002 | TW-VER-001 | worker | — | — | 25K | — | |
| TW-WDG-004 | not-started | Widget configuration UI — Per-widget settings dialog using configSchema, configure data source/filters/colors/title | #488 | web | TBD | TW-WDG-002 | TW-VER-001 | worker | — | — | 30K | — | |
| TW-WDG-005 | not-started | Layout management UI — Save/rename/switch/delete layouts, reset to default. UI controls in dashboard header area | #488 | web | TBD | TW-WDG-002 | TW-VER-001 | worker | — | — | 20K | — | |
@@ -26,9 +26,9 @@
| Metric | Value |
| ------------- | ------------------------- |
| Total tasks | 16 |
-| Completed | 4 (PLAN-001, THM-001–003) |
-| In Progress | 0 |
-| Remaining | 12 |
-| PRs merged | #493, #494, #495 |
+| Completed | 5 (PLAN-001, THM-001–003, WDG-001) |
+| In Progress | 0 |
+| Remaining | 11 |
+| PRs merged | #493, #494, #495, #496 |
| Issues closed | — |
| Milestone | MS18-ThemeWidgets |