feat: unify workspace sidebar and create Foundry graph pages

This commit is contained in:
2026-11-05 23:57:45 +08:00
parent 6ca4b00d2c
commit 71bf8b0f5e
15 changed files with 417 additions and 238 deletions
+31 -2
View File
@@ -4,11 +4,25 @@ import { spawn } from "node:child_process";
import { once } from "node:events";
import { fileURLToPath } from "node:url";
const host = "http://127.0.0.1:3005";
const upstream = createServer((req, res) => {
const graphs = new Map();
const upstream = createServer(async (req, res) => {
res.setHeader("Content-Type", "application/json");
if (req.url.endsWith("/auth/logout")) {
res.statusCode = 204;
res.end();
} else if (req.url === "/projects/endoleap/workspaces/space/materials" && req.method === "POST") {
assert.equal(req.headers.authorization, "Bearer test-only-session");
let raw = "";
for await (const chunk of req) raw += chunk;
const input = JSON.parse(raw);
assert.deepEqual(input, { kind: "board", title: "未命名图谱", content: { schema_version: 1 } });
const graph = { ...input, id: "foundry-generated-id", workspace_id: "space", version: 1 };
graphs.set(graph.id, graph);
res.statusCode = 201;
res.end(JSON.stringify({ id: graph.id, workspace_id: graph.workspace_id, kind: graph.kind, version: graph.version }));
} else if (req.url === "/projects/endoleap/workspaces/space/materials/foundry-generated-id") {
assert.equal(req.headers.authorization, "Bearer test-only-session");
res.end(JSON.stringify(graphs.get("foundry-generated-id")));
} else if (req.url.includes("/workspaces/space/materials")) {
res.end(JSON.stringify({ url: req.url, method: req.method }));
} else if (req.url.endsWith("/auth/login")) {
@@ -69,7 +83,7 @@ try {
await new Promise((r) => setTimeout(r, 100));
}
assert(ready, "Frontend did not start");
for (const path of ["/", "/login", "/auth/callback", "/dashboard", "/dashboard?demo=1"]) {
for (const path of ["/", "/login", "/auth/callback", "/dashboard", "/dashboard?demo=1", "/dashboard/space/foundry-generated-id"]) {
const page = await fetch(host + path);
assert.equal(page.status, 200, path);
const html = await page.text();
@@ -102,6 +116,21 @@ try {
200,
);
assert.equal((await fetch(host + "/api/foundry/profile")).status, 401);
const create = await fetch(host + "/api/foundry/workspaces/space/materials", {
method: "POST",
headers: { "Content-Type": "application/json", Cookie: session, Origin: host },
body: JSON.stringify({ kind: "board", title: "未命名图谱", content: { schema_version: 1 } }),
});
assert.equal(create.status, 201);
const graph = await create.json();
assert.deepEqual(graph, { id: "foundry-generated-id", workspace_id: "space", kind: "board", version: 1 });
const opened = await fetch(host + `/api/foundry/workspaces/${graph.workspace_id}/materials/${graph.id}`, { headers: { Cookie: session } });
assert.equal(opened.status, 200);
const stored = await opened.json();
assert.equal(stored.title, "未命名图谱");
assert.equal(stored.workspace_id, "space");
assert.equal(stored.content.schema_version, 1);
for (const origin of ["https://example.com", "null"])
assert.equal(
(
+21 -1
View File
@@ -1,6 +1,6 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { createBoard, loadWorkspaceHome } from "../src/lib/workspace.ts";
import { boardHref, createBoard, DEFAULT_BOARD_TITLE, loadWorkspaceHome } from "../src/lib/workspace.ts";
test("profile and workspace reads both start before either completes", async () => {
const calls = [];
@@ -80,3 +80,23 @@ test("an existing workspace posts a board directly", async () => {
);
assert.deepEqual(calls, ["workspaces/existing/materials"]);
});
test("new graphs use a default name and retain the Foundry-assigned identity", async () => {
const created = await createBoard(async (path, options) => {
assert.equal(path, "workspaces/space/materials");
assert.deepEqual(JSON.parse(options.body), {
kind: "board", title: DEFAULT_BOARD_TITLE, content: { schema_version: 1 },
});
return { id: "server-generated-id", workspace_id: "space", kind: "board", version: 1 };
}, " ", "space", () => assert.fail("unexpected workspace creation"));
assert.equal(created.id, "server-generated-id");
assert.equal(created.workspace_id, "space");
assert.equal(created.title, "未命名图谱");
assert.equal(boardHref(created.workspace_id, created.id), "/dashboard/space/server-generated-id");
});
test("graph URLs preserve workspace identity and safely encode route segments", () => {
assert.equal(boardHref("space/a", "id?b"), "/dashboard/space%2Fa/id%3Fb");
assert.equal(boardHref("demo", "board", true), "/dashboard/demo/board?demo=1");
});