test(temporary-notes): cover the create race-guard, broadcast deadline + cache patch; unify page->tree-node mappers
Address review comment 2159 on the temporary-notes UI work. Tests: - tree-model: cover handleCreate's race-guard temporaryExpiresAt patch — (a) server node inserted WITHOUT a deadline + create response carries one => node gains the deadline; (b) node already has a deadline => not overwritten, prev returned by reference. - ws-tree.service.spec: broadcastPageCreated now asserts the deadline is carried when present and pinned to null (`?? null`) when absent. - page-embed-query (new spec): syncTemporaryExpiresInCache patches the in-tree node's temporaryExpiresAt, and leaves the atom value at the same reference when the id is absent from the loaded tree (no write). Refactor (closes the drift bug-class at the root): - Client: extract one canonical pageToTreeNode(page, overrides) mapper in tree/utils and route buildTree, handleCreate's optimistic insert, the restore mutation and the duplicate handler through it. Restore stays permanent (server nulls temporaryExpiresAt) and duplicate stays permanent (server arms no timer) — both now reflect the server without a reload, where before they dropped the field entirely. - Server: extract one toTreeNodeSnapshot(page) helper called by both the PAGE_CREATED event enrichment (page.repo) and the addTreeNode broadcast (ws-tree.service), so the optional temporaryExpiresAt can't drift between the two literals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ import {
|
||||
} from "@/features/page-embed/queries/page-embed-query";
|
||||
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts";
|
||||
import { treeModel } from "@/features/page/tree/model/tree-model";
|
||||
import { pageToTreeNode } from "@/features/page/tree/utils";
|
||||
import { useTreeMutation } from "@/features/page/tree/hooks/use-tree-mutation.ts";
|
||||
import type { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
||||
import classes from "@/features/page/tree/styles/tree.module.css";
|
||||
@@ -130,18 +131,14 @@ export function NodeMenu({ node, canEdit }: NodeMenuProps) {
|
||||
const currentIndex = siblings?.index ?? 0;
|
||||
const newIndex = currentIndex + 1;
|
||||
|
||||
const treeNodeData: SpaceTreeNode = {
|
||||
id: duplicatedPage.id,
|
||||
slugId: duplicatedPage.slugId,
|
||||
name: duplicatedPage.title,
|
||||
position: duplicatedPage.position,
|
||||
spaceId: duplicatedPage.spaceId,
|
||||
parentPageId: duplicatedPage.parentPageId,
|
||||
icon: duplicatedPage.icon,
|
||||
hasChildren: duplicatedPage.hasChildren,
|
||||
// Routed through the canonical mapper so the field copy stays in lockstep
|
||||
// with buildTree. The server does NOT arm a death timer on duplicate (the
|
||||
// copy's `temporaryExpiresAt` defaults to null = permanent), so the mapper
|
||||
// carries that null through and the duplicated node correctly shows no
|
||||
// clock marker — matching the server without a reload.
|
||||
const treeNodeData: SpaceTreeNode = pageToTreeNode(duplicatedPage, {
|
||||
canEdit: true,
|
||||
children: [],
|
||||
};
|
||||
});
|
||||
|
||||
setData((prev) =>
|
||||
treeModel.insert(prev, parentId, treeNodeData, newIndex),
|
||||
|
||||
Reference in New Issue
Block a user