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
+29 -7
View File
@@ -11,7 +11,12 @@ import { GripVertical, Settings2, Trash2, RotateCcw, Plus, LayoutGrid } from "lu
import type { DashboardLayout, WidgetPlacement, PresetId } from "@/lib/dashboard";
import { computePresetLayoutFromMetas, widgetContentKey } from "@/lib/dashboard";
import type { SerializedWidgetMeta } from "@/modules/_core/registry";
import { saveDashboardLayout, resetDashboardLayout } from "@/app/d/actions";
import {
saveDashboardLayout,
resetDashboardLayout,
syncEditorDraftLayout,
discardEditorDraftLayout,
} from "@/app/d/actions";
import { Button } from "@/components/ui/button";
import { WidgetPicker } from "./widget-picker";
@@ -58,8 +63,18 @@ export function DashboardEditor({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dashboard.slug]);
function publishDraft(next: WidgetPlacement[]) {
startTransition(async () => {
await syncEditorDraftLayout(dashboard.id, { version: 1, widgets: next });
router.refresh();
});
}
function handleCancel() {
router.push(`/d/${dashboard.slug}`);
startTransition(async () => {
await discardEditorDraftLayout(dashboard.id);
router.push(`/d/${dashboard.slug}`);
});
}
function handleSave() {
@@ -89,26 +104,32 @@ export function DashboardEditor({
}
function removeWidget(index: number) {
setPlacements((current) => current.filter((_, i) => i !== index));
const next = placements.filter((_, i) => i !== index);
setPlacements(next);
setIsDirty(true);
publishDraft(next);
}
function addWidget(widgetId: string, config: unknown) {
const meta = widgetMetas.find((m) => m.id === widgetId);
if (!meta) return;
const maxY = placements.reduce((m, p) => Math.max(m, p.y + p.h), 0);
setPlacements((current) => [
...current,
const next = [
...placements,
{ widgetId, config, x: 0, y: maxY, w: meta.defaultSize.w, h: meta.defaultSize.h },
]);
];
setPlacements(next);
setIsDirty(true);
setPickerOpen(false);
publishDraft(next);
}
function updateConfig(index: number, config: unknown) {
setPlacements((current) => current.map((p, i) => (i === index ? { ...p, config } : p)));
const next = placements.map((p, i) => (i === index ? { ...p, config } : p));
setPlacements(next);
setIsDirty(true);
setConfiguringIndex(null);
publishDraft(next);
}
function applyPreset(preset: PresetId) {
@@ -116,6 +137,7 @@ export function DashboardEditor({
setPlacements(next.widgets);
setIsDirty(true);
setPresetMenuOpen(false);
publishDraft(next.widgets);
}
const gridItems: Layout = placements.map((p, i) => ({