feat: add domains, ideas, layouts, widgets API modules

- Add DomainsModule with full CRUD, search, and activity logging
- Add IdeasModule with quick capture endpoint
- Add LayoutsModule for user dashboard layouts
- Add WidgetsModule for widget definitions (read-only)
- Update ActivityService with domain/idea logging methods
- Register all new modules in AppModule
This commit is contained in:
Jason Woltje
2026-01-29 13:47:03 -06:00
parent 973502f26e
commit f47dd8bc92
66 changed files with 4277 additions and 29 deletions

View File

@@ -0,0 +1,182 @@
/**
* HUD container - main dashboard interface
*/
import { useMemo } from "react";
import { Button } from "@mosaic/ui";
import { RotateCcw } from "lucide-react";
import { WidgetGrid } from "./WidgetGrid";
import { WidgetRenderer } from "./WidgetRenderer";
import { useLayout } from "@/lib/hooks/useLayout";
import type { WidgetPlacement } from "@mosaic/shared";
export interface HUDProps {
className?: string;
}
/**
* Registry of available widget components
* This will be populated with actual widget components
*/
const WIDGET_REGISTRY = {
TasksWidget: {
name: "tasks",
displayName: "Tasks",
description: "View and manage your tasks",
defaultWidth: 2,
defaultHeight: 3,
minWidth: 1,
minHeight: 2,
},
CalendarWidget: {
name: "calendar",
displayName: "Calendar",
description: "Upcoming events and schedule",
defaultWidth: 2,
defaultHeight: 2,
minWidth: 1,
minHeight: 2,
},
QuickCaptureWidget: {
name: "quick-capture",
displayName: "Quick Capture",
description: "Capture ideas and notes",
defaultWidth: 2,
defaultHeight: 1,
minWidth: 1,
minHeight: 1,
},
AgentStatusWidget: {
name: "agent-status",
displayName: "Agent Status",
description: "View running agent sessions",
defaultWidth: 2,
defaultHeight: 2,
minWidth: 1,
minHeight: 1,
},
} as const;
type WidgetRegistryKey = keyof typeof WIDGET_REGISTRY;
export function HUD({ className = "" }: HUDProps) {
const {
currentLayout,
updateLayout,
addWidget,
removeWidget,
switchLayout,
resetLayout,
} = useLayout();
const isEditing = true; // For now, always in edit mode (can be toggled later)
const handleLayoutChange = (newLayout: readonly { i: string; x: number; y: number; w: number; h: number }[]) => {
updateLayout([...newLayout] as WidgetPlacement[]);
};
const handleAddWidget = (widgetType: WidgetRegistryKey) => {
const widgetConfig = WIDGET_REGISTRY[widgetType];
const widgetId = `${widgetType.toLowerCase()}-${Date.now()}`;
// Find the next available position
const maxY = currentLayout?.layout.reduce((max, w) => Math.max(max, w.y + w.h), 0) || 0;
const newWidget = {
i: widgetId,
x: 0,
y: maxY,
w: widgetConfig.defaultWidth,
h: widgetConfig.defaultHeight,
minW: widgetConfig.minWidth,
minH: widgetConfig.minHeight,
isDraggable: true,
isResizable: true,
};
addWidget(newWidget);
};
const handleResetLayout = () => {
if (confirm("Are you sure you want to reset the layout? This will remove all widgets.")) {
resetLayout();
}
};
const widgetComponents = useMemo(() => {
if (!currentLayout?.layout) return [];
return currentLayout.layout.map((widget) => (
<WidgetRenderer
key={widget.i}
widget={widget}
isEditing={isEditing}
onRemove={removeWidget}
/>
));
}, [currentLayout?.layout, isEditing, removeWidget]);
return (
<div className={`hud-container ${className}`}>
{/* Toolbar */}
<div className="bg-white border-b border-gray-200 px-6 py-4 mb-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900">Dashboard</h1>
<div className="flex items-center gap-2">
<select
value={currentLayout?.id || ""}
onChange={(e) => switchLayout(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Default Layout</option>
{/* Add more layout options here */}
</select>
<Button
onClick={handleResetLayout}
variant="secondary"
size="sm"
className="flex items-center gap-2"
>
<RotateCcw className="w-4 h-4" />
Reset
</Button>
{/* Widget type selector */}
<div className="relative">
<select
onChange={(e) => {
const widgetType = e.target.value as WidgetRegistryKey;
if (widgetType) {
handleAddWidget(widgetType);
e.target.value = "";
}
}}
className="px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 cursor-pointer"
defaultValue=""
>
<option value="" disabled>
Add Widget
</option>
{Object.entries(WIDGET_REGISTRY).map(([key, config]) => (
<option key={key} value={key}>
{config.displayName}
</option>
))}
</select>
</div>
</div>
</div>
</div>
{/* Widget Grid */}
<WidgetGrid
layout={currentLayout?.layout || []}
onLayoutChange={handleLayoutChange}
isEditing={isEditing}
>
{widgetComponents}
</WidgetGrid>
</div>
);
}