feat(FN-2732): add manual dream processing actions in dashboard

- Extend useMemoryData with triggerDreamNow and dreamRunning state by locating and invoking the Memory Dreams automation
- Add Dream Now actions to MemoryView and SettingsModal memory settings with loading UI, success/error toasts, and file refresh in MemoryView
- Expand hook and component tests to cover dream-trigger visibility and execution flows in both views
- Update terminal mobile keyboard layout CSS contract expectations for the current mobile modal width and min-height rules
This commit is contained in:
Fusion
2026-04-28 01:58:49 -07:00
committed by gsxdsm
parent 789afc30b0
commit 0b8ffa1528
7 changed files with 327 additions and 37 deletions

View File

@@ -13,6 +13,15 @@ vi.mock("../../api", () => ({
fetchMemoryStats: vi.fn(),
compactMemory: vi.fn(),
fetchMemoryBackendStatus: vi.fn(),
fetchSettings: vi.fn(),
updateSettings: vi.fn(),
fetchMemoryFiles: vi.fn(),
fetchMemoryFile: vi.fn(),
saveMemoryFile: vi.fn(),
installQmd: vi.fn(),
testMemoryRetrieval: vi.fn(),
fetchAutomations: vi.fn(),
runAutomation: vi.fn(),
}));
// Mock useMemoryBackendStatus hook
@@ -42,6 +51,8 @@ import {
triggerInsightExtraction,
fetchMemoryAudit,
compactMemory,
fetchAutomations,
runAutomation,
} from "../../api";
describe("useMemoryData", () => {
@@ -273,6 +284,69 @@ describe("useMemoryData", () => {
expect(fetchMemoryInsights).toHaveBeenCalledTimes(2); // Initial + refresh
});
it("triggerDreamNow finds and runs the Memory Dreams automation", async () => {
vi.mocked(fetchMemory).mockResolvedValue({ content: "Initial content" });
vi.mocked(fetchMemoryInsights).mockResolvedValue({ content: null, exists: false });
vi.mocked(fetchMemoryAudit).mockResolvedValue({
generatedAt: "2024-01-01T00:00:00.000Z",
workingMemory: { exists: true, size: 20, sectionCount: 1 },
insightsMemory: { exists: false, size: 0, insightCount: 0, categories: {} },
extraction: { runAt: "", success: false, insightCount: 0, duplicateCount: 0, skippedCount: 0, summary: "" },
pruning: { applied: false, reason: "", sizeDelta: 0, originalSize: 0, newSize: 0 },
checks: [],
health: "warning",
});
vi.mocked(fetchAutomations).mockResolvedValue([
{ id: "auto-1", name: "Other", cron: "* * * * *", enabled: true },
{ id: "auto-2", name: "Memory Dreams", cron: "0 4 * * *", enabled: true },
] as any);
vi.mocked(runAutomation).mockResolvedValue({ schedule: { id: "auto-2" }, result: { success: true } } as any);
const { result } = renderHook(() => useMemoryData({ projectId: "test-project" }));
await waitFor(() => {
expect(result.current.workingMemoryLoading).toBe(false);
});
await act(async () => {
await result.current.triggerDreamNow();
});
expect(fetchAutomations).toHaveBeenCalledWith({ scope: "project", projectId: "test-project" });
expect(runAutomation).toHaveBeenCalledWith("auto-2", { scope: "project", projectId: "test-project" });
expect(result.current.dreamRunning).toBe(false);
});
it("triggerDreamNow throws when Memory Dreams schedule is missing and resets state", async () => {
vi.mocked(fetchMemory).mockResolvedValue({ content: "Initial content" });
vi.mocked(fetchMemoryInsights).mockResolvedValue({ content: null, exists: false });
vi.mocked(fetchMemoryAudit).mockResolvedValue({
generatedAt: "2024-01-01T00:00:00.000Z",
workingMemory: { exists: true, size: 20, sectionCount: 1 },
insightsMemory: { exists: false, size: 0, insightCount: 0, categories: {} },
extraction: { runAt: "", success: false, insightCount: 0, duplicateCount: 0, skippedCount: 0, summary: "" },
pruning: { applied: false, reason: "", sizeDelta: 0, originalSize: 0, newSize: 0 },
checks: [],
health: "warning",
});
vi.mocked(fetchAutomations).mockResolvedValue([{ id: "auto-1", name: "Other" }] as any);
const { result } = renderHook(() => useMemoryData({ projectId: "test-project" }));
await waitFor(() => {
expect(result.current.workingMemoryLoading).toBe(false);
});
await act(async () => {
await expect(result.current.triggerDreamNow()).rejects.toThrow(
"Memory Dreams schedule not found. Enable dream processing in memory settings first.",
);
});
expect(runAutomation).not.toHaveBeenCalled();
expect(result.current.dreamRunning).toBe(false);
});
it("sets correct loading states during async operations", async () => {
vi.mocked(fetchMemory).mockResolvedValue({ content: "Initial content" });
vi.mocked(fetchMemoryInsights).mockResolvedValue({ content: null, exists: false });

View File

@@ -13,6 +13,8 @@ import {
fetchMemoryFiles,
fetchMemoryFile,
saveMemoryFile,
fetchAutomations,
runAutomation,
installQmd,
testMemoryRetrieval,
type MemoryAuditReport,
@@ -85,6 +87,10 @@ interface UseMemoryDataResult {
extractInsights: () => Promise<{ success: boolean; summary: string }>;
extracting: boolean;
// Dreams
triggerDreamNow: () => Promise<unknown>;
dreamRunning: boolean;
// Audit
auditReport: MemoryAuditReport | null;
auditLoading: boolean;
@@ -162,6 +168,9 @@ export function useMemoryData(options: UseMemoryDataOptions = {}): UseMemoryData
// Extraction state
const [extracting, setExtracting] = useState(false);
// Dreams state
const [dreamRunning, setDreamRunning] = useState(false);
// Audit state
const [auditReport, setAuditReport] = useState<MemoryAuditReport | null>(null);
const [auditLoading, setAuditLoading] = useState(true);
@@ -514,6 +523,21 @@ export function useMemoryData(options: UseMemoryDataOptions = {}): UseMemoryData
}
}, [projectId, refreshInsights, refreshAudit]);
const triggerDreamNow = useCallback(async () => {
setDreamRunning(true);
try {
const automations = await fetchAutomations({ scope: "project", projectId });
const memoryDreamsSchedule = automations.find((automation) => automation.name === "Memory Dreams");
if (!memoryDreamsSchedule) {
throw new Error("Memory Dreams schedule not found. Enable dream processing in memory settings first.");
}
return await runAutomation(memoryDreamsSchedule.id, { scope: "project", projectId });
} finally {
setDreamRunning(false);
}
}, [projectId]);
// Compact memory
const compactMemoryAction = useCallback(async (path?: string) => {
setCompacting(true);
@@ -582,6 +606,10 @@ export function useMemoryData(options: UseMemoryDataOptions = {}): UseMemoryData
extractInsights,
extracting,
// Dreams
triggerDreamNow,
dreamRunning,
// Audit
auditReport,
auditLoading,