docs(dashboard): explain directory and identity lifecycle

This commit is contained in:
2026-11-12 16:45:11 +08:00
parent 923c82f366
commit c311c7d7f8
+9
View File
@@ -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 (
<>