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: [], }, },