feat(#37-41): Add domains, ideas, relationships, agents, widgets schema

Schema additions for issues #37-41:

New models:
- Domain (#37): Life domains (work, marriage, homelab, etc.)
- Idea (#38): Brain dumps with pgvector embeddings
- Relationship (#39): Generic entity linking (blocks, depends_on)
- Agent (#40): ClawdBot agent tracking with metrics
- AgentSession (#40): Conversation session tracking
- WidgetDefinition (#41): HUD widget registry
- UserLayout (#41): Per-user dashboard configuration

Updated models:
- Task, Event, Project: Added domainId foreign key
- User, Workspace: Added new relations

New enums:
- IdeaStatus: CAPTURED, PROCESSING, ACTIONABLE, ARCHIVED, DISCARDED
- RelationshipType: BLOCKS, BLOCKED_BY, DEPENDS_ON, etc.
- AgentStatus: IDLE, WORKING, WAITING, ERROR, TERMINATED
- EntityType: Added IDEA, DOMAIN

Migration: 20260129182803_add_domains_ideas_agents_widgets
This commit is contained in:
Jason Woltje
2026-01-29 12:29:21 -06:00
parent a220c2dc0a
commit 973502f26e
308 changed files with 18374 additions and 113 deletions

View File

@@ -0,0 +1,27 @@
"use client";
import { Calendar } from "@/components/calendar/Calendar";
import { mockEvents } from "@/lib/api/events";
export default function CalendarPage() {
// TODO: Replace with real API call when backend is ready
// const { data: events, isLoading } = useQuery({
// queryKey: ["events"],
// queryFn: fetchEvents,
// });
const events = mockEvents;
const isLoading = false;
return (
<main className="container mx-auto px-4 py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900">Calendar</h1>
<p className="text-gray-600 mt-2">
View your schedule at a glance
</p>
</div>
<Calendar events={events} isLoading={isLoading} />
</main>
);
}

View File

@@ -0,0 +1,37 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/lib/auth/auth-context";
import { Navigation } from "@/components/layout/Navigation";
import type { ReactNode } from "react";
export default function AuthenticatedLayout({ children }: { children: ReactNode }) {
const router = useRouter();
const { isAuthenticated, isLoading } = useAuth();
useEffect(() => {
if (!isLoading && !isAuthenticated) {
router.push("/login");
}
}, [isAuthenticated, isLoading, router]);
if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
</div>
);
}
if (!isAuthenticated) {
return null;
}
return (
<div className="min-h-screen bg-gray-50">
<Navigation />
<div className="pt-16">{children}</div>
</div>
);
}

View File

@@ -0,0 +1,30 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import TasksPage from "./page";
// Mock the TaskList component
vi.mock("@/components/tasks/TaskList", () => ({
TaskList: ({ tasks, isLoading }: { tasks: unknown[]; isLoading: boolean }) => (
<div data-testid="task-list">
{isLoading ? "Loading" : `${tasks.length} tasks`}
</div>
),
}));
describe("TasksPage", () => {
it("should render the page title", () => {
render(<TasksPage />);
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Tasks");
});
it("should render the TaskList component", () => {
render(<TasksPage />);
expect(screen.getByTestId("task-list")).toBeInTheDocument();
});
it("should have proper layout structure", () => {
const { container } = render(<TasksPage />);
const main = container.querySelector("main");
expect(main).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,27 @@
"use client";
import { TaskList } from "@/components/tasks/TaskList";
import { mockTasks } from "@/lib/api/tasks";
export default function TasksPage() {
// TODO: Replace with real API call when backend is ready
// const { data: tasks, isLoading } = useQuery({
// queryKey: ["tasks"],
// queryFn: fetchTasks,
// });
const tasks = mockTasks;
const isLoading = false;
return (
<main className="container mx-auto px-4 py-8">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900">Tasks</h1>
<p className="text-gray-600 mt-2">
Organize your work at your own pace
</p>
</div>
<TaskList tasks={tasks} isLoading={isLoading} />
</main>
);
}