feat: journal dashboard widgets, agent polish, and edit-mode live previews
CI / checks (push) Failing after 2m7s
CI / build (push) Successful in 4m36s

Journal dashboard widgets and quick-add; rich-text quick-add dialogs.

Dashboard draft sync for live edit previews; assistant bubble + API tools.

Journal UX: stress slider, mood grid, query cap fix.
This commit is contained in:
ginnoir
2026-07-04 22:03:45 -05:00
parent 4a924a4107
commit a09747c314
76 changed files with 4594 additions and 611 deletions
+13
View File
@@ -0,0 +1,13 @@
import { eq } from "drizzle-orm";
import { db } from "@/lib/db";
import { users } from "@/modules/_core/schema";
export async function getAssistantEnabled(userId: string): Promise<boolean> {
const [row] = await db
.select({ assistantEnabled: users.assistantEnabled })
.from(users)
.where(eq(users.id, userId))
.limit(1);
return row?.assistantEnabled ?? false;
}
+34
View File
@@ -0,0 +1,34 @@
import "server-only";
import { cookies } from "next/headers";
import { parseDashboardLayout, type DashboardLayout } from "./dashboard";
function draftCookieName(dashboardId: string) {
return `dashboard-editor-draft-${dashboardId}`;
}
export async function readEditorDraftLayout(dashboardId: string): Promise<DashboardLayout | null> {
const jar = await cookies();
const raw = jar.get(draftCookieName(dashboardId))?.value;
if (!raw) return null;
try {
return parseDashboardLayout(JSON.parse(raw));
} catch {
return null;
}
}
export async function writeEditorDraftLayout(dashboardId: string, layout: DashboardLayout) {
const jar = await cookies();
jar.set(draftCookieName(dashboardId), JSON.stringify(layout), {
httpOnly: true,
sameSite: "lax",
maxAge: 60 * 60,
path: "/",
});
}
export async function clearEditorDraftLayout(dashboardId: string) {
const jar = await cookies();
jar.delete(draftCookieName(dashboardId));
}
+30 -1
View File
@@ -1,8 +1,9 @@
import "server-only";
import { getRegistry } from "@/modules/_core";
import type { SerializedWidgetMeta } from "@/modules/_core/registry";
import type { DashboardLayout, PresetId } from "./dashboard";
import type { DashboardLayout, PresetId, WidgetPlacement } from "./dashboard";
import { computePresetLayoutFromMetas } from "./dashboard";
import { getWidget } from "@/modules/_core";
/** Build a layout matching one of the design's three dashboard arrangements
* using the live module registry. Server-only — the registry is empty on
@@ -29,3 +30,31 @@ function computePresetLayout(preset: PresetId): DashboardLayout {
export function computeDefaultLayout(): DashboardLayout {
return computePresetLayout("classic");
}
export function normalizeDashboardLayout(layout: DashboardLayout): DashboardLayout {
return {
version: layout.version,
widgets: layout.widgets.map((placement) => normalizeWidgetPlacement(placement)),
};
}
function normalizeWidgetPlacement(placement: WidgetPlacement): WidgetPlacement {
const widget = getWidget(placement.widgetId);
if (!widget) return placement;
const merged =
placement.config != null &&
typeof placement.config === "object" &&
!Array.isArray(placement.config)
? {
...(widget.defaultConfig as Record<string, unknown>),
...(placement.config as Record<string, unknown>),
}
: widget.defaultConfig;
const parsed = widget.configSchema.safeParse(merged);
return {
...placement,
config: parsed.success ? parsed.data : widget.defaultConfig,
};
}
+21 -1
View File
@@ -7,7 +7,27 @@ import * as notesSchema from "@/modules/notes/schema";
import * as gardenSchema from "@/modules/garden/schema";
import * as journalSchema from "@/modules/journal/schema";
const client = postgres(process.env["DATABASE_URL"]!);
type PostgresClient = ReturnType<typeof postgres>;
const globalForDb = globalThis as typeof globalThis & {
__famappPostgres?: PostgresClient;
};
function createClient(): PostgresClient {
const url = process.env["DATABASE_URL"];
if (!url) throw new Error("DATABASE_URL is not set");
return postgres(url, {
max: process.env.NODE_ENV === "production" ? 10 : 1,
idle_timeout: 20,
connect_timeout: 10,
});
}
const client = globalForDb.__famappPostgres ?? createClient();
if (process.env.NODE_ENV !== "production") {
globalForDb.__famappPostgres = client;
}
const schema = {
...coreSchema,