From 50bf97e4eae211f1800978f4c3d45d660e9ef02f Mon Sep 17 00:00:00 2001 From: Qoder-AI <215799558+Qoder-AI@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:01:12 +0800 Subject: [PATCH 1/3] fix(web): retain memory through the local end of day --- .../context-continuity-panel.test.tsx | 27 ++++++++++++++++++- .../components/context-continuity-panel.tsx | 4 ++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/web/src/components/context-continuity-panel.test.tsx b/web/src/components/context-continuity-panel.test.tsx index f93ea30e..1c6ccdd2 100644 --- a/web/src/components/context-continuity-panel.test.tsx +++ b/web/src/components/context-continuity-panel.test.tsx @@ -1,5 +1,5 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import type { APIClient } from "../api/client"; import { downloadTextFile } from "../lib/download"; @@ -82,6 +82,31 @@ describe("ContextContinuityPanel", () => { "application/json", `${JSON.stringify(exported, null, 2)}\n`)); }); + it("retains memory through the end of the selected local calendar day", async () => { + const user = userEvent.setup(); + const get = vi.fn().mockImplementation((path: string) => { + if (path.includes("project-instructions")) return Promise.resolve(projectInstructionState()); + if (path === "/memories") return Promise.resolve([memoryView()]); + if (path === "/memory-candidates") return Promise.resolve([]); + if (path.includes("/tree")) return Promise.resolve(sessionTree()); + return Promise.reject(new Error(`unexpected read ${path}`)); + }); + const postControl = vi.fn().mockResolvedValue(memoryView()); + renderPanel({ hasControl: true, get, postControl, patchControl: vi.fn(), + deleteControl: vi.fn() } as unknown as APIClient); + + await screen.findByText("Test convention"); + await user.type(screen.getByLabelText("Memory title"), "Release checklist"); + await user.type(screen.getByLabelText("Memory content"), "Check the release notes."); + fireEvent.change(screen.getByLabelText("Retain until"), { target: { value: "2030-02-28" } }); + await user.click(screen.getByRole("button", { name: "Create memory" })); + + await waitFor(() => expect(postControl).toHaveBeenCalledWith("/memories", + expect.objectContaining({ title: "Release checklist", content: "Check the release notes.", + retention_until: new Date(2030, 1, 28, 23, 59, 59, 999).toISOString() }), + expect.stringMatching(/^web-memory-create-/))); + }); + it("keeps mutation controls disabled on a read-only connection", async () => { renderPanel({ hasControl: false, get: vi.fn().mockImplementation((path: string) => path.includes("project-instructions") diff --git a/web/src/components/context-continuity-panel.tsx b/web/src/components/context-continuity-panel.tsx index da048e9c..c3ced9d5 100644 --- a/web/src/components/context-continuity-panel.tsx +++ b/web/src/components/context-continuity-panel.tsx @@ -521,7 +521,9 @@ function ProjectInstructionsSection({ client, query, refreshing, refreshError, o } function retentionTimestamp(value: string): string | undefined { - return value ? new Date(`${value}T23:59:59.000Z`).toISOString() : undefined; + if (!value) return undefined; + const [year, month, day] = value.split("-").map(Number); + return new Date(year, month - 1, day, 23, 59, 59, 999).toISOString(); } function memoryReferenceList(value: string): string[] { From a148abac9e4d03a233b54e45e9e12bb0ed1a43e0 Mon Sep 17 00:00:00 2001 From: Qoder-AI <215799558+Qoder-AI@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:37:50 +0800 Subject: [PATCH 2/3] fix(web): prefill memory retention from the local calendar day --- .../context-continuity-panel.test.tsx | 22 ++++++++++++++++++- .../components/context-continuity-panel.tsx | 11 +++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/web/src/components/context-continuity-panel.test.tsx b/web/src/components/context-continuity-panel.test.tsx index 1c6ccdd2..d23ba446 100644 --- a/web/src/components/context-continuity-panel.test.tsx +++ b/web/src/components/context-continuity-panel.test.tsx @@ -1,5 +1,5 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import type { APIClient } from "../api/client"; import { downloadTextFile } from "../lib/download"; @@ -107,6 +107,26 @@ describe("ContextContinuityPanel", () => { expect.stringMatching(/^web-memory-create-/))); }); + it("opens the memory editor on the retained local calendar day", async () => { + const user = userEvent.setup(); + // Local midnight makes the UTC date differ from the local day in non-UTC time zones. + const retentionUntil = new Date(2030, 2, 15, 0, 30).toISOString(); + const get = vi.fn().mockImplementation((path: string) => { + if (path.includes("project-instructions")) return Promise.resolve(projectInstructionState()); + if (path === "/memories") return Promise.resolve([{ ...memoryView(), retention_until: retentionUntil }]); + if (path === "/memory-candidates") return Promise.resolve([]); + if (path.includes("/tree")) return Promise.resolve(sessionTree()); + return Promise.reject(new Error(`unexpected read ${path}`)); + }); + renderPanel({ hasControl: true, get, postControl: vi.fn(), patchControl: vi.fn(), + deleteControl: vi.fn() } as unknown as APIClient); + + await screen.findByText("Test convention"); + await user.click(screen.getByRole("button", { name: "Edit" })); + const article = screen.getByText("Test convention").closest("article"); + expect(within(article as HTMLElement).getByLabelText("Retain until")).toHaveValue("2030-03-15"); + }); + it("keeps mutation controls disabled on a read-only connection", async () => { renderPanel({ hasControl: false, get: vi.fn().mockImplementation((path: string) => path.includes("project-instructions") diff --git a/web/src/components/context-continuity-panel.tsx b/web/src/components/context-continuity-panel.tsx index c3ced9d5..bd17d865 100644 --- a/web/src/components/context-continuity-panel.tsx +++ b/web/src/components/context-continuity-panel.tsx @@ -339,7 +339,7 @@ export function ContextContinuityPanel({ client, runID, sessionID, workspaceID, setEditingMemoryID(memory.id); setEditDraft({ title: memory.title, content: memory.content, sourceRef: memory.source_ref ?? "", references: memory.references.join("\n"), - retention: memory.retention_until?.slice(0, 10) ?? "", + retention: retentionDateValue(memory.retention_until), redactSensitive: memory.redacted }); }} type="button">{t("编辑", "Edit")}