From 12ff76fb89e4a2e4a0c6b40c3529601db9851f76 Mon Sep 17 00:00:00 2001 From: claude code agent 227 Date: Sat, 27 Jun 2026 00:29:19 +0300 Subject: [PATCH] fix(temporary-notes): live sidebar clock marker + stacked mobile create buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue 1 — the sidebar tree's temporary-note clock marker did not appear/ disappear until a page reload when a note's temporary state changed. - Make/unmake permanent from the page header menu and the in-page banner went through syncTemporaryExpiresInCache(), which patched the page query cache but never touched treeDataAtom, so the sidebar node kept its stale temporaryExpiresAt. Patch the tree node there too (via jotai's default store), so the marker updates without a reload. - Creating a note as temporary showed no marker until reload: the create flow's cache write (invalidateOnCreatePage) omitted temporaryExpiresAt, so the tree rebuild (buildTree -> mergeRootTrees) overwrote the optimistic/socket node's marker with undefined. Carry temporaryExpiresAt in that cached entry. - Thread temporaryExpiresAt through the server addTreeNode broadcast (PAGE_CREATED snapshot -> TreeNodeSnapshot -> broadcastPageCreated) so OTHER clients watching the space also render the marker immediately, and harden handleCreate's idempotency guard to patch the deadline if the broadcast won the insert race. Issue 2 — the home and space-overview "New note" / "New temporary note" buttons sat side-by-side and the temporary label clipped on narrow mobile widths. Lay them out full-width, stacked vertically, and tint the temporary button orange (matching the clock marker + banner) while the regular one stays neutral gray. Tests: extend tree-socket-reducers.test.ts (addTreeNode carries temporaryExpiresAt). Verified live with Playwright: marker appears on create and toggles both ways with no reload; mobile buttons are stacked, full-width, unclipped, and differently colored. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../home/components/new-note-button.tsx | 24 +++++++++++++------ .../page-embed/queries/page-embed-query.ts | 17 +++++++++++++ .../components/header/page-header-menu.tsx | 4 ++-- .../src/features/page/queries/page-query.ts | 5 ++++ .../page/tree/hooks/use-tree-mutation.ts | 17 ++++++++++++- .../components/space-create-note-buttons.tsx | 13 ++++++---- .../websocket/tree-socket-reducers.test.ts | 14 +++++++++++ .../src/database/listeners/page.listener.ts | 5 ++++ .../src/database/repos/page/page.repo.ts | 3 +++ apps/server/src/ws/ws-tree.service.ts | 4 ++++ 10 files changed, 92 insertions(+), 14 deletions(-) diff --git a/apps/client/src/features/home/components/new-note-button.tsx b/apps/client/src/features/home/components/new-note-button.tsx index 69bcac37..43649bc6 100644 --- a/apps/client/src/features/home/components/new-note-button.tsx +++ b/apps/client/src/features/home/components/new-note-button.tsx @@ -1,4 +1,4 @@ -import { Button, Group, Menu, Text } from "@mantine/core"; +import { Button, Menu, Stack, Text } from "@mantine/core"; import { IconHourglass, IconPlus } from "@tabler/icons-react"; import { ReactNode } from "react"; import { useNavigate } from "react-router-dom"; @@ -21,11 +21,15 @@ function CreateNoteButton({ temporary, label, icon, + color, }: { writableSpaces: ISpace[]; temporary: boolean; label: string; icon: ReactNode; + // Mantine color token; lets the temporary action tint toward the warm + // orange/amber used by the clock marker + banner while "New note" stays neutral. + color: string; }) { const { t } = useTranslation(); const navigate = useNavigate(); @@ -54,7 +58,8 @@ function CreateNoteButton({ - + ); } diff --git a/apps/client/src/features/websocket/tree-socket-reducers.test.ts b/apps/client/src/features/websocket/tree-socket-reducers.test.ts index 20abdf95..81c62b56 100644 --- a/apps/client/src/features/websocket/tree-socket-reducers.test.ts +++ b/apps/client/src/features/websocket/tree-socket-reducers.test.ts @@ -323,4 +323,18 @@ describe("applyAddTreeNode", () => { "child", ]); }); + + it("carries temporaryExpiresAt onto the inserted node so the clock marker shows on create (no reload)", () => { + // A note created as temporary broadcasts addTreeNode with the death-timer + // deadline in its payload; the receiver's inserted node must keep it so + // space-tree-row renders the orange clock marker immediately. + const tree = roots(); + const expiresAt = "2026-06-27T21:00:00.000Z"; + const next = applyAddTreeNode(tree, { + parentId: null as unknown as string, + index: 0, + data: node("temp", { position: "a3", temporaryExpiresAt: expiresAt }), + }); + expect(treeModel.find(next, "temp")?.temporaryExpiresAt).toBe(expiresAt); + }); }); diff --git a/apps/server/src/database/listeners/page.listener.ts b/apps/server/src/database/listeners/page.listener.ts index 3a779aa3..52b9f963 100644 --- a/apps/server/src/database/listeners/page.listener.ts +++ b/apps/server/src/database/listeners/page.listener.ts @@ -21,6 +21,11 @@ export interface TreeNodeSnapshot { position: string; spaceId: string; parentPageId: string | null; + // Death-timer deadline carried so the `addTreeNode` broadcast shows the + // temporary-note clock marker immediately on every client (incl. the author, + // whose optimistic insert can lose the race to this broadcast). null/absent => + // permanent. + temporaryExpiresAt?: Date | string | null; } export class PageEvent { diff --git a/apps/server/src/database/repos/page/page.repo.ts b/apps/server/src/database/repos/page/page.repo.ts index 45cb57ab..1b711750 100644 --- a/apps/server/src/database/repos/page/page.repo.ts +++ b/apps/server/src/database/repos/page/page.repo.ts @@ -209,6 +209,9 @@ export class PageRepo { position: result.position, spaceId: result.spaceId, parentPageId: result.parentPageId, + // Carry the death-timer deadline so a note created as temporary shows + // its sidebar clock marker on every client without a reload. + temporaryExpiresAt: result.temporaryExpiresAt, }, ], }); diff --git a/apps/server/src/ws/ws-tree.service.ts b/apps/server/src/ws/ws-tree.service.ts index 223fb25c..15b4dd87 100644 --- a/apps/server/src/ws/ws-tree.service.ts +++ b/apps/server/src/ws/ws-tree.service.ts @@ -38,6 +38,10 @@ export class WsTreeService { spaceId: page.spaceId, parentPageId: page.parentPageId, hasChildren: false, + // Carry the death-timer deadline so receivers (and the author, if this + // broadcast wins the race against the optimistic insert) render the + // temporary-note clock marker immediately. null => permanent. + temporaryExpiresAt: page.temporaryExpiresAt ?? null, children: [], }, },