From c311c7d7f872783a7ccbc3ca7aa5f37575836e70 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Thu, 12 Nov 2026 16:45:11 +0800 Subject: [PATCH] docs(dashboard): explain directory and identity lifecycle --- src/app/dashboard/dashboard.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app/dashboard/dashboard.tsx b/src/app/dashboard/dashboard.tsx index e946ec0..74f0fef 100644 --- a/src/app/dashboard/dashboard.tsx +++ b/src/app/dashboard/dashboard.tsx @@ -55,6 +55,7 @@ export default function DashboardSidebar() { // 路径workspace优先于查询和本地选择;slug由目录解析,活动board可补充该路由引用的真实ID。 const workspaceRef = params.workspace || searchParams.get("workspace") || selectedWorkspace; const currentWorkspace = workspaces.find((item) => matchesRef(item, workspaceRef)); + // 目录未解析时可暂用路由 ref;这里的字符串回退不是“空间存在/有权限”的证明,后续服务请求仍需验证。 const workspace = currentWorkspace?.id || (activeBoard?.workspace_ref === workspaceRef ? activeBoard.workspace_id : workspaceRef); /** demo用本地资料,真实模式并行加载profile/空间;active+signal阻止卸载后的资料结果写入。 */ @@ -68,6 +69,7 @@ export default function DashboardSidebar() { } let active = true; const controller = new AbortController(); + // 只切换加载状态,不清空之前 profile/workspaces/error;成功读取也不会在这里自动清除旧 error。 setStatus("loading"); loadWorkspaceHome(api, controller.signal) .then(({ profile, workspaces }) => { @@ -89,6 +91,7 @@ export default function DashboardSidebar() { /** 每次目录加载先留本空间项,再取完整分页;仅最新listVersion可采用列表,换空间时中断旧请求。 */ useEffect(() => { if (!workspace || status !== "ready") return; + // listVersion 只保护目录结果采用,不是网络取消或账号代次;创建成功时也递增它,防较旧列表覆盖新项。 const version = ++listVersion.current; let active = true; setBoards((list) => list.filter((board) => board.workspace_id === workspace)); @@ -120,6 +123,7 @@ export default function DashboardSidebar() { setCreating(true); setError(""); try { + // 捕获触发本次创建的 workspace/demo;await 后不另核对挂载/账户,宿主生命周期应避免旧操作采用到新界面。 const board = demo ? { id: crypto.randomUUID(), workspace_id: workspace, title: DEFAULT_BOARD_TITLE, kind: "board" as const, version: 1 } : await createBoard(api, DEFAULT_BOARD_TITLE, workspace, (created) => { @@ -128,6 +132,7 @@ export default function DashboardSidebar() { }); listVersion.current += 1; setBoards((list) => [board, ...list]); + // demo 已先更新 React 列表,随后 Storage 写失败也不回滚该列表;错误仅报告在侧栏。 if (demo) localStorage.setItem("endoleap_demo_boards_" + workspace, JSON.stringify([board, ...readDemoBoards(workspace)])); setQuery(""); router.push(boardHref(currentWorkspace?.slug || board.workspace_id, board.slug || board.id, demo)); @@ -158,6 +163,7 @@ export default function DashboardSidebar() { } /** 真实退出先retireIdentity并完成本地关闭/保存,再请求服务登出;失败留在当前界面报告,不跳过保存。 */ + // 此函数无独立 busy/ref 互斥;多次点击仍可调用。退休身份成功但上游登出失败时也不会复活旧 runtime。 async function logout() { try { if (!demo) { await runtimeHost.retireIdentity(); await api("auth/logout", { method: "POST" }); } @@ -177,6 +183,7 @@ export default function DashboardSidebar() { /** 成功后同时更新本地目录与匹配的活动board元数据;错误转换成文案交EditableTitle保留草稿显示。 */ async function renameTitle(title: string) { + // 优先使用 activeBoard.id;编辑按钮先约束路由/空间匹配,此函数本身不重新执行同一组匹配检查。 const id = activeBoard?.id || params.board; if (!id || !workspace) return; try { @@ -184,6 +191,7 @@ export default function DashboardSidebar() { const list = readDemoBoards(workspace); localStorage.setItem("endoleap_demo_boards_" + workspace, JSON.stringify(list.map((board) => board.id === id ? { ...board, title } : board))); } else await renameBoard(api, workspace, id, title); + // 目录更新按 ID 匹配,活动元数据更新额外比 workspace;此处不递增 listVersion,旧目录读取仍可能随后到达。 setBoards((list) => list.map((board) => board.id === id ? { ...board, title } : board)); setActiveBoard((board) => board?.id === id && board.workspace_id === workspace ? { ...board, title } : board); } catch (error) { throw new Error(errorText(error)); } @@ -196,6 +204,7 @@ export default function DashboardSidebar() { const transition = { duration: reduceMotion ? 0 : 0.24, ease: [0.22, 1, 0.36, 1] as const }; const roleLabel = currentWorkspace?.role === "admin" ? "管理员" : currentWorkspace?.role === "editor" ? "编辑者" : currentWorkspace?.role === "viewer" ? "查看者" : "工作空间"; + // 折叠只改变显示和键盘可达性,侧栏仍挂载,账户/目录状态和 effect 不因收起而销毁。 // 展示分为浮动条、侧栏目录、共享账户popover和账户面板;路由/空间作为key让标题及目录局部状态重置。 return ( <>