paintress 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,13 +8,13 @@
8
8
 
9
9
  ## What it is
10
10
 
11
- paintress reads and writes structured design documents — JSON files with a documented schema (`.pen`, and its own `.ptr` for anything it writes). It renders them to HTML, serves a local viewer, and exposes the same operations as MCP tools and as a CLI.
11
+ paintress reads and writes structured design documents — JSON files with a documented schema. **`.ptr` is paintress's standard format**: everything paintress creates is `.ptr`. **`.pen` is compatible**: read, edited and rendered the same way, never converted behind your back. It renders documents to HTML, serves a local viewer, and exposes the same operations as MCP tools and as a CLI.
12
12
 
13
13
  It is not an editor. There is no canvas, no drag-and-drop, no property panel. A human looks at the design in a browser and clicks nodes to point at them; the agent does the reading and writing.
14
14
 
15
15
  | Part | Purpose |
16
16
  | --- | --- |
17
- | MCP tools | query, read, update, insert, delete, move, batch, validate, render, screenshot |
17
+ | MCP tools | create, query, read, update, insert, delete, move, batch, validate, render, screenshot |
18
18
  | CLI | the same operations plus the viewer process, for use without an agent |
19
19
  | Viewer | local web server: renders a document, highlights changes, hands back clicked nodes as `#1`, `#2`, … |
20
20
  | Screenshot | PNG of a whole canvas, for agents that can look at images |
@@ -43,6 +43,9 @@ paintress install pi cursor # or only the ones you name
43
43
 
44
44
  ```text
45
45
  paintress mcp start the stdio MCP server
46
+ paintress new <file> [--name n] [--width n] [--height n] [--fill c]
47
+ [--layout vertical|horizontal|none] [--padding n] [--gap n] [--force]
48
+ create a .ptr (one empty board)
46
49
  paintress info <file> document summary
47
50
  paintress query <file> [--type t] [--name n|--regex] find nodes
48
51
  paintress get <file> <id> [--depth n] read a node subtree
@@ -75,6 +78,8 @@ paintress watch demo ./demo
75
78
 
76
79
  Canvas navigation: wheel zooms at the pointer, space / middle button / empty area drags to pan, `Ctrl/Cmd+0` returns to 100%, `Shift+1` fits the window. Files with more than one board get a board strip. Edits made through the CLI or MCP are highlighted in the viewer, and clicks assign short ids that the agent can read back with `paintress_marks`.
77
80
 
81
+ The toolbar's right side has two viewer-only controls. **Canvas background** switches Light / Gray / Dark for the design surface without touching the rest of the UI (click a stop or drag the knob). **Language** switches the interface between 中文 and EN; on first load it follows the browser, and anything that is not Chinese gets English. Both are per-browser and remembered locally. Registering a project, or dropping a new `.ptr` / `.pen` into a watched directory, updates every open viewer without a reload.
82
+
78
83
  ## MCP clients
79
84
 
80
85
  `paintress install` writes these; the same entries can be added by hand.
@@ -101,9 +106,25 @@ paintress mcp-config cursor # print the exact snippet instead of editing files
101
106
 
102
107
  - `specs/PLAN.md` — roadmap and open questions
103
108
  - `format/spec.yaml` — the document format spec (the `.pen` profile is frozen; `.ptr` evolves)
109
+ - `format/README.md` — how the two layers relate, and why `.ptr` is the standard format
104
110
  - `.agents/memory/ARCHITECTURE.md` — design decisions and their rationale
105
111
  - `.agents/implementations/` — per-change records with measurements
106
112
 
113
+ ## Release (maintainers)
114
+
115
+ Publishing is tag-driven: a `v*` tag makes `.github/workflows/publish.yml` build, test and `npm publish --provenance` through OIDC trusted publishing. Do not publish from a laptop.
116
+
117
+ ```bash
118
+ pnpm release patch # 0.3.1 → 0.3.2
119
+ pnpm release minor # 0.3.1 → 0.4.0
120
+ pnpm release major # 0.3.1 → 1.0.0
121
+ pnpm release patch --dry-run # print the plan only
122
+ ```
123
+
124
+ The script refuses to run on a dirty worktree or a non-default branch, verifies the tag is free, runs typecheck (server + ui), the test suite, the build and the package-contents check, then commits `chore(release): vX.Y.Z`, tags it, pushes branch + tag, and finally waits until the version is actually on npm (a green run that published nothing is reported as a failure, not a success).
125
+
126
+ `--prepare-only` stops after the local commit and tag; `--no-wait` returns right after the push; `--local` publishes from this machine as an emergency path and skips the CI checks.
127
+
107
128
  ## License
108
129
 
109
130
  MIT
@@ -205,7 +226,7 @@ paintress doctor # entry present, command
205
226
  printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}\n' | paintress mcp
206
227
  ```
207
228
 
208
- The second command must answer with a `result` containing `"serverInfo": {"name": "paintress", …}`. After a session restart, `paintress_doc_info` on any `.pen` file is the end-to-end check.
229
+ The second command must answer with a `result` containing `"serverInfo": {"name": "paintress", …}`. After a session restart, `paintress_doc_info` on any `.ptr` or `.pen` file is the end-to-end check.
209
230
 
210
231
  ### 5. Multi-agent rules
211
232
 
package/README.zh-CN.md CHANGED
@@ -8,13 +8,13 @@
8
8
 
9
9
  ## 这是什么
10
10
 
11
- paintress 读写结构化的设计稿——带公开 schema 的 JSON 文件(`.pen`,以及它自己写出的 `.ptr`)。它把设计稿渲染成 HTML,起一个本地查看器,并把同一套操作同时暴露为 MCP 工具和 CLI。
11
+ paintress 读写结构化的设计稿——带公开 schema 的 JSON 文件。**`.ptr` 是 paintress 的标准格式**:paintress 创建的一切都是 `.ptr`。**`.pen` 兼容**:读取、编辑、渲染完全一致,绝不在背后悄悄转换。它把设计稿渲染成 HTML,起一个本地查看器,并把同一套操作同时暴露为 MCP 工具和 CLI。
12
12
 
13
13
  它不是编辑器:没有画布、没有拖拽、没有属性面板。人在浏览器里看设计稿并点击节点指认目标,agent 负责读写。
14
14
 
15
15
  | 组成 | 作用 |
16
16
  | --- | --- |
17
- | MCP 工具 | 查询、读取、更新、插入、删除、移动、批量、校验、渲染、截图 |
17
+ | MCP 工具 | 新建、查询、读取、更新、插入、删除、移动、批量、校验、渲染、截图 |
18
18
  | CLI | 同样的操作,外加查看器进程;不接 agent 也能用 |
19
19
  | 查看器 | 本地 web 服务:渲染设计稿、高亮变更、把点击的节点变成 `#1`、`#2`… |
20
20
  | 截图 | 整张画布的 PNG,给能看图的 agent 用 |
@@ -43,6 +43,9 @@ paintress install pi cursor # 或只写指定的客户端
43
43
 
44
44
  ```text
45
45
  paintress mcp 启动 stdio MCP server
46
+ paintress new <file> [--name n] [--width n] [--height n] [--fill c]
47
+ [--layout vertical|horizontal|none] [--padding n] [--gap n] [--force]
48
+ 新建 .ptr(一个空画板)
46
49
  paintress info <file> 文档摘要
47
50
  paintress query <file> [--type t] [--name n|--regex] 条件查询节点
48
51
  paintress get <file> <id> [--depth n] 读取节点子树
@@ -75,6 +78,8 @@ paintress watch demo ./demo
75
78
 
76
79
  画布操作:滚轮以指针为锚点缩放,空格 / 中键 / 空白处拖拽平移,`Ctrl/Cmd+0` 回到 100%,`Shift+1` 适应窗口。多画板文件会显示画板条。通过 CLI 或 MCP 做的修改会在查看器里高亮,点击会让节点获得短号,agent 用 `paintress_marks` 读回。
77
80
 
81
+ 工具条右侧有两个只影响查看的开关。**画布背景**切 Light / Gray / Dark,只改画布区底色,不动界面其它部分(点档位或拖滑块)。**界面语言**在中 / EN 之间切;首次打开跟随浏览器语言,不是中文就是英文。两者都按浏览器记住。注册新项目、或往已跟踪目录里放一个新的 `.ptr` / `.pen`,所有已打开的查看器都会自动刷新,不需要手按 F5。
82
+
78
83
  ## MCP 客户端
79
84
 
80
85
  `paintress install` 会写入下面这些位置,也可以手动添加。
@@ -101,9 +106,25 @@ paintress mcp-config cursor # 只打印片段,不改文件
101
106
 
102
107
  - `specs/PLAN.md` —— 路线与未决问题
103
108
  - `format/spec.yaml` —— 文件格式规格(`.pen` 层冻结,`.ptr` 层演进)
109
+ - `format/README.md` —— 两层关系,以及为什么 `.ptr` 是标准格式
104
110
  - `.agents/memory/ARCHITECTURE.md` —— 设计决策与理由
105
111
  - `.agents/implementations/` —— 每次变更的记录与实测数据
106
112
 
113
+ ## 发版(维护者)
114
+
115
+ 发布由 tag 驱动:`v*` tag 会让 `.github/workflows/publish.yml` 构建、测试并通过 OIDC trusted publishing 执行 `npm publish --provenance`。不要在笔记本上直接发布。
116
+
117
+ ```bash
118
+ pnpm release patch # 0.3.1 → 0.3.2
119
+ pnpm release minor # 0.3.1 → 0.4.0
120
+ pnpm release major # 0.3.1 → 1.0.0
121
+ pnpm release patch --dry-run # 只打印计划
122
+ ```
123
+
124
+ 脚本会拒绝在脏工作区或非默认分支上发版,确认 tag 未被占用,跑类型检查(服务端 + ui)、测试、构建与包内容自检,然后提交 `chore(release): vX.Y.Z`、打 tag、推分支与 tag,最后等到版本真的出现在 npm 上才算完(CI 绿了但什么都没发出去,会如实报成失败)。
125
+
126
+ `--prepare-only` 停在本地提交与 tag;`--no-wait` 推完就返回;`--local` 本机直接发布,是应急路径,会跳过 CI 的检查。
127
+
107
128
  ## 许可
108
129
 
109
130
  MIT
@@ -205,7 +226,7 @@ paintress doctor # 条目存在、命令
205
226
  printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}\n' | paintress mcp
206
227
  ```
207
228
 
208
- 第二条命令应当返回包含 `"serverInfo": {"name": "paintress", …}` 的 `result`。会话重启后,对任意 `.pen` 文件调用 `paintress_doc_info` 是一次端到端验证。
229
+ 第二条命令应当返回包含 `"serverInfo": {"name": "paintress", …}` 的 `result`。会话重启后,对任意 `.ptr` 或 `.pen` 文件调用 `paintress_doc_info` 是一次端到端验证。
209
230
 
210
231
  ### 5. 多 agent 规则
211
232
 
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { a as isDaemonRunning, c as pidPath, d as sanitizeProjectName, f as socketPath, i as attachPersistent, l as resolveProjectDir, m as encodeLine, n as runStdioServer, o as sendRequest, p as LineDecoder, r as TOOLS, s as lockPath, u as safeJoin } from "./server-C4TC8Bw3.js";
3
- import { Wt as VERSION, t as renderDocument, w as loadDoc } from "./render-CbYe9rII.js";
2
+ import { a as isDaemonRunning, c as pidPath, d as sanitizeProjectName, f as socketPath, i as attachPersistent, l as resolveProjectDir, m as encodeLine, n as runStdioServer, o as sendRequest, p as LineDecoder, r as TOOLS, s as lockPath, u as safeJoin } from "./server-19PJh0Vn.js";
3
+ import { Kt as VERSION, t as renderDocument, w as loadDoc } from "./render-YpsrjGNy.js";
4
4
  import "./index.js";
5
5
  import { chmodSync, closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
6
6
  import { homedir } from "node:os";
7
7
  import { fileURLToPath } from "node:url";
8
- import { dirname, extname, join, relative, resolve } from "node:path";
8
+ import { dirname, extname, join, relative, resolve, sep } from "node:path";
9
9
  import { createServer } from "node:net";
10
10
  import { createServer as createServer$1 } from "node:http";
11
11
  import { readFile } from "node:fs/promises";
@@ -217,8 +217,37 @@ var ChangesStore = class ChangesStore {
217
217
  }
218
218
  };
219
219
  // src/web/watcher.ts
220
+ /**
221
+ * 按需文件监听:正在查看的文件有浏览器 SSE 连接时才 watch(引用计数)。
222
+ * 注册表另有 design 目录监听:chokidar 按目录排除规则与深度过滤,
223
+ * 不把 node_modules / .git 等无关目录纳入 watch,避免耗尽 inotify 配额。
224
+ */
225
+ const IGNORED_DIRS = /* @__PURE__ */ new Set([
226
+ "node_modules",
227
+ ".git",
228
+ "dist",
229
+ "build",
230
+ ".next",
231
+ "target",
232
+ ".venv",
233
+ "venv",
234
+ "__pycache__"
235
+ ]);
236
+ /** 与 Registry.listDesigns 同一套列举边界:隐藏项、构建目录、深度、扩展名 */
237
+ function designIgnored(root, depth, path, stats) {
238
+ if (path === root) return false;
239
+ const parts = relative(root, path).split(sep).filter(Boolean);
240
+ if (parts.length === 0) return false;
241
+ if (parts.some((part) => part.startsWith(".") || IGNORED_DIRS.has(part))) return true;
242
+ const maxParts = stats?.isDirectory() === true ? depth : depth + 1;
243
+ if (parts.length > maxParts) return true;
244
+ if (stats?.isDirectory() === true) return false;
245
+ if (stats === void 0 && extname(path) === "") return false;
246
+ return !DESIGN_EXT.has(extname(path).toLowerCase());
247
+ }
220
248
  var WatchManager = class {
221
249
  entries = /* @__PURE__ */ new Map();
250
+ designs = /* @__PURE__ */ new Map();
222
251
  /** 取得对某文件的监听;返回释放函数 */
223
252
  acquire(absPath, onChange) {
224
253
  let entry = this.entries.get(absPath);
@@ -265,10 +294,49 @@ var WatchManager = class {
265
294
  size() {
266
295
  return this.entries.size;
267
296
  }
297
+ /**
298
+ * 项目设计稿目录监听:仅 .ptr / .pen 的增删会触发,且遵守项目扫描深度。
299
+ * 相同 root + depth 重复调用复用同一 watcher;depth 变化时重建。
300
+ */
301
+ watchDesigns(root, depth, onChange) {
302
+ const existing = this.designs.get(root);
303
+ if (existing?.depth === depth) return existing;
304
+ if (existing) existing.close();
305
+ const watcher = chokidar.watch(root, {
306
+ ignoreInitial: true,
307
+ ignored: (path, stats) => designIgnored(root, depth, path, stats),
308
+ awaitWriteFinish: {
309
+ stabilityThreshold: 80,
310
+ pollInterval: 20
311
+ },
312
+ persistent: true
313
+ });
314
+ const handle = {
315
+ depth,
316
+ ready: new Promise((resolve) => watcher.once("ready", () => resolve())),
317
+ close: async () => {
318
+ if (this.designs.get(root) === handle) this.designs.delete(root);
319
+ await watcher.close();
320
+ }
321
+ };
322
+ const onDesignChange = (file) => {
323
+ if (DESIGN_EXT.has(extname(file).toLowerCase())) onChange(file);
324
+ };
325
+ this.designs.set(root, handle);
326
+ watcher.on("add", onDesignChange);
327
+ watcher.on("unlink", onDesignChange);
328
+ watcher.on("unlinkDir", onChange);
329
+ watcher.on("error", () => void handle.close());
330
+ return handle;
331
+ }
332
+ unwatchDesigns(root) {
333
+ this.designs.get(root)?.close();
334
+ }
268
335
  async closeAll() {
269
336
  const all = [...this.entries.values()];
270
337
  this.entries.clear();
271
338
  await Promise.all(all.map((e) => e.watcher.close()));
339
+ await Promise.all([...this.designs.values()].map((d) => d.close()));
272
340
  }
273
341
  };
274
342
  // src/web/daemon.ts
@@ -385,6 +453,36 @@ async function startDaemon(opts = {}) {
385
453
  const idleCheckMs = opts.idleCheckMs ?? 15e3;
386
454
  let idleSince = null;
387
455
  let closing = false;
456
+ /**
457
+ * 注册表订阅者(浏览器开着的查看器窗口)。
458
+ * 项目列表变化时推 `projects` 事件,避免用户按 F5 才看到新项目。
459
+ */
460
+ const registryClients = /* @__PURE__ */ new Set();
461
+ const notifyRegistry = () => {
462
+ const payload = JSON.stringify({
463
+ projects: registry.all().length,
464
+ at: Date.now()
465
+ });
466
+ for (const res of registryClients) try {
467
+ res.write(`event: projects\ndata: ${payload}\n\n`);
468
+ } catch {
469
+ registryClients.delete(res);
470
+ }
471
+ };
472
+ /**
473
+ * 目录监听:项目目录里新增/删除设计稿时也推 projects 事件,
474
+ * 让「agent 刚创建的文件」在已开着的窗口里自动出现(不靠 F5)。
475
+ * 合并 150ms:一次批量建多个文件只推一次。
476
+ */
477
+ let registryTimer = null;
478
+ const scheduleRegistryNotify = () => {
479
+ if (registryTimer !== null) return;
480
+ registryTimer = setTimeout(() => {
481
+ registryTimer = null;
482
+ notifyRegistry();
483
+ }, 150);
484
+ registryTimer.unref?.();
485
+ };
388
486
  /** 控制面:一个连接可同时作为 agent 存活信号 */
389
487
  const handleClient = (socket) => {
390
488
  const decoder = new LineDecoder();
@@ -432,19 +530,30 @@ async function startDaemon(opts = {}) {
432
530
  version: VERSION
433
531
  });
434
532
  break;
435
- case "watch":
533
+ case "watch": {
534
+ const entry = registry.watch(req.project, req.dir, req.depth);
535
+ watchers.watchDesigns(entry.rootReal, entry.depth, scheduleRegistryNotify);
436
536
  reply({
437
537
  ok: true,
438
538
  watched: true,
439
- project: registry.info(registry.watch(req.project, req.dir, req.depth))
539
+ project: registry.info(entry)
440
540
  });
541
+ notifyRegistry();
441
542
  break;
442
- case "unwatch":
543
+ }
544
+ case "unwatch": {
545
+ const before = registry.get(req.project);
546
+ const removed = registry.remove(req.project);
443
547
  reply({
444
548
  ok: true,
445
- unwatched: registry.remove(req.project)
549
+ unwatched: removed
446
550
  });
551
+ if (removed) {
552
+ if (before && !registry.all().some((p) => p.rootReal === before.rootReal)) watchers.unwatchDesigns(before.rootReal);
553
+ notifyRegistry();
554
+ }
447
555
  break;
556
+ }
448
557
  case "attach": {
449
558
  const entry = registry.get(req.project);
450
559
  if (!entry) throw new Error(`项目未注册:${req.project}`);
@@ -666,6 +775,21 @@ async function startDaemon(opts = {}) {
666
775
  if (segments[0] === "events") {
667
776
  const project = url.searchParams.get("project") ?? "";
668
777
  const relPath = url.searchParams.get("path") ?? "";
778
+ if (project === "") {
779
+ res.writeHead(200, {
780
+ "content-type": "text/event-stream",
781
+ "cache-control": "no-cache",
782
+ connection: "keep-alive"
783
+ });
784
+ res.write(": connected\n\n");
785
+ registryClients.add(res);
786
+ const registryKeepAlive = setInterval(() => res.write(": ping\n\n"), 2e4);
787
+ req.on("close", () => {
788
+ clearInterval(registryKeepAlive);
789
+ registryClients.delete(res);
790
+ });
791
+ return;
792
+ }
669
793
  const entry = registry.get(project);
670
794
  if (!entry) {
671
795
  send(404, "project not found", "text/plain; charset=utf-8");
@@ -832,6 +956,11 @@ async function startDaemon(opts = {}) {
832
956
  closing = true;
833
957
  if (!opts.quiet) process.stderr.write(`paintress web: 退出(${reason})\n`);
834
958
  clearInterval(idleTimer);
959
+ if (registryTimer !== null) clearTimeout(registryTimer);
960
+ for (const client of registryClients) try {
961
+ client.end();
962
+ } catch {}
963
+ registryClients.clear();
835
964
  await watchers.closeAll();
836
965
  await new Promise((resolve) => sockServer.close(() => resolve()));
837
966
  await new Promise((resolve) => httpServer.close(() => resolve()));
@@ -1145,13 +1274,21 @@ const FLAG_KEYS = /* @__PURE__ */ new Set([
1145
1274
  "idle",
1146
1275
  "daemon",
1147
1276
  "dry-run",
1148
- "opencode-layout"
1277
+ "opencode-layout",
1278
+ "width",
1279
+ "height",
1280
+ "fill",
1281
+ "layout",
1282
+ "padding",
1283
+ "gap",
1284
+ "force"
1149
1285
  ]);
1150
1286
  const BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
1151
1287
  "regex",
1152
1288
  "reusable",
1153
1289
  "daemon",
1154
- "dry-run"
1290
+ "dry-run",
1291
+ "force"
1155
1292
  ]);
1156
1293
  /** 纯函数:argv → 命令 + 位置参数 + 标志(可单测) */
1157
1294
  function parseArgv(argv) {
@@ -1219,7 +1356,7 @@ async function main(argv) {
1219
1356
  }
1220
1357
  const parsed = parseArgv(argv);
1221
1358
  if (!parsed) {
1222
- console.error("用法:paintress <mcp|web|ping|status|watch|unwatch|attach|stop|info|query|get|update|insert|delete|move|batch|render|shot|validate|install|doctor|mcp-config> ...(版本:paintress --version)");
1359
+ console.error("用法:paintress <mcp|web|ping|status|watch|unwatch|attach|stop|new|info|query|get|update|insert|delete|move|batch|render|shot|validate|install|doctor|mcp-config> ...(版本:paintress --version)");
1223
1360
  return 2;
1224
1361
  }
1225
1362
  const { cmd, positionals, flags } = parsed;
@@ -1418,6 +1555,27 @@ async function main(argv) {
1418
1555
  if (!file) throw new Error("缺少 <file> 参数");
1419
1556
  let out;
1420
1557
  switch (cmd) {
1558
+ case "new": {
1559
+ const name = flagStr(flags, "name");
1560
+ const width = flagNum(flags, "width");
1561
+ const height = flagNum(flags, "height");
1562
+ const fill = flagStr(flags, "fill");
1563
+ const layout = flagStr(flags, "layout");
1564
+ const padding = flagNum(flags, "padding");
1565
+ const gap = flagNum(flags, "gap");
1566
+ out = await call("paintress_new", {
1567
+ path: file,
1568
+ ...name !== void 0 ? { name } : {},
1569
+ ...width !== void 0 ? { width } : {},
1570
+ ...height !== void 0 ? { height } : {},
1571
+ ...fill !== void 0 ? { fill } : {},
1572
+ ...layout !== void 0 ? { layout } : {},
1573
+ ...padding !== void 0 ? { padding } : {},
1574
+ ...gap !== void 0 ? { gap } : {},
1575
+ ...flags["force"] === true ? { overwrite: true } : {}
1576
+ });
1577
+ break;
1578
+ }
1421
1579
  case "info":
1422
1580
  out = await call("paintress_doc_info", { path: file });
1423
1581
  break;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { Nt as Affine, tt as BackgroundBlurEffect, _t as BlendMode, nt as BlurEffect, Pt as BoolOrVar, L as BrowserSchema, Ft as Color, It as ColorOrVar, Lt as CornerRadius, vt as CrossAxis, A as DocumentObjectSchema, j as DocumentSchema, rt as EffectEntry, it as Effects, R as EllipseSchema, C as FileChangedError, at as FillEntry, yt as FillRule, ot as Fills, z as FrameSchema, st as GradientFill, bt as GradientType, ct as GraphicsBlock, B as GroupSchema, xt as IconLibrary, V as IconSchema, lt as ImageFill, St as ImageFit, H as KNOWN_NODE_TYPES, U as LayoutBlock, Ct as LayoutDir, wt as MainAxis, ut as MeshGradientFill, W as NodeBase, G as NodeSchema, K as NoteSchema, Rt as NumOrVar, M as PTR_SPEC_VERSION, zt as Padding, q as PathSchema, J as PolygonSchema, Tt as PositionMode, N as PtrDocumentSchema, Y as RectangleSchema, X as RefSchema, Z as ScriptSchema, dt as ShaderEffect, ft as ShaderFill, pt as ShadowEffect, Et as ShadowKind, Bt as Sizing, mt as SolidFill, Vt as StringOrVar, Dt as StrokeAlign, ht as StrokeBlock, Ot as StrokeCap, kt as StrokeJoin, gt as StrokeWidth, At as TextAlign, jt as TextAlignVertical, Mt as TextGrowth, Q as TextSchema, $ as UnknownNodeSchema, Ht as VARIABLE_NAME, P as VariableDef, Ut as VariableRef, F as XPaintress, I as X_PAINTRESS_KEY, h as batch, g as buildIndex, O as collectIds, s as commonToCss, n as computeCanvas, c as cornerRadiusToCss, l as effectsToCss, r as escapeAttr, i as escapeHtml, p as expandRefs, u as fillsToCss, k as genId, _ as get, o as getLucideIcon, v as insert, w as loadDoc, m as makeVarResolver, y as move, b as query, x as remove, t as renderDocument, a as renderTree, T as saveDoc, et as schemaForType, E as serializeDoc, d as strokeToCss, f as textToCss, S as update, D as validateDoc } from "./render-CbYe9rII.js";
1
+ export { Ft as Affine, rt as BackgroundBlurEffect, yt as BlendMode, it as BlurEffect, It as BoolOrVar, R as BrowserSchema, Lt as Color, Rt as ColorOrVar, zt as CornerRadius, bt as CrossAxis, A as DocumentObjectSchema, j as DocumentSchema, at as EffectEntry, ot as Effects, z as EllipseSchema, C as FileChangedError, st as FillEntry, xt as FillRule, ct as Fills, B as FrameSchema, lt as GradientFill, St as GradientType, ut as GraphicsBlock, V as GroupSchema, H as IconFontSchema, Ct as IconLibrary, U as IconSchema, dt as ImageFill, wt as ImageFit, W as KNOWN_NODE_TYPES, G as LayoutBlock, Tt as LayoutDir, Et as MainAxis, ft as MeshGradientFill, K as NodeBase, q as NodeSchema, J as NoteSchema, Bt as NumOrVar, M as PEN_DOC_VERSION, N as PTR_SPEC_VERSION, Vt as Padding, Y as PathSchema, X as PolygonSchema, Dt as PositionMode, P as PtrDocumentSchema, Z as RectangleSchema, Q as RefSchema, $ as ScriptSchema, pt as ShaderEffect, mt as ShaderFill, ht as ShadowEffect, Ot as ShadowKind, Ht as Sizing, gt as SolidFill, Ut as StringOrVar, kt as StrokeAlign, _t as StrokeBlock, At as StrokeCap, jt as StrokeJoin, vt as StrokeWidth, Mt as TextAlign, Nt as TextAlignVertical, Pt as TextGrowth, et as TextSchema, tt as UnknownNodeSchema, Wt as VARIABLE_NAME, F as VariableDef, Gt as VariableRef, I as XPaintress, L as X_PAINTRESS_KEY, h as batch, g as buildIndex, O as collectIds, s as commonToCss, n as computeCanvas, c as cornerRadiusToCss, l as effectsToCss, r as escapeAttr, i as escapeHtml, p as expandRefs, u as fillsToCss, k as genId, _ as get, o as getLucideIcon, v as insert, w as loadDoc, m as makeVarResolver, y as move, b as query, x as remove, t as renderDocument, a as renderTree, T as saveDoc, nt as schemaForType, E as serializeDoc, d as strokeToCss, f as textToCss, S as update, D as validateDoc } from "./render-YpsrjGNy.js";
@@ -1 +1 @@
1
- export { t as createMcpServer, n as runStdioServer } from "../server-C4TC8Bw3.js";
1
+ export { t as createMcpServer, n as runStdioServer } from "../server-19PJh0Vn.js";
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from "node:module";
2
- import { copyFileSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
2
+ import { copyFileSync, linkSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { z } from "zod";
5
5
  import { basename, dirname, join } from "node:path";
@@ -410,6 +410,19 @@ const IconSchema = z.object({
410
410
  weight: NumOrVar.optional(),
411
411
  fill: z.unknown().optional()
412
412
  }).passthrough();
413
+ /**
414
+ * icon_font:上游设计工具的图标节点写法(`iconFontName` + `iconFontFamily`)。
415
+ * 语义与 `icon`(`icon` + `library`)等价,仅字段名不同;渲染与校验按同一套处理。
416
+ */
417
+ const IconFontSchema = z.object({
418
+ type: z.literal("icon_font"),
419
+ ...NodeBase,
420
+ ...sizeBlock,
421
+ iconFontFamily: z.string().optional(),
422
+ iconFontName: StringOrVar.optional(),
423
+ weight: NumOrVar.optional(),
424
+ fill: z.unknown().optional()
425
+ }).passthrough();
413
426
  const ScriptSchema = z.object({
414
427
  type: z.literal("script"),
415
428
  ...NodeBase,
@@ -458,11 +471,12 @@ const KNOWN_NODE_TYPES = [
458
471
  "text",
459
472
  "note",
460
473
  "icon",
474
+ "icon_font",
461
475
  "script",
462
476
  "browser",
463
477
  "ref"
464
478
  ];
465
- /** 完整节点 union:已知 12 种 + 未知兜底。 */
479
+ /** 完整节点 union:已知 13 种 + 未知兜底。 */
466
480
  const UnknownNodeSchema = z.object({
467
481
  type: z.string(),
468
482
  id: z.string().optional()
@@ -477,6 +491,7 @@ const NodeSchema = z.union([
477
491
  TextSchema,
478
492
  NoteSchema,
479
493
  IconSchema,
494
+ IconFontSchema,
480
495
  ScriptSchema,
481
496
  BrowserSchema,
482
497
  RefSchema,
@@ -493,6 +508,7 @@ const SCHEMA_BY_TYPE = {
493
508
  text: TextSchema,
494
509
  note: NoteSchema,
495
510
  icon: IconSchema,
511
+ icon_font: IconFontSchema,
496
512
  script: ScriptSchema,
497
513
  browser: BrowserSchema,
498
514
  ref: RefSchema
@@ -531,6 +547,12 @@ const XPaintress = z.object({
531
547
  }).passthrough();
532
548
  const X_PAINTRESS_KEY = "x-paintress";
533
549
  const PTR_SPEC_VERSION = "1";
550
+ /**
551
+ * 新文档写入的 `version` 字段值。
552
+ * 必须与 `format/spec.yaml§meta.layers.pen_profile.observed_document_version` 同步
553
+ * (`test/spec-conformance.spec.ts` 会断言两者相等)。
554
+ */
555
+ const PEN_DOC_VERSION = "2.22";
534
556
  /** 文档根。passthrough:fileToken 与未来未知顶层键全部透传。 */
535
557
  const DocumentObjectSchema = z.object({
536
558
  version: z.string(),
@@ -715,7 +737,18 @@ function saveDoc(path, doc, opts = {}) {
715
737
  const text = JSON.stringify(out, null, 2);
716
738
  const tmp = join(dirname(path), `.${basename(path)}.tmp-${process.pid}`);
717
739
  writeFileSync(tmp, text, "utf8");
718
- renameSync(tmp, path);
740
+ if (opts.createOnly) {
741
+ try {
742
+ linkSync(tmp, path);
743
+ } catch (cause) {
744
+ try {
745
+ unlinkSync(tmp);
746
+ } catch {}
747
+ if (cause.code === "EEXIST") throw new Error(`文件已存在,拒绝覆盖:${path}`);
748
+ throw cause;
749
+ }
750
+ unlinkSync(tmp);
751
+ } else renameSync(tmp, path);
719
752
  return statSync(path).mtimeMs;
720
753
  }
721
754
  /** round-trip 序列化(不落盘):用于 diff、快照与测试 */
@@ -1486,11 +1519,15 @@ function renderNode(node, parentLayout, vars, warnings, extraAttrs = []) {
1486
1519
  css.push(...textToCss(node, vars, warnings));
1487
1520
  inner = escapeHtml(typeof node["content"] === "string" ? vars.resolve(node["content"]) : "");
1488
1521
  break;
1489
- case "icon": {
1522
+ case "icon":
1523
+ case "icon_font": {
1490
1524
  const colorCss = fillsToCss(node["fill"], vars, warnings, label).find(([p]) => p === "background-color");
1491
1525
  if (colorCss) css.push(["color", colorCss[1]], ["background", "transparent"]);
1492
- const lib = typeof node["library"] === "string" ? node["library"] : "lucide";
1493
- const iconName = typeof node["icon"] === "string" ? node["icon"] : "";
1526
+ const fontVariant = type === "icon_font";
1527
+ const libField = fontVariant ? node["iconFontFamily"] : node["library"];
1528
+ const nameField = fontVariant ? node["iconFontName"] : node["icon"];
1529
+ const lib = typeof libField === "string" && libField !== "" ? libField : "lucide";
1530
+ const iconName = typeof nameField === "string" ? vars.resolve(nameField) : "";
1494
1531
  let svg = null;
1495
1532
  if (lib === "lucide" || lib === "feather") svg = getLucideIcon(iconName, warnings);
1496
1533
  else warnings.push(`${label}: 图标库 "${lib}" 暂只支持 lucide/feather,渲染为占位框`);
@@ -1550,6 +1587,7 @@ const KNOWN_RENDER_TYPES = /* @__PURE__ */ new Set([
1550
1587
  "text",
1551
1588
  "note",
1552
1589
  "icon",
1590
+ "icon_font",
1553
1591
  "script",
1554
1592
  "browser"
1555
1593
  ]);
@@ -1610,4 +1648,4 @@ function renderDocument(doc, opts = {}) {
1610
1648
  canvas
1611
1649
  };
1612
1650
  }
1613
- export { UnknownNodeSchema as $, DocumentObjectSchema as A, TextAlign as At, GroupSchema as B, Sizing as Bt, FileChangedError as C, LayoutDir as Ct, validateDoc as D, StrokeAlign as Dt, serializeDoc as E, ShadowKind as Et, XPaintress as F, Color as Ft, NodeSchema as G, KNOWN_NODE_TYPES as H, VARIABLE_NAME as Ht, X_PAINTRESS_KEY as I, ColorOrVar as It, PolygonSchema as J, NoteSchema as K, BrowserSchema as L, CornerRadius as Lt, PTR_SPEC_VERSION as M, TextGrowth as Mt, PtrDocumentSchema as N, Affine as Nt, collectIds as O, StrokeCap as Ot, VariableDef as P, BoolOrVar as Pt, TextSchema as Q, EllipseSchema as R, NumOrVar as Rt, update as S, ImageFit as St, saveDoc as T, PositionMode as Tt, LayoutBlock as U, VariableRef as Ut, IconSchema as V, StringOrVar as Vt, NodeBase as W, VERSION as Wt, RefSchema as X, RectangleSchema as Y, ScriptSchema as Z, get as _, BlendMode as _t, renderTree as a, FillEntry as at, query as b, GradientType as bt, cornerRadiusToCss as c, GraphicsBlock as ct, strokeToCss as d, ShaderEffect as dt, schemaForType as et, textToCss as f, ShaderFill as ft, buildIndex as g, StrokeWidth as gt, batch as h, StrokeBlock as ht, escapeHtml as i, Effects as it, DocumentSchema as j, TextAlignVertical as jt, genId as k, StrokeJoin as kt, effectsToCss as l, ImageFill as lt, makeVarResolver as m, SolidFill as mt, computeCanvas as n, BlurEffect as nt, getLucideIcon as o, Fills as ot, expandRefs as p, ShadowEffect as pt, PathSchema as q, escapeAttr as r, EffectEntry as rt, commonToCss as s, GradientFill as st, renderDocument as t, BackgroundBlurEffect as tt, fillsToCss as u, MeshGradientFill as ut, insert as v, CrossAxis as vt, loadDoc as w, MainAxis as wt, remove as x, IconLibrary as xt, move as y, FillRule as yt, FrameSchema as z, Padding as zt };
1651
+ export { ScriptSchema as $, DocumentObjectSchema as A, StrokeCap as At, FrameSchema as B, NumOrVar as Bt, FileChangedError as C, IconLibrary as Ct, validateDoc as D, PositionMode as Dt, serializeDoc as E, MainAxis as Et, VariableDef as F, Affine as Ft, LayoutBlock as G, VariableRef as Gt, IconFontSchema as H, Sizing as Ht, XPaintress as I, BoolOrVar as It, NoteSchema as J, NodeBase as K, VERSION as Kt, X_PAINTRESS_KEY as L, Color as Lt, PEN_DOC_VERSION as M, TextAlign as Mt, PTR_SPEC_VERSION as N, TextAlignVertical as Nt, collectIds as O, ShadowKind as Ot, PtrDocumentSchema as P, TextGrowth as Pt, RefSchema as Q, BrowserSchema as R, ColorOrVar as Rt, update as S, GradientType as St, saveDoc as T, LayoutDir as Tt, IconSchema as U, StringOrVar as Ut, GroupSchema as V, Padding as Vt, KNOWN_NODE_TYPES as W, VARIABLE_NAME as Wt, PolygonSchema as X, PathSchema as Y, RectangleSchema as Z, get as _, StrokeBlock as _t, renderTree as a, EffectEntry as at, query as b, CrossAxis as bt, cornerRadiusToCss as c, Fills as ct, strokeToCss as d, ImageFill as dt, TextSchema as et, textToCss as f, MeshGradientFill as ft, buildIndex as g, SolidFill as gt, batch as h, ShadowEffect as ht, escapeHtml as i, BlurEffect as it, DocumentSchema as j, StrokeJoin as jt, genId as k, StrokeAlign as kt, effectsToCss as l, GradientFill as lt, makeVarResolver as m, ShaderFill as mt, computeCanvas as n, schemaForType as nt, getLucideIcon as o, Effects as ot, expandRefs as p, ShaderEffect as pt, NodeSchema as q, escapeAttr as r, BackgroundBlurEffect as rt, commonToCss as s, FillEntry as st, renderDocument as t, UnknownNodeSchema as tt, fillsToCss as u, GraphicsBlock as ut, insert as v, StrokeWidth as vt, loadDoc as w, ImageFit as wt, remove as x, FillRule as xt, move as y, BlendMode as yt, EllipseSchema as z, CornerRadius as zt };
@@ -1,13 +1,43 @@
1
- import { D as validateDoc, O as collectIds, S as update, T as saveDoc, Wt as VERSION, _ as get, b as query, h as batch, t as renderDocument, v as insert, w as loadDoc, x as remove, y as move } from "./render-CbYe9rII.js";
1
+ import { D as validateDoc, Kt as VERSION, M as PEN_DOC_VERSION, O as collectIds, S as update, T as saveDoc, _ as get, b as query, h as batch, j as DocumentSchema, k as genId, t as renderDocument, v as insert, w as loadDoc, x as remove, y as move } from "./render-YpsrjGNy.js";
2
2
  import { existsSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import { z } from "zod";
5
5
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
6
+ import { randomUUID } from "node:crypto";
6
7
  import { chromium } from "playwright-core";
7
8
  import { connect } from "node:net";
8
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
11
  import { spawn } from "node:child_process";
12
+ // src/doc/create.ts
13
+ function createDoc(opts = {}) {
14
+ const boardId = opts.boardId ?? genId(/* @__PURE__ */ new Set());
15
+ const board = {
16
+ type: "frame",
17
+ id: boardId,
18
+ name: opts.name ?? "Board",
19
+ x: 0,
20
+ y: 0,
21
+ width: opts.width ?? 900,
22
+ height: opts.height ?? 600,
23
+ fill: opts.fill ?? "#FFFFFF"
24
+ };
25
+ const layout = opts.layout ?? "vertical";
26
+ if (layout !== "none") {
27
+ board["layout"] = layout;
28
+ board["gap"] = opts.gap ?? 0;
29
+ board["padding"] = opts.padding ?? 0;
30
+ }
31
+ return {
32
+ doc: DocumentSchema.parse({
33
+ version: PEN_DOC_VERSION,
34
+ fileToken: opts.fileToken ?? randomUUID(),
35
+ variables: {},
36
+ children: [board]
37
+ }),
38
+ boardId
39
+ };
40
+ }
11
41
  // src/shot/screenshot.ts
12
42
  /**
13
43
  * 截图层:渲染 → 临时 HTML → headless Chromium → PNG(决策 3:两个消费者分开,AI 要 PNG)。
@@ -357,7 +387,75 @@ function countByType(children) {
357
387
  walk(children);
358
388
  return counts;
359
389
  }
390
+ /**
391
+ * 新建文档。path 不带扩展名时补 `.ptr`:新建一律默认标准格式,
392
+ * `.pen` 只有在调用方显式写扩展名时才产出(兼容旧工具链)。
393
+ */
394
+ function newDoc(args) {
395
+ const raw = reqStr(args, "path");
396
+ const path = /\.(pen|ptr)$/i.test(raw) ? raw : `${raw}.ptr`;
397
+ const flavor = path.toLowerCase().endsWith(".pen") ? "pen" : "ptr";
398
+ if (existsSync(path) && optBool(args, "overwrite") !== true) throw new Error(`文件已存在,未覆盖:${path}(确需覆盖传 overwrite=true)`);
399
+ const width = optNum(args, "width");
400
+ const height = optNum(args, "height");
401
+ if (width !== void 0 && width <= 0) throw new Error(`width 必须大于 0,收到:${width}`);
402
+ if (height !== void 0 && height <= 0) throw new Error(`height 必须大于 0,收到:${height}`);
403
+ const layoutArg = optStr(args, "layout");
404
+ if (layoutArg !== void 0 && ![
405
+ "vertical",
406
+ "horizontal",
407
+ "none"
408
+ ].includes(layoutArg)) throw new Error(`layout 仅支持 vertical / horizontal / none,收到:${layoutArg}`);
409
+ const { doc, boardId } = createDoc({
410
+ name: optStr(args, "name"),
411
+ width,
412
+ height,
413
+ fill: optStr(args, "fill"),
414
+ layout: layoutArg === "vertical" || layoutArg === "horizontal" || layoutArg === "none" ? layoutArg : void 0,
415
+ padding: optNum(args, "padding"),
416
+ gap: optNum(args, "gap")
417
+ });
418
+ const check = validateDoc(doc);
419
+ if (!check.ok) throw new Error(`新建文档结构校验失败,未落盘:\n${check.errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}`);
420
+ mkdirSync(dirname(path), { recursive: true });
421
+ saveDoc(path, doc, {
422
+ flavor,
423
+ createOnly: optBool(args, "overwrite") !== true
424
+ });
425
+ const board = doc.children[0];
426
+ return {
427
+ ok: true,
428
+ path,
429
+ flavor,
430
+ version: doc.version,
431
+ boardId,
432
+ canvas: {
433
+ width: Number(board?.["width"] ?? 0),
434
+ height: Number(board?.["height"] ?? 0)
435
+ },
436
+ warnings: check.warnings
437
+ };
438
+ }
360
439
  const TOOLS = {
440
+ paintress_new: {
441
+ description: "新建文档(.ptr 是 paintress 的标准格式;.pen 仅作兼容)。创建一个带空 board 的最小可用文档,可直接 render/screenshot 再逐层 insert;已存在同名文件时报错,除非 overwrite=true。path 不带扩展名时自动补 .ptr",
442
+ inputSchema: {
443
+ path: z.string().describe("新文档路径;不带扩展名时自动补 .ptr"),
444
+ name: z.string().optional().describe("board 名称(默认 Board)"),
445
+ width: z.number().positive().optional().describe("board 宽(默认 900)"),
446
+ height: z.number().positive().optional().describe("board 高(默认 600)"),
447
+ fill: z.string().optional().describe("board 背景色(默认 #FFFFFF)"),
448
+ layout: z.enum([
449
+ "vertical",
450
+ "horizontal",
451
+ "none"
452
+ ]).optional().describe("子节点布局(默认 vertical)"),
453
+ padding: z.number().optional().describe("board 内边距(默认 0)"),
454
+ gap: z.number().optional().describe("主轴向间距(默认 0)"),
455
+ overwrite: z.boolean().optional().describe("已存在时覆盖(默认 false)")
456
+ },
457
+ run: (a) => newDoc(a)
458
+ },
361
459
  paintress_doc_info: {
362
460
  description: "文档摘要:版本、格式风味、节点统计、变量表、顶层节点",
363
461
  inputSchema: { path: z.string().describe("设计稿路径") },
@@ -614,7 +712,7 @@ async function ensureDaemon(opts = {}) {
614
712
  async function autoRegister(cwd = process.cwd()) {
615
713
  if (process.env["PAINTRESS_NO_AUTO_WEB"] === "1") return null;
616
714
  try {
617
- const { started } = await ensureDaemon();
715
+ const { started } = await ensureDaemon({ idleTimeoutMs: 36e5 });
618
716
  const project = deriveProjectName(cwd);
619
717
  const watched = await sendRequest({
620
718
  kind: "watch",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paintress",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Design-document operation layer for AI agents: read, query, edit, render",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,7 +38,8 @@
38
38
  "verify:pkg": "node scripts/verify-package.mjs",
39
39
  "prepack": "pnpm build && pnpm verify:pkg",
40
40
  "build:ui": "pnpm --dir ui build",
41
- "dev:ui": "pnpm --dir ui dev"
41
+ "dev:ui": "pnpm --dir ui dev",
42
+ "release": "node ./scripts/release.mjs"
42
43
  },
43
44
  "dependencies": {
44
45
  "@modelcontextprotocol/sdk": "^1.32.2",
@@ -1,11 +1,17 @@
1
1
  ---
2
2
  name: paintress
3
- description: Read, query, edit, render and screenshot .pen/.ptr design documents through MCP tools, and drive the local viewer. Use when the user refers to a design file, a node in it, a board screenshot, or shorthand ids like #1.
3
+ description: Create, read, query, edit, render and screenshot `.ptr` (paintress standard format) and `.pen` (compatible) design documents through MCP tools, and drive the local viewer. Use when the user refers to a design file, wants a new design file created, a node in it, a board screenshot, or shorthand ids like #1.
4
4
  ---
5
5
 
6
6
  # paintress
7
7
 
8
- paintress is a design-document operation layer: JSON design files (`.pen`, and paintress's own `.ptr`) in, MCP tools and a CLI out. It does not draw anything itself — it reads and writes the file, and renders it to HTML for a browser.
8
+ paintress is a design-document operation layer: JSON design files in, MCP tools and a CLI out. It does not draw anything itself — it reads and writes the file, and renders it to HTML for a browser.
9
+
10
+ ## Formats
11
+
12
+ - **`.ptr` is paintress's standard format.** Create this by default for anything new. It carries the `x-paintress` block (`spec`, `generator`, timestamps) and is the layer that evolves.
13
+ - **`.pen` is compatible**: read, queried, edited and rendered exactly the same way. Do not convert a `.pen` the user is still working with — edit it in place.
14
+ - Both render identically; the difference is only which layer owns future format changes.
9
15
 
10
16
  ## Check before installing
11
17
 
@@ -20,6 +26,7 @@ If either prints something, **do not install again**. Use the existing binary. `
20
26
 
21
27
  | Tool | What it does |
22
28
  | --- | --- |
29
+ | `paintress_new` | **create a document.** Writes a valid `.ptr` with one empty board; returns the `boardId`. Refuses to overwrite; `path` without an extension gets `.ptr` |
23
30
  | `paintress_doc_info` | document summary: version, format flavor, node counts, variables, top-level nodes |
24
31
  | `paintress_query` | find nodes by type / name (exact or regex) / id / reusable |
25
32
  | `paintress_get` | fetch one node's subtree; `depth` controls how far down |
@@ -35,11 +42,16 @@ If either prints something, **do not install again**. Use the existing binary. `
35
42
 
36
43
  ## Working rules
37
44
 
38
- 1. **Query before you edit.** Node ids come from the file; never invent them.
39
- 2. **`#N` means a marker.** When the user writes `#1`, call `paintress_marks` and map `#1` to a real node id. Markers are per viewer session.
40
- 3. **Top-level nodes are boards.** Use `paintress_doc_info` to list them before guessing what "the second screen" means.
41
- 4. **After a batch, validate.** `paintress_validate` catches structural mistakes before the user opens the file.
42
- 5. **Screenshots are for you.** `paintress_screenshot` is how you look at the design; the HTML viewer is for the human.
45
+ 1. **Nothing to edit? Create it.** `paintress_new` is the only way to make a file; there is no "empty document" state to work around, and no need to hand-write JSON.
46
+ 2. **Query before you edit.** Node ids come from the file; never invent them.
47
+ 3. **`#N` means a marker.** When the user writes `#1`, call `paintress_marks` and map `#1` to a real node id. Markers are per viewer session.
48
+ 4. **Top-level nodes are boards.** Use `paintress_doc_info` to list them before guessing what "the second screen" means.
49
+ 5. **After a batch, validate.** `paintress_validate` catches structural mistakes before the user opens the file.
50
+ 6. **Screenshots are for you.** `paintress_screenshot` is how you look at the design; the HTML viewer is for the human.
51
+
52
+ ## Node fields worth knowing
53
+
54
+ `type` and `id` are immutable after creation. Text nodes hold their string in **`content`** (not `text`). `update` is a shallow merge and does **not** validate field names — a typo silently creates a new key and the render does not change. When unsure of a field name, `paintress_get` the node and read the real one.
43
55
 
44
56
  ## Viewer
45
57
 
@@ -51,3 +63,18 @@ paintress watch <name> <dir> # register a directory
51
63
  ```
52
64
 
53
65
  Ask the user to open the URL and click the nodes they mean; then read `paintress_marks` to learn which ids those clicks became.
66
+
67
+ ## Bind the project yourself — do not wait to be asked
68
+
69
+ When the user refers to a design, asks to see one, or asks to draw something in this project, **you** make it viewable. The MCP server auto-registers the working directory at startup (its name is derived from the last two path segments, e.g. `acme-shop`); if that already covers the request, just hand over the viewer URL:
70
+
71
+ - viewer: `http://localhost:3316/`
72
+ - deep link to a file: `http://localhost:3316/?project=<name>&file=<relative/path>`
73
+
74
+ If the design lives elsewhere (a `.pen`/`.ptr` outside the working directory, or a second project), bind it yourself:
75
+
76
+ ```bash
77
+ paintress watch <name> <dir> [--depth 1..6]
78
+ ```
79
+
80
+ Prefer the same auto-derived name when it applies, so the URL stays stable. Never ask the user to run `watch` — the goal is that "look at / draw a design" resolves to a viewable URL with zero extra steps. The project list in any open viewer window refreshes on its own once the registration lands.
@@ -0,0 +1 @@
1
+ :root{--bg:#f6f1ea;--panel:#fffcf8;--border:#e6ded2;--text:#1f2a37;--muted:#7a7166;--accent:#205080;--accent-soft:#20508014;--gold:#908030;--gold-soft:#90803073;--radius:8px}*{box-sizing:border-box;margin:0;padding:0}html,body,#root{height:100%}body{color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased;font:14px/1.55 system-ui,-apple-system,Segoe UI,Noto Sans SC,sans-serif}.app{grid-template-columns:280px 1fr;height:100%;display:grid}.sidebar{background:var(--panel);border-right:1px solid var(--border);flex-direction:column;min-height:0;display:flex}.sidebar-head{border-bottom:1px solid var(--border);align-items:center;gap:8px;padding:16px 16px 12px;display:flex}.sidebar-head h1{letter-spacing:-.01em;color:var(--accent);font-size:15px;font-weight:650}.badge{color:var(--muted);background:var(--bg);border-radius:999px;padding:1px 6px;font-size:11px}.section{min-height:0;padding:12px 8px 4px;overflow-y:auto}.section.files{border-top:1px solid var(--border);flex:1;padding-top:12px}.section h2{text-transform:uppercase;letter-spacing:.06em;color:var(--muted);justify-content:space-between;gap:8px;padding:0 8px 8px;font-size:11px;font-weight:600;display:flex}.hint{color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px;font-weight:400}.list{list-style:none}.item{text-align:left;cursor:pointer;width:100%;font:inherit;color:inherit;background:0 0;border:0;border-radius:6px;flex-direction:column;gap:1px;padding:7px 8px;display:flex}.item:hover{background:var(--bg)}.item.active{background:var(--accent-soft);color:var(--accent)}.item-name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.item-meta{color:var(--muted);font-size:11px}.item.active .item-meta{color:var(--accent);opacity:.75}.empty{color:var(--muted);padding:4px 8px 12px;font-size:12px;line-height:1.7}.empty code{background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:1px 5px;font-size:11px}.sidebar-foot{border-top:1px solid var(--border);color:var(--muted);flex-wrap:wrap;align-items:center;gap:8px;padding:10px 16px;font-size:12px;display:flex}.toggle{cursor:pointer;align-items:center;gap:5px;display:inline-flex}.ghost{background:var(--bg);border:1px solid var(--border);font:inherit;color:var(--text);cursor:pointer;border-radius:6px;padding:3px 9px;font-size:12px}.ghost:hover{border-color:var(--accent);color:var(--accent)}.stage{background:var(--canvas-bg,var(--bg));flex-direction:column;min-width:0;min-height:0;display:flex}.toolbar{border-bottom:1px solid var(--border);background:var(--panel);justify-content:space-between;align-items:center;gap:12px;padding:8px 14px;display:flex}.toolbar code{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.preview{border:1px solid var(--border);background:var(--canvas-bg,#fff);border-radius:10px;flex:1;width:calc(100% - 24px);min-height:0;margin:12px;box-shadow:0 1px 2px #1f2a370d,0 8px 24px #1f2a370d}.stage[data-canvas-bg=dark] .preview{border-color:#ffffff1f;box-shadow:0 1px 2px #00000059,0 10px 30px #00000052}.placeholder{color:var(--muted);flex:1;place-items:center;display:grid}.error{border-radius:var(--radius);color:#b91c1c;white-space:pre-wrap;background:#fef2f2;border:1px solid #fecaca;margin:12px;padding:10px 12px;font-size:12px}.section.marks{border-top:1px solid var(--border);flex:0 auto;max-height:34vh}.mark-no{background:var(--accent-soft);min-width:20px;color:var(--accent);font-variant-numeric:tabular-nums;border-radius:4px;margin-right:4px;padding:0 4px;font-size:11px;display:inline-block}.item.active .mark-no{background:#fff}.ghost.tiny{text-transform:none;letter-spacing:0;padding:1px 6px;font-size:11px}.crumbs{flex-wrap:wrap;align-items:center;gap:3px;min-width:0;display:flex;overflow:hidden}.crumbs code{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.crumb{font:inherit;color:var(--muted);cursor:pointer;text-overflow:ellipsis;white-space:nowrap;background:0 0;border:0;border-radius:4px;max-width:220px;padding:2px 5px;font-size:12px;overflow:hidden}.crumb:hover{background:var(--bg);color:var(--text)}.crumb.active{color:var(--accent);background:var(--accent-soft)}.sep{color:var(--border);font-size:11px}.brand-mark{flex:none;width:auto;height:32px}.canvas-bg{-webkit-user-select:none;user-select:none;flex:none;align-items:center;gap:8px;display:inline-flex}.canvas-bg-label{letter-spacing:.04em;text-transform:uppercase;color:var(--muted);text-align:right;min-width:34px;font-size:11px;font-weight:500}.canvas-bg-track{width:84px;height:calc(var(--thumb) + var(--pad) * 2);cursor:pointer;touch-action:none;background:linear-gradient(#e7e0d5,#f3eee6 60%,#f7f2ea);border-radius:999px;position:relative;box-shadow:inset 0 1px 2px #1f2a373d,inset 0 -1px #ffffffe6,inset 0 0 0 1px #1f2a3712,0 1px #fff}.canvas-bg-track:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.canvas-bg-thumb{top:var(--pad);left:var(--pad);width:var(--thumb);height:var(--thumb);transform:translateX(calc(var(--pos) * var(--step)));background:linear-gradient(#fff,#f1ebe1);border-radius:50%;justify-content:center;align-items:center;transition:transform .17s cubic-bezier(.22,.61,.36,1);display:flex;position:absolute;box-shadow:0 1px 2px #1f2a374d,0 0 0 1px #1f2a3714,inset 0 1px #fff}.canvas-bg-track.dragging .canvas-bg-thumb{transition:none}.canvas-bg-dot{background:var(--dot,#fff);border-radius:50%;width:11px;height:11px;transition:background .17s;box-shadow:0 0 0 1px #1f2a3742,0 1px 1px #1f2a371f}.lang-switch{background:linear-gradient(#ece6dc,#f6f1e9);border-radius:999px;flex:none;align-items:center;gap:2px;margin-left:4px;padding:2px;display:inline-flex;box-shadow:inset 0 1px 2px #1f2a3729,inset 0 0 0 1px #1f2a370d}.lang-opt{font:inherit;color:var(--muted);cursor:pointer;background:0 0;border:0;border-radius:999px;padding:2px 7px;font-size:11px;line-height:16px}.lang-opt:hover{color:var(--text)}.lang-opt.active{color:var(--accent);background:linear-gradient(#fffdf9,#f3ede4);font-weight:600;box-shadow:0 1px 2px #1f2a372e,inset 0 1px #fff}.tool-sep{background:var(--border);flex:none;width:1px;height:18px;margin:0 2px}.tools{flex:none;align-items:center;gap:6px;display:flex}.zoom{font-variant-numeric:tabular-nums;text-align:right;min-width:40px}.boardbar{border-bottom:1px solid var(--border);background:var(--panel);align-items:center;gap:6px;padding:6px 14px;display:flex;overflow-x:auto}.board{background:var(--bg);border:1px solid var(--border);font:inherit;color:var(--text);cursor:pointer;border-radius:6px;flex:none;align-items:baseline;gap:6px;padding:3px 9px;font-size:12px;display:inline-flex}.board:hover{border-color:var(--accent);color:var(--accent)}.board.active{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}.board-size{color:var(--muted);font-variant-numeric:tabular-nums;font-size:10px}.board.active .board-size{color:var(--accent);opacity:.75}
@@ -0,0 +1,7 @@
1
+ (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e={context:void 0,registry:void 0,effects:void 0,done:!1,getContextId(){return t(this.context.count)},getNextContextId(){return t(this.context.count++)}};function t(t){let n=String(t),r=n.length-1;return e.context.id+(r?String.fromCharCode(96+r):``)+n}function n(t){e.context=t}var r=(e,t)=>e===t,i=Symbol(`solid-track`),a={equals:r},o=null,s=oe,c=1,l=2,u={owned:null,cleanups:null,context:null,owner:null},d=null,f=null,p=null,m=null,h=null,g=0;function _(e,t){let n=p,r=d,i=e.length===0,a=t===void 0?r:t,o=i?u:{owned:null,cleanups:null,context:a?a.context:null,owner:a},s=i?e:()=>e(v(o));d=o,p=null;try{return j(s,!0)}finally{p=n,d=r}}function v(e){return()=>C(()=>I(e))}function y(e,t){t=t?Object.assign({},a,t):a;let n={value:e,observers:null,observerSlots:null,comparator:t.equals||void 0};return[ne.bind(n),e=>(typeof e==`function`&&(e=f&&f.running&&f.sources.has(n)?e(n.tValue):e(n.value)),re(n,e))]}function b(e,t,n){k(ae(e,t,!1,c))}function x(e,t,n){s=N;let r=ae(e,t,!1,c),i=O&&D(O);i&&(r.suspense=i),n&&n.render||(r.user=!0),h?h.push(r):k(r)}function S(e,t,n){n=n?Object.assign({},a,n):a;let r=ae(e,t,!0,0);return r.observers=null,r.observerSlots=null,r.comparator=n.equals||void 0,k(r),ne.bind(r)}function C(e){if(p===null)return e();let t=p;p=null;try{return e()}finally{p=t}}function w(e,t,n){let r=Array.isArray(e),i,a=n&&n.defer;return n=>{let o;if(r){o=Array(e.length);for(let t=0;t<e.length;t++)o[t]=e[t]()}else o=e();if(a)return a=!1,n;let s=C(()=>t(o,i,n));return i=o,s}}function T(e){x(()=>C(e))}function E(e){return d===null||(d.cleanups===null?d.cleanups=[e]:d.cleanups.push(e)),e}var[ee,te]=y(!1);function D(e){let t;return d&&d.context&&(t=d.context[e.id])!==void 0?t:e.defaultValue}var O;function ne(){let e=f&&f.running;if(this.sources&&(e?this.tState:this.state)){if((e?this.tState:this.state)===c)k(this);else{let e=m;m=null,j(()=>P(this),!1),m=e}}if(p){let e=this.observers;if(!e||e[e.length-1]!==p){let t=e?e.length:0;p.sources?(p.sources.push(this),p.sourceSlots.push(t)):(p.sources=[this],p.sourceSlots=[t]),e?(e.push(p),this.observerSlots.push(p.sources.length-1)):(this.observers=[p],this.observerSlots=[p.sources.length-1])}}return e&&f.sources.has(this)?this.tValue:this.value}function re(e,t,n){let r=f&&f.running&&f.sources.has(e)?e.tValue:e.value;if(!e.comparator||!e.comparator(r,t)){if(f){let r=f.running;(r||!n&&f.sources.has(e))&&(f.sources.add(e),e.tValue=t),r||(e.value=t)}else e.value=t;e.observers&&e.observers.length&&j(()=>{for(let t=0;t<e.observers.length;t+=1){let n=e.observers[t],r=f&&f.running;r&&f.disposed.has(n)||((r?!n.tState:!n.state)&&(n.pure?m.push(n):h.push(n),n.observers&&F(n)),r?n.tState=c:n.state=c)}if(m.length>1e6)throw m=[],Error()},!1)}return t}function k(e){if(!e.fn)return;I(e);let t=g;ie(e,f&&f.running&&f.sources.has(e)?e.tValue:e.value,t),f&&!f.running&&f.sources.has(e)&&queueMicrotask(()=>{j(()=>{f&&(f.running=!0),p=d=e,ie(e,e.tValue,t),p=d=null},!1)})}function ie(e,t,n){let r,i=d,a=p;p=d=e;try{r=e.fn(t)}catch(t){return e.pure&&(f&&f.running?(e.tState=c,e.tOwned&&e.tOwned.forEach(I),e.tOwned=void 0):(e.state=c,e.owned&&e.owned.forEach(I),e.owned=null)),e.updatedAt=n+1,ue(t)}finally{p=a,d=i}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&`observers`in e?re(e,r,!0):f&&f.running&&e.pure?(f.sources.has(e)||(e.value=r),f.sources.add(e),e.tValue=r):e.value=r,e.updatedAt=n)}function ae(e,t,n,r=c,i){let a={fn:e,state:r,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:t,owner:d,context:d?d.context:null,pure:n};return f&&f.running&&(a.state=0,a.tState=r),d===null||d!==u&&(f&&f.running&&d.pure?d.tOwned?d.tOwned.push(a):d.tOwned=[a]:d.owned?d.owned.push(a):d.owned=[a]),a}function A(e){let t=f&&f.running;if((t?e.tState:e.state)===0)return;if((t?e.tState:e.state)===l)return P(e);if(e.suspense&&C(e.suspense.inFallback))return e.suspense.effects.push(e);let n=[e];for(;(e=e.owner)&&(!e.updatedAt||e.updatedAt<g);){if(t&&f.disposed.has(e))return;(t?e.tState:e.state)&&n.push(e)}for(let r=n.length-1;r>=0;r--){if(e=n[r],t){let t=e,i=n[r+1];for(;(t=t.owner)&&t!==i;)if(f.disposed.has(t))return}if((t?e.tState:e.state)===c)k(e);else if((t?e.tState:e.state)===l){let t=m;m=null,j(()=>P(e,n[0]),!1),m=t}}}function j(e,t){if(m)return e();let n=!1;t||(m=[]),h?n=!0:h=[],g++;try{let t=e();return M(n),t}catch(e){n||(h=null),m=null,ue(e)}}function M(e){if(m&&=(oe(m),null),e)return;let t;if(f){if(!f.promises.size&&!f.queue.size){let e=f.sources,n=f.disposed;h.push.apply(h,f.effects),t=f.resolve;for(let e of h)`tState`in e&&(e.state=e.tState),delete e.tState;f=null,j(()=>{for(let e of n)I(e);for(let t of e){if(t.value=t.tValue,t.owned)for(let e=0,n=t.owned.length;e<n;e++)I(t.owned[e]);t.tOwned&&(t.owned=t.tOwned),delete t.tValue,delete t.tOwned,t.tState=0}te(!1)},!1)}else if(f.running){f.running=!1,f.effects.push.apply(f.effects,h),h=null,te(!0);return}}let n=h;h=null,n.length&&j(()=>s(n),!1),t&&t()}function oe(e){for(let t=0;t<e.length;t++)A(e[t])}function N(t){let r,i=0;for(r=0;r<t.length;r++){let e=t[r];e.user?t[i++]=e:A(e)}if(e.context){if(e.count){e.effects||=[],e.effects.push(...t.slice(0,i));return}n()}for(e.effects&&(e.done||!e.count)&&(t=[...e.effects,...t],i+=e.effects.length,delete e.effects),r=0;r<i;r++)A(t[r])}function P(e,t){let n=f&&f.running;n?e.tState=0:e.state=0;for(let r=0;r<e.sources.length;r+=1){let i=e.sources[r];if(i.sources){let e=n?i.tState:i.state;e===c?i!==t&&(!i.updatedAt||i.updatedAt<g)&&A(i):e===l&&P(i,t)}}}function F(e){let t=f&&f.running;for(let n=0;n<e.observers.length;n+=1){let r=e.observers[n];(t?!r.tState:!r.state)&&(t?r.tState=l:r.state=l,r.pure?m.push(r):h.push(r),r.observers&&F(r))}}function I(e){let t;if(e.sources)for(;e.sources.length;){let t=e.sources.pop(),n=e.sourceSlots.pop(),r=t.observers;if(r&&r.length){let e=r.pop(),i=t.observerSlots.pop();n<r.length&&(e.sourceSlots[i]=n,r[n]=e,t.observerSlots[n]=i)}}if(e.tOwned){for(t=e.tOwned.length-1;t>=0;t--)I(e.tOwned[t]);delete e.tOwned}if(f&&f.running&&e.pure)se(e,!0);else if(e.owned){for(t=e.owned.length-1;t>=0;t--)I(e.owned[t]);e.owned=null}if(e.cleanups){for(t=e.cleanups.length-1;t>=0;t--)e.cleanups[t]();e.cleanups=null}f&&f.running?e.tState=0:e.state=0}function se(e,t){if(t||(e.tState=0,f.disposed.add(e)),e.owned)for(let t=0;t<e.owned.length;t++)se(e.owned[t])}function ce(e){return e instanceof Error?e:Error(typeof e==`string`?e:`Unknown error`,{cause:e})}function le(e,t,n){try{for(let n of t)n(e)}catch(e){ue(e,n&&n.owner||null)}}function ue(e,t=d){let n=o&&t&&t.context&&t.context[o],r=ce(e);if(!n)throw r;h?h.push({fn(){le(r,n,t)},state:c}):le(r,n,t)}var de=Symbol(`fallback`);function fe(e){for(let t=0;t<e.length;t++)e[t]()}function pe(e,t,n={}){let r=[],a=[],o=[],s=0,c=t.length>1?[]:null;return E(()=>fe(o)),()=>{let l=e()||[],u=l.length,d,f;return l[i],C(()=>{let e,t,i,m,h,g,v,y,b;if(u===0)s!==0&&(fe(o),o=[],r=[],a=[],s=0,c&&=[]),n.fallback&&(r=[de],a[0]=_(e=>(o[0]=e,n.fallback())),s=1);else if(s===0){for(a=Array(u),f=0;f<u;f++)r[f]=l[f],a[f]=_(p);s=u}else{for(i=Array(u),m=Array(u),c&&(h=Array(u)),g=0,v=Math.min(s,u);g<v&&r[g]===l[g];g++);for(v=s-1,y=u-1;v>=g&&y>=g&&r[v]===l[y];v--,y--)i[y]=a[v],m[y]=o[v],c&&(h[y]=c[v]);for(e=new Map,t=Array(y+1),f=y;f>=g;f--)b=l[f],d=e.get(b),t[f]=d===void 0?-1:d,e.set(b,f);for(d=g;d<=v;d++)b=r[d],f=e.get(b),f!==void 0&&f!==-1?(i[f]=a[d],m[f]=o[d],c&&(h[f]=c[d]),f=t[f],e.set(b,f)):o[d]();for(f=g;f<u;f++)f in i?(a[f]=i[f],o[f]=m[f],c&&(c[f]=h[f],c[f](f))):a[f]=_(p);a=a.slice(0,s=u),r=l.slice(0)}return a});function p(e){if(o[f]=e,c){let[e,n]=y(f);return c[f]=n,t(l[f],e)}return t(l[f])}}}function L(e,t){return C(()=>e(t||{}))}var R=e=>`Stale read from <${e}>.`;function z(e){let t=`fallback`in e&&{fallback:()=>e.fallback};return S(pe(()=>e.each,e.children,t||void 0))}function B(e){let t=e.keyed,n=S(()=>e.when,void 0,void 0),r=t?n:S(n,void 0,{equals:(e,t)=>!e==!t});return S(()=>{let i=r();if(i){let a=e.children;return typeof a==`function`&&a.length>0?C(()=>a(t?i:()=>{if(!C(r))throw R(`Show`);return n()})):a}return e.fallback},void 0,void 0)}var me=e=>S(()=>e());function he(e,t,n){let r=n.length,i=t.length,a=r,o=0,s=0,c=t[i-1].nextSibling,l=null;for(;o<i||s<a;)if(t[o]===n[s])o++,s++;else{for(;t[i-1]===n[a-1];)i--,a--;if(i===o){let t=a<r?s?n[s-1].nextSibling:n[a-s]:c;for(;s<a;)e.insertBefore(n[s++],t)}else if(a===s)for(;o<i;)l&&l.has(t[o])||t[o].remove(),o++;else if(t[o]===n[a-1]&&n[s]===t[i-1]){let r=t[--i].nextSibling;e.insertBefore(n[s++],t[o++].nextSibling),e.insertBefore(n[--a],r),t[i]=n[a]}else{if(!l){l=new Map;let e=s;for(;e<a;)l.set(n[e],e++)}let r=l.get(t[o]);if(r!=null){if(s<r&&r<a){let c=o,u=1,d;for(;++c<i&&c<a&&(d=l.get(t[c]))!=null&&d===r+u;)u++;if(u>r-s){let i=t[o];for(;s<r;)e.insertBefore(n[s++],i)}else e.replaceChild(n[s++],t[o++])}else o++}else t[o++].remove()}}}var V=`_$DX_DELEGATE`;function ge(e,t,n,r={}){let i;return _(r=>{i=r,t===document?e():W(t,e(),!n&&t.firstChild?null:void 0,n)},r.owner),()=>{i(),t.textContent=``}}function H(e,t,n,r){let i,a=()=>{let t=r?document.createElementNS(`http://www.w3.org/1998/Math/MathML`,`template`):document.createElement(`template`);return t.innerHTML=e,n?t.content.firstChild.firstChild:r?t.firstChild:t.content.firstChild},o=t?()=>C(()=>document.importNode(i||=a(),!0)):()=>(i||=a()).cloneNode(!0);return o.cloneNode=o,o}function _e(e,t=window.document){let n=t[V]||(t[V]=new Set);for(let r=0,i=e.length;r<i;r++){let i=e[r];n.has(i)||(n.add(i),t.addEventListener(i,xe))}}function U(e,t,n){be(e)||(n==null?e.removeAttribute(t):e.setAttribute(t,n))}function ve(e,t,n){n==null?e.style.removeProperty(t):e.style.setProperty(t,n)}function ye(e,t,n){return C(()=>e(t,n))}function W(e,t,n,r){if(n!==void 0&&!r&&(r=[]),typeof t!=`function`)return G(e,t,r,n);b(r=>G(e,t(),r,n),r)}function be(t){return!!e.context&&!e.done&&(!t||t.isConnected)}function xe(t){if(e.registry&&e.events&&e.events.find(([e,n])=>n===t))return;let n=t.target,r=`$$${t.type}`,i=t.target,a=t.currentTarget,o=e=>Object.defineProperty(t,"target",{configurable:!0,value:e}),s=()=>{let e=n[r];if(e&&!n.disabled){let i=n[`${r}Data`];if(i===void 0?e.call(n,t):e.call(n,i,t),t.cancelBubble)return}return n.host&&typeof n.host!=`string`&&!n.host._$host&&n.contains(t.target)&&o(n.host),!0},c=()=>{for(;s()&&(n=n._$host||n.parentNode||n.host););};Object.defineProperty(t,"currentTarget",{configurable:!0,get(){return n||document}}),e.registry&&!e.done&&(e.done=_$HY.done=!0);try{if(t.composedPath){let e=t.composedPath();o(e[0]);for(let t=0;t<e.length-2&&(n=e[t],s());t++){if(n._$host){n=n._$host,c();break}if(n.parentNode===a)break}}else c();o(i)}finally{t=n=null}}function G(e,t,n,r,i){let a=be(e);if(a){!n&&(n=[...e.childNodes]);let t=[];for(let e=0;e<n.length;e++){let r=n[e];r.nodeType===8&&r.data.slice(0,2)===`!$`?r.remove():t.push(r)}n=t}for(;typeof n==`function`;)n=n();if(t===n)return n;let o=typeof t,s=r!==void 0;if(e=s&&n[0]&&n[0].parentNode||e,o===`string`||o===`number`){if(a||o===`number`&&(t=t.toString(),t===n))return n;if(s){let i=n[0];i&&i.nodeType===3?i.data!==t&&(i.data=t):i=document.createTextNode(t),n=q(e,n,r,i)}else n=n!==``&&typeof n==`string`?e.firstChild.data=t:e.textContent=t}else if(t==null||o===`boolean`){if(a)return n;n=q(e,n,r)}else if(o===`function`)return b(()=>{let i=t();for(;typeof i==`function`;)i=i();n=G(e,i,n,r)}),()=>n;else if(Array.isArray(t)){let o=[],c=n&&Array.isArray(n);if(K(o,t,n,i))return b(()=>n=G(e,o,n,r,!0)),()=>n;if(a){if(!o.length)return n;if(r===void 0)return n=[...e.childNodes];let t=o[0];if(t.parentNode!==e)return n;let i=[t];for(;(t=t.nextSibling)!==r;)i.push(t);return n=i}if(o.length===0){if(n=q(e,n,r),s)return n}else c?n.length===0?Se(e,o,r):he(e,n,o):(n&&q(e),Se(e,o));n=o}else if(t.nodeType){if(a&&t.parentNode)return n=s?[t]:t;if(Array.isArray(n)){if(s)return n=q(e,n,r,t);q(e,n,null,t)}else n==null||n===``||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}return n}function K(e,t,n,r){let i=!1;for(let a=0,o=t.length;a<o;a++){let o=t[a],s=n&&n[e.length],c;if(o!=null&&o!==!0&&o!==!1){if((c=typeof o)==`object`&&o.nodeType)e.push(o);else if(Array.isArray(o))i=K(e,o,s)||i;else if(c===`function`){if(r){for(;typeof o==`function`;)o=o();i=K(e,Array.isArray(o)?o:[o],Array.isArray(s)?s:[s])||i}else e.push(o),i=!0}else{let t=String(o);s&&s.nodeType===3&&s.data===t?e.push(s):e.push(document.createTextNode(t))}}}return i}function Se(e,t,n=null){for(let r=0,i=t.length;r<i;r++)e.insertBefore(t[r],n)}function q(e,t,n,r){if(n===void 0)return e.textContent=``;let i=r||document.createTextNode(``);if(t.length){let r=!1;for(let a=t.length-1;a>=0;a--){let o=t[a];if(i!==o){let t=o.parentNode===e;!r&&!a?t?e.replaceChild(i,o):e.insertBefore(i,n):t&&o.remove()}else r=!0}}else e.insertBefore(i,n);return[i]}async function J(e){let t=await fetch(e,{headers:{accept:`application/json`}});if(!t.ok){let e=await t.text().catch(()=>``);throw Error(`HTTP ${t.status}${e?`: ${e.slice(0,200)}`:``}`)}return await t.json()}function Ce(){return J(`/api/projects`)}function we(e){return J(`/api/projects/${encodeURIComponent(e)}/files`)}function Te(e,t){return`/render/${encodeURIComponent(e)}/${t.split(`/`).map(encodeURIComponent).join(`/`)}?embed=1`}function Ee(e,t){return`/events?project=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`}function De(){return`/events`}function Oe(e){return J(`/api/marks?project=${encodeURIComponent(e)}`)}async function ke(e){let t=await fetch(`/api/marks`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e)});if(!t.ok)throw Error(`HTTP ${t.status}`);let n=await t.json();if(!n.ok)throw Error(n.error??`添加标记失败`);return n.mark}async function Ae(e){let t=await fetch(`/api/marks?project=${encodeURIComponent(e)}`,{method:`DELETE`});if(!t.ok)throw Error(`HTTP ${t.status}`);return(await t.json()).cleared}function je(e,t){return J(`/api/changes?project=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`)}var Y=.1,X=.02;function Me(e,t){return t===0||Math.abs(e)>=Math.abs(t)?null:t>0?-1:1}function Z(e,t=Y){return Math.min(4,Math.max(t,e))}function Ne(e,t,n,r,i=Y){let a=Z(e.z*t,i);if(a===e.z)return e;let o=a/e.z;return{z:a,tx:n-(n-e.tx)*o,ty:r-(r-e.ty)*o}}function Pe(e,t,n,r=Y){return Ne(e,Z(t,r)/e.z,n.width/2,n.height/2,r)}function Q(e,t,n=24){let r=Math.max(1,e.w),i=Math.max(1,e.h),a=Math.max(1,t.width-n*2),o=Math.max(1,t.height-n*2),s=Z(Math.min(a/r,o/i),X);return{z:s,tx:(t.width-r*s)/2-e.x*s,ty:(t.height-i*s)/2-e.y*s}}function Fe(e,t,n){return{z:e.z,tx:e.tx+t,ty:e.ty+n}}function Ie(e){return`translate(${e.tx}px, ${e.ty}px) scale(${e.z})`}function Le(e){if(e===null||e===``)return null;let t;try{t=JSON.parse(e)}catch{return null}if(typeof t!=`object`||!t)return null;let{z:n,tx:r,ty:i}=t;return typeof n!=`number`||typeof r!=`number`||typeof i!=`number`||!Number.isFinite(n)||!Number.isFinite(r)||!Number.isFinite(i)||n<=0?null:{z:Z(n,X),tx:r,ty:i}}var Re=3,ze=.0015;function Be({doc:e,onChange:t,onLevelStep:n}){let r=e.querySelector(`.ptr-canvas`);if(r===null)return null;let i=r;i.style.transformOrigin=`0 0`;let a={z:1,tx:0,ty:0},o=!1,s=!1,c=!1,l=null,u=()=>({width:e.documentElement.clientWidth||1,height:e.documentElement.clientHeight||1});function d(){i.style.transform=Ie(a),t?.(a)}function f(e){a=e,d()}function p(){i.style.transition=``}function m(e){i.style.transition=`transform 180ms ease-out`,f(e),setTimeout(p,220)}function h(t){e.documentElement.style.cursor=t}function g(e){if(e.preventDefault(),e.altKey&&n!==void 0){let t=Me(e.deltaX,e.deltaY);t!==null&&n(t,{x:e.clientX,y:e.clientY});return}p();let t=1;e.deltaMode===1?t=16:e.deltaMode===2&&(t=u().height);let r=a.z<.1?X:Y;f(Ne(a,Math.exp(-e.deltaY*t*ze),e.clientX,e.clientY,r))}function _(t){return t.button===1?!0:t.button===0?o?!0:t.target===i||t.target===e.body||t.target===e.documentElement:!1}function v(e){_(e)&&(e.preventDefault(),l={lastX:e.clientX,lastY:e.clientY,startX:e.clientX,startY:e.clientY},s=!1)}function y(t){if(!l)return;if(!s){if(Math.hypot(t.clientX-l.startX,t.clientY-l.startY)<Re)return;s=!0,p(),e.body.style.userSelect=`none`,h(`grabbing`)}let n=t.clientX-l.lastX,r=t.clientY-l.lastY;l.lastX=t.clientX,l.lastY=t.clientY,f(Fe(a,n,r))}function b(){l&&(l=null,s&&(s=!1,c=!0,e.body.style.userSelect=``,h(o?`grab`:``)))}function x(e){o!==e&&(o=e,s||h(e?`grab`:``))}function S(e){if(e.key===` `&&!e.metaKey&&!e.ctrlKey&&!e.altKey){let t=e.target,n=t?.tagName??``;if(n===`INPUT`||n===`TEXTAREA`||t?.isContentEditable===!0)return;x(!0),e.preventDefault()}else T(e)&&e.preventDefault()}function C(e){e.key===` `&&x(!1)}function w(){x(!1),b()}function T(e){return(e.ctrlKey||e.metaKey)&&(e.key===`0`||e.key===`)`||e.code===`Digit0`||e.code===`Numpad0`)?(f(Pe(a,1,u())),!0):e.shiftKey&&!e.ctrlKey&&!e.metaKey&&(e.key===`!`||e.code===`Digit1`)?(E(),!0):!1}function E(){f(Q({x:0,y:0,w:i.offsetWidth,h:i.offsetHeight},u()))}function ee(e){let t=u();m({z:a.z,tx:t.width/2-(e.x+e.w/2)*a.z,ty:t.height/2-(e.y+e.h/2)*a.z})}return e.addEventListener(`wheel`,g,{passive:!1}),e.addEventListener(`mousedown`,v,!0),e.addEventListener(`mousemove`,y,!0),e.addEventListener(`mouseup`,b,!0),e.addEventListener(`keydown`,S),e.addEventListener(`keyup`,C),e.defaultView?.addEventListener(`blur`,w),{get:()=>a,set:f,fitAll:E,centerOn:ee,focusBox:e=>m(Q(e,u())),zoomToPercent:e=>f(Pe(a,e,u())),consumeDragClick:()=>{let e=c;return c=!1,e},handleKey:T,setSpace:x,destroy:()=>{e.removeEventListener(`wheel`,g),e.removeEventListener(`mousedown`,v,!0),e.removeEventListener(`mousemove`,y,!0),e.removeEventListener(`mouseup`,b,!0),e.removeEventListener(`keydown`,S),e.removeEventListener(`keyup`,C),e.defaultView?.removeEventListener(`blur`,w)}}}function Ve(e){let{clientX:t,rectLeft:n,rectWidth:r,pad:i,thumb:a,count:o}=e,s=r-i*2-a;if(s<=0||o<2)return null;let c=(t-n-i-a/2)/s;return Math.max(0,Math.min(o-1,c*(o-1)))}var He=H(`<div class=canvas-bg><span class=canvas-bg-label></span><div class=canvas-bg-track role=slider tabindex=0 aria-valuemin=0 style=--pad:3px;--thumb:20px><div class=canvas-bg-thumb aria-hidden=true><span class=canvas-bg-dot>`),$=[`light`,`gray`,`dark`],Ue={light:`Light`,gray:`Gray`,dark:`Dark`},We={light:`#ffffff`,gray:`#d2cec7`,dark:`#242b36`},Ge=`paintress:canvas-bg`;function Ke(e){return $.includes(e)}function qe(){try{let e=localStorage.getItem(Ge);if(Ke(e))return e}catch{}return`light`}function Je(e){try{localStorage.setItem(Ge,e)}catch{}}var Ye=3,Xe=20;function Ze(e){let[t,n]=y(!1),[r,i]=y(null),[a,o]=y(29),s,c=()=>$.indexOf(e.value),l=()=>r()??c();T(()=>{let e=()=>{let e=s;if(!e)return;let t=e.getBoundingClientRect().width-6-Xe;o(t/($.length-1))};if(e(),typeof ResizeObserver>`u`||!s)return;let t=new ResizeObserver(e);t.observe(s),E(()=>t.disconnect())});function u(e){let t=s;if(!t)return c();let n=t.getBoundingClientRect();return Ve({clientX:e.clientX,rectLeft:n.left,rectWidth:n.width,pad:Ye,thumb:Xe,count:$.length})??c()}function d(t){let n=$[Math.round(t)];n!==void 0&&n!==e.value&&e.onChange(n)}function f(e){e.button===0&&(e.preventDefault(),e.currentTarget.setPointerCapture(e.pointerId),n(!0),i(u(e)))}function p(e){t()&&i(u(e))}function m(e){if(!t())return;let r=e.currentTarget;n(!1),i(null),d(u(e)),r.hasPointerCapture(e.pointerId)&&r.releasePointerCapture(e.pointerId)}function h(){n(!1),i(null)}function g(t){let n=t.key===`ArrowRight`||t.key===`ArrowUp`?1:t.key===`ArrowLeft`||t.key===`ArrowDown`?-1:0;if(n===0)return;t.preventDefault();let r=$[Math.max(0,Math.min($.length-1,c()+n))];r!==void 0&&e.onChange(r)}return(()=>{var n=He(),r=n.firstChild,i=r.nextSibling,o=i.firstChild.firstChild;W(r,()=>Ue[e.value]),i.$$keydown=g,i.addEventListener(`lostpointercapture`,h),i.addEventListener(`pointercancel`,h),i.$$pointerup=m,i.$$pointermove=p,i.$$pointerdown=f;var u=s;return typeof u==`function`?ye(u,i):s=i,b(n=>{var r=!!t(),s=e.label,u=$.length-1,d=c(),f=Ue[e.value],p=e.label,m=`${a()}px`,h=String(l()),g=We[e.value];return r!==n.e&&i.classList.toggle(`dragging`,n.e=r),s!==n.t&&U(i,`aria-label`,n.t=s),u!==n.a&&U(i,`aria-valuemax`,n.a=u),d!==n.o&&U(i,`aria-valuenow`,n.o=d),f!==n.i&&U(i,`aria-valuetext`,n.i=f),p!==n.n&&U(i,`title`,n.n=p),m!==n.s&&ve(i,`--step`,n.s=m),h!==n.h&&ve(i,`--pos`,n.h=h),g!==n.r&&ve(o,`--dot`,n.r=g),n},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0,s:void 0,h:void 0,r:void 0}),n})()}_e([`pointerdown`,`pointermove`,`pointerup`,`keydown`]);var Qe={zh:{projects:`项目`,noProjects:`还没有注册项目`,projectFiles:e=>`${e} 个设计稿`,designs:`设计稿`,depth:`depth`,noFiles:`没有 .ptr / .pen 文件`,marks:`标记`,clear:`清空`,liveReload:`实时刷新`,refreshList:`刷新列表`,pickProject:`从左侧选择项目`,pickFile:`从左侧选择一个设计稿`,fit:`适应`,fitTitle:`适应窗口(Shift+1)`,actualSize:`1:1`,actualSizeTitle:`100%(Ctrl/Cmd+0)`,rerender:`重新渲染`,allBoards:`全部`,canvasBackground:`画布背景`,language:`界面语言`},en:{projects:`Projects`,noProjects:`No projects registered yet`,projectFiles:e=>`${e} design${e===1?``:`s`}`,designs:`Designs`,depth:`depth`,noFiles:`No .ptr / .pen files`,marks:`Marks`,clear:`Clear`,liveReload:`Live reload`,refreshList:`Refresh`,pickProject:`Pick a project on the left`,pickFile:`Pick a design on the left`,fit:`Fit`,fitTitle:`Fit to window (Shift+1)`,actualSize:`1:1`,actualSizeTitle:`100% (Ctrl/Cmd+0)`,rerender:`Re-render`,allBoards:`All`,canvasBackground:`Canvas background`,language:`Language`}};function $e(e){return Qe[e]}function et(e){return(e?.languages?.[0]??e?.language??``).toLowerCase().startsWith(`zh`)?`zh`:`en`}var tt=`paintress:lang`;function nt(e){return e===`zh`||e===`en`}function rt(){try{let e=localStorage.getItem(tt);if(nt(e))return e}catch{}return et(typeof navigator>`u`?void 0:navigator)}function it(e){try{localStorage.setItem(tt,e)}catch{}}var at=H(`<span class=badge>v`),ot=H(`<ul class=list>`),st=H(`<section class="section files"><h2> <span class=hint> `),ct=H(`<section class="section marks"><h2><button class="ghost tiny"></button></h2><ul class=list>`),lt=H(`<span class=hint>pid `),ut=H(`<div class=error>`),dt=H(`<code>/`),ft=H(`<span class="hint zoom">%`),pt=H(`<button class="ghost tiny">`),mt=H(`<button class=ghost>`),ht=H(`<span class=tool-sep>`),gt=H(`<div class=boardbar><button class=board>`),_t=H(`<iframe class=preview title="design preview"sandbox="allow-same-origin allow-scripts">`),vt=H(`<div class=app><aside class=sidebar><header class=sidebar-head><img class=brand-mark src=/logo.svg alt><h1>paintress</h1></header><section class=section><h2></h2></section><footer class=sidebar-foot><label class=toggle><input type=checkbox></label><button class=ghost></button></footer></aside><main class=stage><div class=toolbar><div class=crumbs></div><div class=tools><div class=lang-switch role=group><button class=lang-opt>中</button><button class=lang-opt>EN`),yt=H(`<p class=empty><br><code>paintress watch &lt;name&gt; &lt;dir&gt;`),bt=H(`<li><button class=item><span class=item-name></span><span class=item-meta>`),xt=H(`<p class=empty>`),St=H(`<li><button class=item><span class=item-name>`),Ct=H(`<li><button class=item><span class=item-name><b class=mark-no>#</b> </span><span class=item-meta>`),wt=H(`<span class=sep>/`),Tt=H(`<button class=crumb>`),Et=H(`<p>`),Dt=H(`<div class=placeholder>`),Ot=H(`<button class=board><span class=board-size>×`);function kt(e){return typeof CSS<`u`&&typeof CSS.escape==`function`?CSS.escape(e):e.replace(/["\\]/g,`\\$&`)}function At(){let[e,t]=y([]),[n,r]=y(null),[i,a]=y([]),[o,s]=y(null),[c,l]=y([]),[u,d]=y(null),[f,p]=y([]),[m,h]=y(null),[g,_]=y({}),[v,S]=y(0),[C,ee]=y(!0),[te,D]=y(``),[O,ne]=y([]),[re,k]=y(null),[ie,ae]=y(1),[A,j]=y(rt()),[M,oe]=y(qe()),N=()=>$e(A()),P,F,I=null,se;function ce(){let e=new URLSearchParams,t=n(),r=o();t&&e.set(`project`,t),r&&e.set(`file`,r);let i=e.toString();history.replaceState(null,``,i?`?${i}`:location.pathname)}async function le(){try{let e=await Ce();t(e.projects),_({pid:e.pid,version:e.version}),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}}async function ue(e){try{l((await Oe(e)).marks)}catch{l([])}}async function de(e){r(e),s(null),d(null),p([]);try{let t=await we(e);a(t.files),h(null)}catch(e){a([]),h(e instanceof Error?e.message:String(e))}ue(e),ce()}async function fe(){await le();let t=n();if(t!==null){if(!e().some(e=>e.name===t))r(null),s(null),a([]),d(null),p([]),ce();else{try{let n=await we(t),r=i();a(n.files);let c=o();if(c===null){let i=n.files.filter(e=>!r.includes(e));i.length>0&&e().some(e=>e.name===t)&&pe(i[0])}else n.files.includes(c)||(s(null),d(null),p([]),ce())}catch{}ue(t)}}}function pe(e){s(e),d(null),p([]),ce()}T(()=>{let e=new EventSource(De());e.addEventListener(`projects`,()=>void fe()),E(()=>e.close());let t=e=>{let t=e.target;return t?.tagName===`INPUT`||t?.tagName===`TEXTAREA`||t?.isContentEditable===!0},n=e=>{t(e)||(e.key===` `&&!e.ctrlKey&&!e.metaKey&&!e.altKey?(I?.setSpace(!0),e.preventDefault()):I?.handleKey(e)===!0&&e.preventDefault())},r=e=>{e.key===` `&&I?.setSpace(!1)};window.addEventListener(`keydown`,n),window.addEventListener(`keyup`,r),E(()=>{window.removeEventListener(`keydown`,n),window.removeEventListener(`keyup`,r)});let i=new URLSearchParams(location.search);(async()=>{await le();let e=i.get(`project`);if(e){await de(e);let t=i.get(`file`);t&&pe(t)}})()});let R=()=>P?.contentDocument??null;function he(){let e=R();if(!e||e.getElementById(`ptr-style`))return;let t=e.createElement(`style`);t.id=`ptr-style`,t.textContent=`[data-ptr-selected]{outline:2px solid #908030 !important;outline-offset:-2px;}
2
+ [data-ptr-hover]:not([data-ptr-selected]){outline:1px dashed rgba(144,128,48,.55) !important;outline-offset:-2px;}
3
+ [data-ptr-changed]{box-shadow:inset 0 0 0 2px #908030 !important;animation:ptr-pulse 1.4s ease-in-out 3;}
4
+ @keyframes ptr-pulse{0%,100%{box-shadow:inset 0 0 0 2px #908030}50%{box-shadow:inset 0 0 0 2px rgba(144,128,48,.35)}}
5
+ html{cursor:default;}
6
+ html,body{overflow:hidden;}
7
+ html,body{background:var(--ptr-canvas-bg,#ffffff);}`,e.head.appendChild(t)}function V(){let e=R();if(!e)return;for(let t of e.querySelectorAll(`[data-ptr-selected]`))t.removeAttribute(`data-ptr-selected`);let t=u();t&&e.querySelector(`[data-node-id="${kt(t)}"]`)?.setAttribute(`data-ptr-selected`,``)}function ge(e){return e.parentElement?.closest(`[data-node-id]`)??null}function H(e){return e.querySelector(`:scope > [data-node-id]`)}function _e(e){if(I?.consumeDragClick()===!0){e.preventDefault(),e.stopPropagation();return}let t=e.target?.closest(`[data-node-id]`);t&&(e.preventDefault(),e.stopPropagation(),Me(e.altKey?ge(t)??t:t))}function ve(e,t){let n=R();if(!n)return;let r=u(),i=r===null?null:n.querySelector(`[data-node-id="${kt(r)}"]`);if(i===null){let e=(n.elementFromPoint(t.x,t.y)?.closest(`[data-node-id]`))?.dataset.nodeId;e!==void 0&&Z(e);return}let a=(e===1?ge(i):H(i))?.dataset.nodeId;a!==void 0&&Z(a)}function be(e){let t=R();if(t){for(let e of t.querySelectorAll(`[data-ptr-hover]`))e.removeAttribute(`data-ptr-hover`);(e.target?.closest(`[data-node-id]`))?.setAttribute(`data-ptr-hover`,``)}}async function xe(){let e=n(),t=o();if(e&&t)try{let n=await je(e,t);if(!n.ok||n.ids.length===0)return;let r=R();if(!r)return;for(let e of n.ids)r.querySelector(`[data-node-id="${kt(e)}"]`)?.setAttribute(`data-ptr-changed`,``)}catch{}}function G(){let e=R();return e?Array.from(e.querySelectorAll(`[data-board]`)).map(e=>{let t=e.dataset;return{id:t.nodeId??``,...t.boardName===void 0?{}:{name:t.boardName},x:Number(t.boardX??0),y:Number(t.boardY??0),w:Number(t.boardW??0),h:Number(t.boardH??0)}}):[]}function K(){let e=n(),t=o();return e&&t?`paintress:viewport:${e}/${t}`:null}function Se(e){let t=K();t!==null&&(clearTimeout(se),se=setTimeout(()=>{try{localStorage.setItem(t,JSON.stringify(e))}catch{}},300))}function q(){let e=R();e&&(he(),Pe(),I?.destroy(),I=Be({doc:e,onChange:e=>{ae(e.z),Se(e)},onLevelStep:ve}),ne(G()),k(null),J(),e.addEventListener(`click`,_e,!0),e.addEventListener(`mousemove`,be,!0),V(),xe())}function J(){if(!I)return;let e=K(),t=e===null?null:localStorage.getItem(e),n=t===null?null:Le(t);n===null?I.fitAll():I.set(n)}function Y(e){if(k(e),!I)return;if(e===null){I.fitAll();return}let t=O().find(t=>t.id===e);t&&I.focusBox({x:t.x,y:t.y,w:t.w,h:t.h})}function X(e){let t=[],n=e;for(;n;){let e=n.closest(`[data-node-id]`);if(!e)break;let r=e;t.unshift({id:r.dataset.nodeId??``,type:r.dataset.nodeType??`?`,name:r.dataset.nodeName}),n=e.parentElement}return t}async function Me(e){let t=e.dataset.nodeId??``;if(t===``)return;let r=X(e);d(t),p(r),V();let i=n();if(i)try{await ke({project:i,nodeId:t,...e.dataset.nodeName===void 0?{}:{name:e.dataset.nodeName},...e.dataset.nodeType===void 0?{}:{type:e.dataset.nodeType},path:r.map(e=>e.name??e.type).join(` / `)}),l((await Oe(i)).marks),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}}function Z(e){d(e),V();let t=R(),n=t?.querySelector(`[data-node-id="${kt(e)}"]`);if(!n||!t){p([]);return}p(X(n));let r=t.querySelector(`.ptr-canvas`);if(!r||!I){n.scrollIntoView({block:`center`});return}let i=n.getBoundingClientRect(),a=r.getBoundingClientRect(),o=I.get().z;I.centerOn({x:(i.left-a.left)/o,y:(i.top-a.top)/o,w:i.width/o,h:i.height/o})}async function Ne(){let e=n();if(e)try{await Ae(e),l([]),d(null),p([]),V()}catch(e){h(e instanceof Error?e.message:String(e))}}x(()=>{let e=A();document.documentElement.lang=e===`zh`?`zh-CN`:`en`,it(e)});function Pe(){if(F!==void 0){if(o()===null)F.style.removeProperty(`--canvas-bg`),F.removeAttribute(`data-canvas-bg`);else{let e=We[M()];F.style.setProperty(`--canvas-bg`,e),F.setAttribute(`data-canvas-bg`,M())}R()?.documentElement.style.setProperty(`--ptr-canvas-bg`,We[M()])}}x(()=>{M(),Je(M()),Pe()}),x(w([n,o,v],()=>{let e=n(),t=o();D(e&&t?`${Te(e,t)}&r=${v()}`:``),Pe()}));let Q=null;return x(w([n,o,C],()=>{Q?.close(),Q=null;let e=n(),t=o();C()&&e&&t&&(Q=new EventSource(Ee(e,t)),Q.addEventListener(`change`,()=>S(e=>e+1)))})),E(()=>Q?.close()),(()=>{var t=vt(),r=t.firstChild,a=r.firstChild;a.firstChild.nextSibling;var s=a.nextSibling,l=s.firstChild,d=s.nextSibling,p=d.firstChild,h=p.firstChild,_=p.nextSibling,v=r.nextSibling,y=v.firstChild,x=y.firstChild,w=x.nextSibling,T=w.firstChild,E=T.firstChild,D=E.nextSibling;return W(a,L(B,{get when(){return g().version},get children(){var e=at();return e.firstChild,W(e,()=>g().version,null),e}}),null),W(l,()=>N().projects),W(s,L(B,{get when(){return e().length>0},get fallback(){return(()=>{var e=yt(),t=e.firstChild;return W(e,()=>N().noProjects,t),e})()},get children(){var t=ot();return W(t,L(z,{get each(){return e()},children:e=>(()=>{var t=bt(),r=t.firstChild,i=r.firstChild,a=i.nextSibling;return r.$$click=()=>void de(e.name),W(i,()=>e.name),W(a,()=>N().projectFiles(e.fileCount)),b(()=>r.classList.toggle(`active`,n()===e.name)),t})()})),t}}),null),W(r,L(B,{get when(){return n()},get children(){var t=st(),r=t.firstChild,a=r.firstChild,s=a.nextSibling,c=s.firstChild;return W(r,()=>N().designs,a),W(s,()=>N().depth,c),W(s,()=>e().find(e=>e.name===n())?.depth??`-`,null),W(t,L(B,{get when(){return i().length>0},get fallback(){return(()=>{var e=xt();return W(e,()=>N().noFiles),e})()},get children(){var e=ot();return W(e,L(z,{get each(){return i()},children:e=>(()=>{var t=St(),n=t.firstChild,r=n.firstChild;return n.$$click=()=>pe(e),W(r,e),b(()=>n.classList.toggle(`active`,o()===e)),t})()})),e}}),null),t}}),d),W(r,L(B,{get when(){return c().length>0},get children(){var e=ct(),t=e.firstChild,n=t.firstChild,r=t.nextSibling;return W(t,()=>N().marks,n),n.$$click=()=>void Ne(),W(n,()=>N().clear),W(r,L(z,{get each(){return c()},children:e=>(()=>{var t=Ct(),n=t.firstChild,r=n.firstChild,i=r.firstChild;i.firstChild,i.nextSibling;var a=r.nextSibling;return n.$$click=()=>Z(e.nodeId),W(i,()=>e.shortId,null),W(r,()=>e.name??e.nodeId,null),W(a,()=>e.path),b(()=>n.classList.toggle(`active`,u()===e.nodeId)),t})()})),e}}),d),h.addEventListener(`change`,e=>ee(e.currentTarget.checked)),W(p,()=>N().liveReload,null),_.$$click=()=>void le(),W(_,()=>N().refreshList),W(d,L(B,{get when(){return g().pid},get children(){var e=lt();return e.firstChild,W(e,()=>g().pid,null),e}}),null),ye(e=>{F=e},v),W(v,L(B,{get when(){return m()},get children(){var e=ut();return W(e,m),e}}),y),W(x,L(B,{get when(){return f().length===0},get fallback(){return L(z,{get each(){return f()},children:(e,t)=>[L(B,{get when(){return t()>0},get children(){return wt()}}),(()=>{var t=Tt();return t.$$click=()=>Z(e.id),W(t,()=>e.name??e.type),b(()=>t.classList.toggle(`active`,u()===e.id)),t})()]})},get children(){return L(B,{get when(){return me(()=>n()!==null)()&&o()!==null},get children(){var e=dt(),t=e.firstChild;return W(e,n,t),W(e,o,null),e}})}})),W(w,L(B,{get when(){return me(()=>n()!==null)()&&o()!==null},get children(){return[(()=>{var e=ft(),t=e.firstChild;return W(e,()=>Math.round(ie()*100),t),e})(),(()=>{var e=pt();return e.$$click=()=>I?.fitAll(),W(e,()=>N().fit),b(()=>U(e,`title`,N().fitTitle)),e})(),(()=>{var e=pt();return e.$$click=()=>I?.zoomToPercent(1),W(e,()=>N().actualSize),b(()=>U(e,`title`,N().actualSizeTitle)),e})(),(()=>{var e=mt();return e.$$click=()=>S(e=>e+1),W(e,()=>N().rerender),e})(),ht()]}}),T),W(w,L(Ze,{get value(){return M()},onChange:oe,get label(){return N().canvasBackground}}),T),E.$$click=()=>j(`zh`),D.$$click=()=>j(`en`),W(v,L(B,{get when(){return me(()=>n()!==null)()&&o()!==null},get fallback(){return(()=>{var e=Dt();return W(e,L(B,{get when(){return n()},get fallback(){return(()=>{var e=Et();return W(e,()=>N().pickProject),e})()},get children(){var e=Et();return W(e,()=>N().pickFile),e}})),e})()},get children(){return[L(B,{get when(){return O().length>1},get children(){var e=gt(),t=e.firstChild;return t.$$click=()=>Y(null),W(t,()=>N().allBoards),W(e,L(z,{get each(){return O()},children:e=>(()=>{var t=Ot(),n=t.firstChild,r=n.firstChild;return t.$$click=()=>Y(e.id),W(t,()=>e.name??`#${e.id}`,n),W(n,()=>Math.round(e.w),r),W(n,()=>Math.round(e.h),null),b(()=>t.classList.toggle(`active`,re()===e.id)),t})()}),null),b(()=>t.classList.toggle(`active`,re()===null)),e}}),(()=>{var e=_t();return e.addEventListener(`load`,q),ye(e=>{P=e},e),b(()=>U(e,`src`,te())),e})()]}}),null),b(e=>{var t=N().language,n=N().language,r=A()===`zh`,i=A()===`zh`,a=A()===`en`,o=A()===`en`;return t!==e.e&&U(T,`aria-label`,e.e=t),n!==e.t&&U(T,`title`,e.t=n),r!==e.a&&E.classList.toggle(`active`,e.a=r),i!==e.o&&U(E,`aria-pressed`,e.o=i),a!==e.i&&D.classList.toggle(`active`,e.i=a),o!==e.n&&U(D,`aria-pressed`,e.n=o),e},{e:void 0,t:void 0,a:void 0,o:void 0,i:void 0,n:void 0}),b(()=>h.checked=C()),t})()}_e([`click`]);var jt=document.getElementById(`root`);if(!jt)throw Error(`缺少 #root 容器`);ge(()=>L(At,{}),jt);
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>paintress</title>
7
- <script type="module" crossorigin src="/assets/index-WQnLS4sD.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-lyiZ9W-B.css">
7
+ <script type="module" crossorigin src="/assets/index-om6XE6pt.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-BEKlFXv9.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -1,6 +0,0 @@
1
- (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e={context:void 0,registry:void 0,effects:void 0,done:!1,getContextId(){return t(this.context.count)},getNextContextId(){return t(this.context.count++)}};function t(t){let n=String(t),r=n.length-1;return e.context.id+(r?String.fromCharCode(96+r):``)+n}function n(t){e.context=t}var r=(e,t)=>e===t,i=Symbol(`solid-track`),a={equals:r},o=null,s=N,c=1,l=2,u={owned:null,cleanups:null,context:null,owner:null},d=null,f=null,p=null,m=null,h=null,g=0;function _(e,t){let n=p,r=d,i=e.length===0,a=t===void 0?r:t,o=i?u:{owned:null,cleanups:null,context:a?a.context:null,owner:a},s=i?e:()=>e(v(o));d=o,p=null;try{return M(s,!0)}finally{p=n,d=r}}function v(e){return()=>C(()=>I(e))}function y(e,t){t=t?Object.assign({},a,t):a;let n={value:e,observers:null,observerSlots:null,comparator:t.equals||void 0};return[re.bind(n),e=>(typeof e==`function`&&(e=f&&f.running&&f.sources.has(n)?e(n.tValue):e(n.value)),O(n,e))]}function b(e,t,n){k(A(e,t,!1,c))}function x(e,t,n){s=oe;let r=A(e,t,!1,c),i=D&&ne(D);i&&(r.suspense=i),n&&n.render||(r.user=!0),h?h.push(r):k(r)}function S(e,t,n){n=n?Object.assign({},a,n):a;let r=A(e,t,!0,0);return r.observers=null,r.observerSlots=null,r.comparator=n.equals||void 0,k(r),re.bind(r)}function C(e){if(p===null)return e();let t=p;p=null;try{return e()}finally{p=t}}function w(e,t,n){let r=Array.isArray(e),i,a=n&&n.defer;return n=>{let o;if(r){o=Array(e.length);for(let t=0;t<e.length;t++)o[t]=e[t]()}else o=e();if(a)return a=!1,n;let s=C(()=>t(o,i,n));return i=o,s}}function ee(e){x(()=>C(e))}function T(e){return d===null||(d.cleanups===null?d.cleanups=[e]:d.cleanups.push(e)),e}var[te,E]=y(!1);function ne(e){let t;return d&&d.context&&(t=d.context[e.id])!==void 0?t:e.defaultValue}var D;function re(){let e=f&&f.running;if(this.sources&&(e?this.tState:this.state)){if((e?this.tState:this.state)===c)k(this);else{let e=m;m=null,M(()=>P(this),!1),m=e}}if(p){let e=this.observers;if(!e||e[e.length-1]!==p){let t=e?e.length:0;p.sources?(p.sources.push(this),p.sourceSlots.push(t)):(p.sources=[this],p.sourceSlots=[t]),e?(e.push(p),this.observerSlots.push(p.sources.length-1)):(this.observers=[p],this.observerSlots=[p.sources.length-1])}}return e&&f.sources.has(this)?this.tValue:this.value}function O(e,t,n){let r=f&&f.running&&f.sources.has(e)?e.tValue:e.value;if(!e.comparator||!e.comparator(r,t)){if(f){let r=f.running;(r||!n&&f.sources.has(e))&&(f.sources.add(e),e.tValue=t),r||(e.value=t)}else e.value=t;e.observers&&e.observers.length&&M(()=>{for(let t=0;t<e.observers.length;t+=1){let n=e.observers[t],r=f&&f.running;r&&f.disposed.has(n)||((r?!n.tState:!n.state)&&(n.pure?m.push(n):h.push(n),n.observers&&F(n)),r?n.tState=c:n.state=c)}if(m.length>1e6)throw m=[],Error()},!1)}return t}function k(e){if(!e.fn)return;I(e);let t=g;ie(e,f&&f.running&&f.sources.has(e)?e.tValue:e.value,t),f&&!f.running&&f.sources.has(e)&&queueMicrotask(()=>{M(()=>{f&&(f.running=!0),p=d=e,ie(e,e.tValue,t),p=d=null},!1)})}function ie(e,t,n){let r,i=d,a=p;p=d=e;try{r=e.fn(t)}catch(t){return e.pure&&(f&&f.running?(e.tState=c,e.tOwned&&e.tOwned.forEach(I),e.tOwned=void 0):(e.state=c,e.owned&&e.owned.forEach(I),e.owned=null)),e.updatedAt=n+1,z(t)}finally{p=a,d=i}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&`observers`in e?O(e,r,!0):f&&f.running&&e.pure?(f.sources.has(e)||(e.value=r),f.sources.add(e),e.tValue=r):e.value=r,e.updatedAt=n)}function A(e,t,n,r=c,i){let a={fn:e,state:r,updatedAt:null,owned:null,sources:null,sourceSlots:null,cleanups:null,value:t,owner:d,context:d?d.context:null,pure:n};return f&&f.running&&(a.state=0,a.tState=r),d===null||d!==u&&(f&&f.running&&d.pure?d.tOwned?d.tOwned.push(a):d.tOwned=[a]:d.owned?d.owned.push(a):d.owned=[a]),a}function j(e){let t=f&&f.running;if((t?e.tState:e.state)===0)return;if((t?e.tState:e.state)===l)return P(e);if(e.suspense&&C(e.suspense.inFallback))return e.suspense.effects.push(e);let n=[e];for(;(e=e.owner)&&(!e.updatedAt||e.updatedAt<g);){if(t&&f.disposed.has(e))return;(t?e.tState:e.state)&&n.push(e)}for(let r=n.length-1;r>=0;r--){if(e=n[r],t){let t=e,i=n[r+1];for(;(t=t.owner)&&t!==i;)if(f.disposed.has(t))return}if((t?e.tState:e.state)===c)k(e);else if((t?e.tState:e.state)===l){let t=m;m=null,M(()=>P(e,n[0]),!1),m=t}}}function M(e,t){if(m)return e();let n=!1;t||(m=[]),h?n=!0:h=[],g++;try{let t=e();return ae(n),t}catch(e){n||(h=null),m=null,z(e)}}function ae(e){if(m&&=(N(m),null),e)return;let t;if(f){if(!f.promises.size&&!f.queue.size){let e=f.sources,n=f.disposed;h.push.apply(h,f.effects),t=f.resolve;for(let e of h)`tState`in e&&(e.state=e.tState),delete e.tState;f=null,M(()=>{for(let e of n)I(e);for(let t of e){if(t.value=t.tValue,t.owned)for(let e=0,n=t.owned.length;e<n;e++)I(t.owned[e]);t.tOwned&&(t.owned=t.tOwned),delete t.tValue,delete t.tOwned,t.tState=0}E(!1)},!1)}else if(f.running){f.running=!1,f.effects.push.apply(f.effects,h),h=null,E(!0);return}}let n=h;h=null,n.length&&M(()=>s(n),!1),t&&t()}function N(e){for(let t=0;t<e.length;t++)j(e[t])}function oe(t){let r,i=0;for(r=0;r<t.length;r++){let e=t[r];e.user?t[i++]=e:j(e)}if(e.context){if(e.count){e.effects||=[],e.effects.push(...t.slice(0,i));return}n()}for(e.effects&&(e.done||!e.count)&&(t=[...e.effects,...t],i+=e.effects.length,delete e.effects),r=0;r<i;r++)j(t[r])}function P(e,t){let n=f&&f.running;n?e.tState=0:e.state=0;for(let r=0;r<e.sources.length;r+=1){let i=e.sources[r];if(i.sources){let e=n?i.tState:i.state;e===c?i!==t&&(!i.updatedAt||i.updatedAt<g)&&j(i):e===l&&P(i,t)}}}function F(e){let t=f&&f.running;for(let n=0;n<e.observers.length;n+=1){let r=e.observers[n];(t?!r.tState:!r.state)&&(t?r.tState=l:r.state=l,r.pure?m.push(r):h.push(r),r.observers&&F(r))}}function I(e){let t;if(e.sources)for(;e.sources.length;){let t=e.sources.pop(),n=e.sourceSlots.pop(),r=t.observers;if(r&&r.length){let e=r.pop(),i=t.observerSlots.pop();n<r.length&&(e.sourceSlots[i]=n,r[n]=e,t.observerSlots[n]=i)}}if(e.tOwned){for(t=e.tOwned.length-1;t>=0;t--)I(e.tOwned[t]);delete e.tOwned}if(f&&f.running&&e.pure)L(e,!0);else if(e.owned){for(t=e.owned.length-1;t>=0;t--)I(e.owned[t]);e.owned=null}if(e.cleanups){for(t=e.cleanups.length-1;t>=0;t--)e.cleanups[t]();e.cleanups=null}f&&f.running?e.tState=0:e.state=0}function L(e,t){if(t||(e.tState=0,f.disposed.add(e)),e.owned)for(let t=0;t<e.owned.length;t++)L(e.owned[t])}function se(e){return e instanceof Error?e:Error(typeof e==`string`?e:`Unknown error`,{cause:e})}function R(e,t,n){try{for(let n of t)n(e)}catch(e){z(e,n&&n.owner||null)}}function z(e,t=d){let n=o&&t&&t.context&&t.context[o],r=se(e);if(!n)throw r;h?h.push({fn(){R(r,n,t)},state:c}):R(r,n,t)}var ce=Symbol(`fallback`);function B(e){for(let t=0;t<e.length;t++)e[t]()}function le(e,t,n={}){let r=[],a=[],o=[],s=0,c=t.length>1?[]:null;return T(()=>B(o)),()=>{let l=e()||[],u=l.length,d,f;return l[i],C(()=>{let e,t,i,m,h,g,v,y,b;if(u===0)s!==0&&(B(o),o=[],r=[],a=[],s=0,c&&=[]),n.fallback&&(r=[ce],a[0]=_(e=>(o[0]=e,n.fallback())),s=1);else if(s===0){for(a=Array(u),f=0;f<u;f++)r[f]=l[f],a[f]=_(p);s=u}else{for(i=Array(u),m=Array(u),c&&(h=Array(u)),g=0,v=Math.min(s,u);g<v&&r[g]===l[g];g++);for(v=s-1,y=u-1;v>=g&&y>=g&&r[v]===l[y];v--,y--)i[y]=a[v],m[y]=o[v],c&&(h[y]=c[v]);for(e=new Map,t=Array(y+1),f=y;f>=g;f--)b=l[f],d=e.get(b),t[f]=d===void 0?-1:d,e.set(b,f);for(d=g;d<=v;d++)b=r[d],f=e.get(b),f!==void 0&&f!==-1?(i[f]=a[d],m[f]=o[d],c&&(h[f]=c[d]),f=t[f],e.set(b,f)):o[d]();for(f=g;f<u;f++)f in i?(a[f]=i[f],o[f]=m[f],c&&(c[f]=h[f],c[f](f))):a[f]=_(p);a=a.slice(0,s=u),r=l.slice(0)}return a});function p(e){if(o[f]=e,c){let[e,n]=y(f);return c[f]=n,t(l[f],e)}return t(l[f])}}}function V(e,t){return C(()=>e(t||{}))}var ue=e=>`Stale read from <${e}>.`;function H(e){let t=`fallback`in e&&{fallback:()=>e.fallback};return S(le(()=>e.each,e.children,t||void 0))}function U(e){let t=e.keyed,n=S(()=>e.when,void 0,void 0),r=t?n:S(n,void 0,{equals:(e,t)=>!e==!t});return S(()=>{let i=r();if(i){let a=e.children;return typeof a==`function`&&a.length>0?C(()=>a(t?i:()=>{if(!C(r))throw ue(`Show`);return n()})):a}return e.fallback},void 0,void 0)}var de=e=>S(()=>e());function fe(e,t,n){let r=n.length,i=t.length,a=r,o=0,s=0,c=t[i-1].nextSibling,l=null;for(;o<i||s<a;)if(t[o]===n[s])o++,s++;else{for(;t[i-1]===n[a-1];)i--,a--;if(i===o){let t=a<r?s?n[s-1].nextSibling:n[a-s]:c;for(;s<a;)e.insertBefore(n[s++],t)}else if(a===s)for(;o<i;)l&&l.has(t[o])||t[o].remove(),o++;else if(t[o]===n[a-1]&&n[s]===t[i-1]){let r=t[--i].nextSibling;e.insertBefore(n[s++],t[o++].nextSibling),e.insertBefore(n[--a],r),t[i]=n[a]}else{if(!l){l=new Map;let e=s;for(;e<a;)l.set(n[e],e++)}let r=l.get(t[o]);if(r!=null){if(s<r&&r<a){let c=o,u=1,d;for(;++c<i&&c<a&&(d=l.get(t[c]))!=null&&d===r+u;)u++;if(u>r-s){let i=t[o];for(;s<r;)e.insertBefore(n[s++],i)}else e.replaceChild(n[s++],t[o++])}else o++}else t[o++].remove()}}}var pe=`_$DX_DELEGATE`;function me(e,t,n,r={}){let i;return _(r=>{i=r,t===document?e():G(t,e(),!n&&t.firstChild?null:void 0,n)},r.owner),()=>{i(),t.textContent=``}}function W(e,t,n,r){let i,a=()=>{let t=r?document.createElementNS(`http://www.w3.org/1998/Math/MathML`,`template`):document.createElement(`template`);return t.innerHTML=e,n?t.content.firstChild.firstChild:r?t.firstChild:t.content.firstChild},o=t?()=>C(()=>document.importNode(i||=a(),!0)):()=>(i||=a()).cloneNode(!0);return o.cloneNode=o,o}function he(e,t=window.document){let n=t[pe]||(t[pe]=new Set);for(let r=0,i=e.length;r<i;r++){let i=e[r];n.has(i)||(n.add(i),t.addEventListener(i,ye))}}function ge(e,t,n){ve(e)||(n==null?e.removeAttribute(t):e.setAttribute(t,n))}function _e(e,t,n){return C(()=>e(t,n))}function G(e,t,n,r){if(n!==void 0&&!r&&(r=[]),typeof t!=`function`)return K(e,t,r,n);b(r=>K(e,t(),r,n),r)}function ve(t){return!!e.context&&!e.done&&(!t||t.isConnected)}function ye(t){if(e.registry&&e.events&&e.events.find(([e,n])=>n===t))return;let n=t.target,r=`$$${t.type}`,i=t.target,a=t.currentTarget,o=e=>Object.defineProperty(t,"target",{configurable:!0,value:e}),s=()=>{let e=n[r];if(e&&!n.disabled){let i=n[`${r}Data`];if(i===void 0?e.call(n,t):e.call(n,i,t),t.cancelBubble)return}return n.host&&typeof n.host!=`string`&&!n.host._$host&&n.contains(t.target)&&o(n.host),!0},c=()=>{for(;s()&&(n=n._$host||n.parentNode||n.host););};Object.defineProperty(t,"currentTarget",{configurable:!0,get(){return n||document}}),e.registry&&!e.done&&(e.done=_$HY.done=!0);try{if(t.composedPath){let e=t.composedPath();o(e[0]);for(let t=0;t<e.length-2&&(n=e[t],s());t++){if(n._$host){n=n._$host,c();break}if(n.parentNode===a)break}}else c();o(i)}finally{t=n=null}}function K(e,t,n,r,i){let a=ve(e);if(a){!n&&(n=[...e.childNodes]);let t=[];for(let e=0;e<n.length;e++){let r=n[e];r.nodeType===8&&r.data.slice(0,2)===`!$`?r.remove():t.push(r)}n=t}for(;typeof n==`function`;)n=n();if(t===n)return n;let o=typeof t,s=r!==void 0;if(e=s&&n[0]&&n[0].parentNode||e,o===`string`||o===`number`){if(a||o===`number`&&(t=t.toString(),t===n))return n;if(s){let i=n[0];i&&i.nodeType===3?i.data!==t&&(i.data=t):i=document.createTextNode(t),n=Y(e,n,r,i)}else n=n!==``&&typeof n==`string`?e.firstChild.data=t:e.textContent=t}else if(t==null||o===`boolean`){if(a)return n;n=Y(e,n,r)}else if(o===`function`)return b(()=>{let i=t();for(;typeof i==`function`;)i=i();n=K(e,i,n,r)}),()=>n;else if(Array.isArray(t)){let o=[],c=n&&Array.isArray(n);if(q(o,t,n,i))return b(()=>n=K(e,o,n,r,!0)),()=>n;if(a){if(!o.length)return n;if(r===void 0)return n=[...e.childNodes];let t=o[0];if(t.parentNode!==e)return n;let i=[t];for(;(t=t.nextSibling)!==r;)i.push(t);return n=i}if(o.length===0){if(n=Y(e,n,r),s)return n}else c?n.length===0?J(e,o,r):fe(e,n,o):(n&&Y(e),J(e,o));n=o}else if(t.nodeType){if(a&&t.parentNode)return n=s?[t]:t;if(Array.isArray(n)){if(s)return n=Y(e,n,r,t);Y(e,n,null,t)}else n==null||n===``||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}return n}function q(e,t,n,r){let i=!1;for(let a=0,o=t.length;a<o;a++){let o=t[a],s=n&&n[e.length],c;if(o!=null&&o!==!0&&o!==!1){if((c=typeof o)==`object`&&o.nodeType)e.push(o);else if(Array.isArray(o))i=q(e,o,s)||i;else if(c===`function`){if(r){for(;typeof o==`function`;)o=o();i=q(e,Array.isArray(o)?o:[o],Array.isArray(s)?s:[s])||i}else e.push(o),i=!0}else{let t=String(o);s&&s.nodeType===3&&s.data===t?e.push(s):e.push(document.createTextNode(t))}}}return i}function J(e,t,n=null){for(let r=0,i=t.length;r<i;r++)e.insertBefore(t[r],n)}function Y(e,t,n,r){if(n===void 0)return e.textContent=``;let i=r||document.createTextNode(``);if(t.length){let r=!1;for(let a=t.length-1;a>=0;a--){let o=t[a];if(i!==o){let t=o.parentNode===e;!r&&!a?t?e.replaceChild(i,o):e.insertBefore(i,n):t&&o.remove()}else r=!0}}else e.insertBefore(i,n);return[i]}async function X(e){let t=await fetch(e,{headers:{accept:`application/json`}});if(!t.ok){let e=await t.text().catch(()=>``);throw Error(`HTTP ${t.status}${e?`: ${e.slice(0,200)}`:``}`)}return await t.json()}function be(){return X(`/api/projects`)}function xe(e){return X(`/api/projects/${encodeURIComponent(e)}/files`)}function Se(e,t){return`/render/${encodeURIComponent(e)}/${t.split(`/`).map(encodeURIComponent).join(`/`)}?embed=1`}function Ce(e,t){return`/events?project=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`}function we(e){return X(`/api/marks?project=${encodeURIComponent(e)}`)}async function Te(e){let t=await fetch(`/api/marks`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify(e)});if(!t.ok)throw Error(`HTTP ${t.status}`);let n=await t.json();if(!n.ok)throw Error(n.error??`添加标记失败`);return n.mark}async function Ee(e){let t=await fetch(`/api/marks?project=${encodeURIComponent(e)}`,{method:`DELETE`});if(!t.ok)throw Error(`HTTP ${t.status}`);return(await t.json()).cleared}function De(e,t){return X(`/api/changes?project=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`)}var Z=.1,Oe=.02;function ke(e,t){return t===0||Math.abs(e)>=Math.abs(t)?null:t>0?-1:1}function Q(e,t=Z){return Math.min(4,Math.max(t,e))}function Ae(e,t,n,r,i=Z){let a=Q(e.z*t,i);if(a===e.z)return e;let o=a/e.z;return{z:a,tx:n-(n-e.tx)*o,ty:r-(r-e.ty)*o}}function je(e,t,n,r=Z){return Ae(e,Q(t,r)/e.z,n.width/2,n.height/2,r)}function Me(e,t,n=24){let r=Math.max(1,e.w),i=Math.max(1,e.h),a=Math.max(1,t.width-n*2),o=Math.max(1,t.height-n*2),s=Q(Math.min(a/r,o/i),Oe);return{z:s,tx:(t.width-r*s)/2-e.x*s,ty:(t.height-i*s)/2-e.y*s}}function Ne(e,t,n){return{z:e.z,tx:e.tx+t,ty:e.ty+n}}function Pe(e){return`translate(${e.tx}px, ${e.ty}px) scale(${e.z})`}function Fe(e){if(e===null||e===``)return null;let t;try{t=JSON.parse(e)}catch{return null}if(typeof t!=`object`||!t)return null;let{z:n,tx:r,ty:i}=t;return typeof n!=`number`||typeof r!=`number`||typeof i!=`number`||!Number.isFinite(n)||!Number.isFinite(r)||!Number.isFinite(i)||n<=0?null:{z:Q(n,Oe),tx:r,ty:i}}var Ie=3,Le=.0015;function Re({doc:e,onChange:t,onLevelStep:n}){let r=e.querySelector(`.ptr-canvas`);if(r===null)return null;let i=r;i.style.transformOrigin=`0 0`;let a={z:1,tx:0,ty:0},o=!1,s=!1,c=!1,l=null,u=()=>({width:e.documentElement.clientWidth||1,height:e.documentElement.clientHeight||1});function d(){i.style.transform=Pe(a),t?.(a)}function f(e){a=e,d()}function p(){i.style.transition=``}function m(e){i.style.transition=`transform 180ms ease-out`,f(e),setTimeout(p,220)}function h(t){e.documentElement.style.cursor=t}function g(e){if(e.preventDefault(),e.altKey&&n!==void 0){let t=ke(e.deltaX,e.deltaY);t!==null&&n(t,{x:e.clientX,y:e.clientY});return}p();let t=1;e.deltaMode===1?t=16:e.deltaMode===2&&(t=u().height);let r=a.z<.1?Oe:Z;f(Ae(a,Math.exp(-e.deltaY*t*Le),e.clientX,e.clientY,r))}function _(t){return t.button===1?!0:t.button===0?o?!0:t.target===i||t.target===e.body||t.target===e.documentElement:!1}function v(e){_(e)&&(e.preventDefault(),l={lastX:e.clientX,lastY:e.clientY,startX:e.clientX,startY:e.clientY},s=!1)}function y(t){if(!l)return;if(!s){if(Math.hypot(t.clientX-l.startX,t.clientY-l.startY)<Ie)return;s=!0,p(),e.body.style.userSelect=`none`,h(`grabbing`)}let n=t.clientX-l.lastX,r=t.clientY-l.lastY;l.lastX=t.clientX,l.lastY=t.clientY,f(Ne(a,n,r))}function b(){l&&(l=null,s&&(s=!1,c=!0,e.body.style.userSelect=``,h(o?`grab`:``)))}function x(e){o!==e&&(o=e,s||h(e?`grab`:``))}function S(e){if(e.key===` `&&!e.metaKey&&!e.ctrlKey&&!e.altKey){let t=e.target,n=t?.tagName??``;if(n===`INPUT`||n===`TEXTAREA`||t?.isContentEditable===!0)return;x(!0),e.preventDefault()}else ee(e)&&e.preventDefault()}function C(e){e.key===` `&&x(!1)}function w(){x(!1),b()}function ee(e){return(e.ctrlKey||e.metaKey)&&(e.key===`0`||e.key===`)`||e.code===`Digit0`||e.code===`Numpad0`)?(f(je(a,1,u())),!0):e.shiftKey&&!e.ctrlKey&&!e.metaKey&&(e.key===`!`||e.code===`Digit1`)?(T(),!0):!1}function T(){f(Me({x:0,y:0,w:i.offsetWidth,h:i.offsetHeight},u()))}function te(e){let t=u();m({z:a.z,tx:t.width/2-(e.x+e.w/2)*a.z,ty:t.height/2-(e.y+e.h/2)*a.z})}return e.addEventListener(`wheel`,g,{passive:!1}),e.addEventListener(`mousedown`,v,!0),e.addEventListener(`mousemove`,y,!0),e.addEventListener(`mouseup`,b,!0),e.addEventListener(`keydown`,S),e.addEventListener(`keyup`,C),e.defaultView?.addEventListener(`blur`,w),{get:()=>a,set:f,fitAll:T,centerOn:te,focusBox:e=>m(Me(e,u())),zoomToPercent:e=>f(je(a,e,u())),consumeDragClick:()=>{let e=c;return c=!1,e},handleKey:ee,setSpace:x,destroy:()=>{e.removeEventListener(`wheel`,g),e.removeEventListener(`mousedown`,v,!0),e.removeEventListener(`mousemove`,y,!0),e.removeEventListener(`mouseup`,b,!0),e.removeEventListener(`keydown`,S),e.removeEventListener(`keyup`,C),e.defaultView?.removeEventListener(`blur`,w)}}}var ze=W(`<span class=badge>v`),Be=W(`<ul class=list>`),Ve=W(`<section class="section files"><h2>设计稿 <span class=hint>depth `),He=W(`<section class="section marks"><h2>标记<button class="ghost tiny">清空</button></h2><ul class=list>`),Ue=W(`<span class=hint>pid `),We=W(`<div class=error>`),Ge=W(`<code>/`),Ke=W(`<div class=toolbar><div class=crumbs></div><div class=tools><span class="hint zoom">%</span><button class="ghost tiny"title=适应窗口(Shift+1)>适应</button><button class="ghost tiny"title=100%(Ctrl/Cmd+0)>1:1</button><button class=ghost>重新渲染`),qe=W(`<div class=boardbar><button class=board>全部`),Je=W(`<iframe class=preview title="design preview"sandbox="allow-same-origin allow-scripts">`),Ye=W(`<div class=app><aside class=sidebar><header class=sidebar-head><img class=brand-mark src=/logo.svg alt><h1>paintress</h1></header><section class=section><h2>项目</h2></section><footer class=sidebar-foot><label class=toggle><input type=checkbox>实时刷新</label><button class=ghost>刷新列表</button></footer></aside><main class=stage>`),Xe=W(`<p class=empty>还没有注册项目<br><code>paintress watch &lt;name&gt; &lt;dir&gt;`),Ze=W(`<li><button class=item><span class=item-name></span><span class=item-meta> 个设计稿`),Qe=W(`<p class=empty>没有 .ptr / .pen 文件`),$e=W(`<li><button class=item><span class=item-name>`),et=W(`<li><button class=item><span class=item-name><b class=mark-no>#</b> </span><span class=item-meta>`),tt=W(`<p>从左侧选择一个设计稿`),nt=W(`<div class=placeholder>`),rt=W(`<p>从左侧选择项目`),it=W(`<span class=sep>/`),at=W(`<button class=crumb>`),ot=W(`<button class=board><span class=board-size>×`);function $(e){return typeof CSS<`u`&&typeof CSS.escape==`function`?CSS.escape(e):e.replace(/["\\]/g,`\\$&`)}function st(){let[e,t]=y([]),[n,r]=y(null),[i,a]=y([]),[o,s]=y(null),[c,l]=y([]),[u,d]=y(null),[f,p]=y([]),[m,h]=y(null),[g,_]=y({}),[v,S]=y(0),[C,te]=y(!0),[E,ne]=y(``),[D,re]=y([]),[O,k]=y(null),[ie,A]=y(1),j,M=null,ae;function N(){let e=new URLSearchParams,t=n(),r=o();t&&e.set(`project`,t),r&&e.set(`file`,r);let i=e.toString();history.replaceState(null,``,i?`?${i}`:location.pathname)}async function oe(){try{let e=await be();t(e.projects),_({pid:e.pid,version:e.version}),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}}async function P(e){try{l((await we(e)).marks)}catch{l([])}}async function F(e){r(e),s(null),d(null),p([]);try{let t=await xe(e);a(t.files),h(null)}catch(e){a([]),h(e instanceof Error?e.message:String(e))}P(e),N()}function I(e){s(e),d(null),p([]),N()}ee(()=>{let e=e=>{let t=e.target;return t?.tagName===`INPUT`||t?.tagName===`TEXTAREA`||t?.isContentEditable===!0},t=t=>{e(t)||(t.key===` `&&!t.ctrlKey&&!t.metaKey&&!t.altKey?(M?.setSpace(!0),t.preventDefault()):M?.handleKey(t)===!0&&t.preventDefault())},n=e=>{e.key===` `&&M?.setSpace(!1)};window.addEventListener(`keydown`,t),window.addEventListener(`keyup`,n),T(()=>{window.removeEventListener(`keydown`,t),window.removeEventListener(`keyup`,n)});let r=new URLSearchParams(location.search);(async()=>{await oe();let e=r.get(`project`);if(e){await F(e);let t=r.get(`file`);t&&I(t)}})()});let L=()=>j?.contentDocument??null;function se(){let e=L();if(!e||e.getElementById(`ptr-style`))return;let t=e.createElement(`style`);t.id=`ptr-style`,t.textContent=`[data-ptr-selected]{outline:2px solid #908030 !important;outline-offset:-2px;}
2
- [data-ptr-hover]:not([data-ptr-selected]){outline:1px dashed rgba(144,128,48,.55) !important;outline-offset:-2px;}
3
- [data-ptr-changed]{box-shadow:inset 0 0 0 2px #908030 !important;animation:ptr-pulse 1.4s ease-in-out 3;}
4
- @keyframes ptr-pulse{0%,100%{box-shadow:inset 0 0 0 2px #908030}50%{box-shadow:inset 0 0 0 2px rgba(144,128,48,.35)}}
5
- html{cursor:default;}
6
- html,body{overflow:hidden;}`,e.head.appendChild(t)}function R(){let e=L();if(!e)return;for(let t of e.querySelectorAll(`[data-ptr-selected]`))t.removeAttribute(`data-ptr-selected`);let t=u();t&&e.querySelector(`[data-node-id="${$(t)}"]`)?.setAttribute(`data-ptr-selected`,``)}function z(e){return e.parentElement?.closest(`[data-node-id]`)??null}function ce(e){return e.querySelector(`:scope > [data-node-id]`)}function B(e){if(M?.consumeDragClick()===!0){e.preventDefault(),e.stopPropagation();return}let t=e.target?.closest(`[data-node-id]`);t&&(e.preventDefault(),e.stopPropagation(),q(e.altKey?z(t)??t:t))}function le(e,t){let n=L();if(!n)return;let r=u(),i=r===null?null:n.querySelector(`[data-node-id="${$(r)}"]`);if(i===null){let e=(n.elementFromPoint(t.x,t.y)?.closest(`[data-node-id]`))?.dataset.nodeId;e!==void 0&&J(e);return}let a=(e===1?z(i):ce(i))?.dataset.nodeId;a!==void 0&&J(a)}function ue(e){let t=L();if(t){for(let e of t.querySelectorAll(`[data-ptr-hover]`))e.removeAttribute(`data-ptr-hover`);(e.target?.closest(`[data-node-id]`))?.setAttribute(`data-ptr-hover`,``)}}async function fe(){let e=n(),t=o();if(e&&t)try{let n=await De(e,t);if(!n.ok||n.ids.length===0)return;let r=L();if(!r)return;for(let e of n.ids)r.querySelector(`[data-node-id="${$(e)}"]`)?.setAttribute(`data-ptr-changed`,``)}catch{}}function pe(){let e=L();return e?Array.from(e.querySelectorAll(`[data-board]`)).map(e=>{let t=e.dataset;return{id:t.nodeId??``,...t.boardName===void 0?{}:{name:t.boardName},x:Number(t.boardX??0),y:Number(t.boardY??0),w:Number(t.boardW??0),h:Number(t.boardH??0)}}):[]}function me(){let e=n(),t=o();return e&&t?`paintress:viewport:${e}/${t}`:null}function W(e){let t=me();t!==null&&(clearTimeout(ae),ae=setTimeout(()=>{try{localStorage.setItem(t,JSON.stringify(e))}catch{}},300))}function he(){let e=L();e&&(se(),M?.destroy(),M=Re({doc:e,onChange:e=>{A(e.z),W(e)},onLevelStep:le}),re(pe()),k(null),ve(),e.addEventListener(`click`,B,!0),e.addEventListener(`mousemove`,ue,!0),R(),fe())}function ve(){if(!M)return;let e=me(),t=e===null?null:localStorage.getItem(e),n=t===null?null:Fe(t);n===null?M.fitAll():M.set(n)}function ye(e){if(k(e),!M)return;if(e===null){M.fitAll();return}let t=D().find(t=>t.id===e);t&&M.focusBox({x:t.x,y:t.y,w:t.w,h:t.h})}function K(e){let t=[],n=e;for(;n;){let e=n.closest(`[data-node-id]`);if(!e)break;let r=e;t.unshift({id:r.dataset.nodeId??``,type:r.dataset.nodeType??`?`,name:r.dataset.nodeName}),n=e.parentElement}return t}async function q(e){let t=e.dataset.nodeId??``;if(t===``)return;let r=K(e);d(t),p(r),R();let i=n();if(i)try{await Te({project:i,nodeId:t,...e.dataset.nodeName===void 0?{}:{name:e.dataset.nodeName},...e.dataset.nodeType===void 0?{}:{type:e.dataset.nodeType},path:r.map(e=>e.name??e.type).join(` / `)}),l((await we(i)).marks),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}}function J(e){d(e),R();let t=L(),n=t?.querySelector(`[data-node-id="${$(e)}"]`);if(!n||!t){p([]);return}p(K(n));let r=t.querySelector(`.ptr-canvas`);if(!r||!M){n.scrollIntoView({block:`center`});return}let i=n.getBoundingClientRect(),a=r.getBoundingClientRect(),o=M.get().z;M.centerOn({x:(i.left-a.left)/o,y:(i.top-a.top)/o,w:i.width/o,h:i.height/o})}async function Y(){let e=n();if(e)try{await Ee(e),l([]),d(null),p([]),R()}catch(e){h(e instanceof Error?e.message:String(e))}}x(w([n,o,v],()=>{let e=n(),t=o();ne(e&&t?`${Se(e,t)}&r=${v()}`:``)}));let X=null;return x(w([n,o,C],()=>{X?.close(),X=null;let e=n(),t=o();C()&&e&&t&&(X=new EventSource(Ce(e,t)),X.addEventListener(`change`,()=>S(e=>e+1)))})),T(()=>X?.close()),(()=>{var t=Ye(),r=t.firstChild,a=r.firstChild;a.firstChild.nextSibling;var s=a.nextSibling;s.firstChild;var l=s.nextSibling,d=l.firstChild,p=d.firstChild,h=d.nextSibling,_=r.nextSibling;return G(a,V(U,{get when(){return g().version},get children(){var e=ze();return e.firstChild,G(e,()=>g().version,null),e}}),null),G(s,V(U,{get when(){return e().length>0},get fallback(){return Xe()},get children(){var t=Be();return G(t,V(H,{get each(){return e()},children:e=>(()=>{var t=Ze(),r=t.firstChild,i=r.firstChild,a=i.nextSibling,o=a.firstChild;return r.$$click=()=>void F(e.name),G(i,()=>e.name),G(a,()=>e.fileCount,o),b(()=>r.classList.toggle(`active`,n()===e.name)),t})()})),t}}),null),G(r,V(U,{get when(){return n()},get children(){var t=Ve(),r=t.firstChild.firstChild.nextSibling;return r.firstChild,G(r,()=>e().find(e=>e.name===n())?.depth??`-`,null),G(t,V(U,{get when(){return i().length>0},get fallback(){return Qe()},get children(){var e=Be();return G(e,V(H,{get each(){return i()},children:e=>(()=>{var t=$e(),n=t.firstChild,r=n.firstChild;return n.$$click=()=>I(e),G(r,e),b(()=>n.classList.toggle(`active`,o()===e)),t})()})),e}}),null),t}}),l),G(r,V(U,{get when(){return c().length>0},get children(){var e=He(),t=e.firstChild,n=t.firstChild.nextSibling,r=t.nextSibling;return n.$$click=()=>void Y(),G(r,V(H,{get each(){return c()},children:e=>(()=>{var t=et(),n=t.firstChild,r=n.firstChild,i=r.firstChild;i.firstChild,i.nextSibling;var a=r.nextSibling;return n.$$click=()=>J(e.nodeId),G(i,()=>e.shortId,null),G(r,()=>e.name??e.nodeId,null),G(a,()=>e.path),b(()=>n.classList.toggle(`active`,u()===e.nodeId)),t})()})),e}}),l),p.addEventListener(`change`,e=>te(e.currentTarget.checked)),h.$$click=()=>void oe(),G(l,V(U,{get when(){return g().pid},get children(){var e=Ue();return e.firstChild,G(e,()=>g().pid,null),e}}),null),G(_,V(U,{get when(){return m()},get children(){var e=We();return G(e,m),e}}),null),G(_,V(U,{get when(){return de(()=>n()!==null)()&&o()!==null},get fallback(){return(()=>{var e=nt();return G(e,V(U,{get when(){return n()},get fallback(){return rt()},get children(){return tt()}})),e})()},get children(){return[(()=>{var e=Ke(),t=e.firstChild,r=t.nextSibling.firstChild,i=r.firstChild,a=r.nextSibling,s=a.nextSibling,c=s.nextSibling;return G(t,V(U,{get when(){return f().length===0},get fallback(){return V(H,{get each(){return f()},children:(e,t)=>[V(U,{get when(){return t()>0},get children(){return it()}}),(()=>{var t=at();return t.$$click=()=>J(e.id),G(t,()=>e.name??e.type),b(()=>t.classList.toggle(`active`,u()===e.id)),t})()]})},get children(){var e=Ge(),t=e.firstChild;return G(e,n,t),G(e,o,null),e}})),G(r,()=>Math.round(ie()*100),i),a.$$click=()=>M?.fitAll(),s.$$click=()=>M?.zoomToPercent(1),c.$$click=()=>S(e=>e+1),e})(),V(U,{get when(){return D().length>1},get children(){var e=qe(),t=e.firstChild;return t.$$click=()=>ye(null),G(e,V(H,{get each(){return D()},children:e=>(()=>{var t=ot(),n=t.firstChild,r=n.firstChild;return t.$$click=()=>ye(e.id),G(t,()=>e.name??`#${e.id}`,n),G(n,()=>Math.round(e.w),r),G(n,()=>Math.round(e.h),null),b(()=>t.classList.toggle(`active`,O()===e.id)),t})()}),null),b(()=>t.classList.toggle(`active`,O()===null)),e}}),(()=>{var e=Je();return e.addEventListener(`load`,he),_e(e=>{j=e},e),b(()=>ge(e,`src`,E())),e})()]}}),null),b(()=>p.checked=C()),t})()}he([`click`]);var ct=document.getElementById(`root`);if(!ct)throw Error(`缺少 #root 容器`);me(()=>V(st,{}),ct);
@@ -1 +0,0 @@
1
- :root{--bg:#f6f1ea;--panel:#fffcf8;--border:#e6ded2;--text:#1f2a37;--muted:#7a7166;--accent:#205080;--accent-soft:#20508014;--gold:#908030;--gold-soft:#90803073;--radius:8px}*{box-sizing:border-box;margin:0;padding:0}html,body,#root{height:100%}body{color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased;font:14px/1.55 system-ui,-apple-system,Segoe UI,Noto Sans SC,sans-serif}.app{grid-template-columns:280px 1fr;height:100%;display:grid}.sidebar{background:var(--panel);border-right:1px solid var(--border);flex-direction:column;min-height:0;display:flex}.sidebar-head{border-bottom:1px solid var(--border);align-items:center;gap:8px;padding:16px 16px 12px;display:flex}.sidebar-head h1{letter-spacing:-.01em;font-size:15px;font-weight:650}.badge{color:var(--muted);background:var(--bg);border-radius:999px;padding:1px 6px;font-size:11px}.section{min-height:0;padding:12px 8px 4px;overflow-y:auto}.section.files{border-top:1px solid var(--border);flex:1;padding-top:12px}.section h2{text-transform:uppercase;letter-spacing:.06em;color:var(--muted);justify-content:space-between;gap:8px;padding:0 8px 8px;font-size:11px;font-weight:600;display:flex}.hint{color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px;font-weight:400}.list{list-style:none}.item{text-align:left;cursor:pointer;width:100%;font:inherit;color:inherit;background:0 0;border:0;border-radius:6px;flex-direction:column;gap:1px;padding:7px 8px;display:flex}.item:hover{background:var(--bg)}.item.active{background:var(--accent-soft);color:var(--accent)}.item-name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.item-meta{color:var(--muted);font-size:11px}.item.active .item-meta{color:var(--accent);opacity:.75}.empty{color:var(--muted);padding:4px 8px 12px;font-size:12px;line-height:1.7}.empty code{background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:1px 5px;font-size:11px}.sidebar-foot{border-top:1px solid var(--border);color:var(--muted);flex-wrap:wrap;align-items:center;gap:8px;padding:10px 16px;font-size:12px;display:flex}.toggle{cursor:pointer;align-items:center;gap:5px;display:inline-flex}.ghost{background:var(--bg);border:1px solid var(--border);font:inherit;color:var(--text);cursor:pointer;border-radius:6px;padding:3px 9px;font-size:12px}.ghost:hover{border-color:var(--accent);color:var(--accent)}.stage{background:var(--bg);flex-direction:column;min-width:0;min-height:0;display:flex}.toolbar{border-bottom:1px solid var(--border);background:var(--panel);justify-content:space-between;align-items:center;gap:12px;padding:8px 14px;display:flex}.toolbar code{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.preview{border:1px solid var(--border);background:#fff;border-radius:10px;flex:1;width:calc(100% - 24px);min-height:0;margin:12px;box-shadow:0 1px 2px #1f2a370d,0 8px 24px #1f2a370d}.placeholder{color:var(--muted);flex:1;place-items:center;display:grid}.error{border-radius:var(--radius);color:#b91c1c;white-space:pre-wrap;background:#fef2f2;border:1px solid #fecaca;margin:12px;padding:10px 12px;font-size:12px}.section.marks{border-top:1px solid var(--border);flex:0 auto;max-height:34vh}.mark-no{background:var(--accent-soft);min-width:20px;color:var(--accent);font-variant-numeric:tabular-nums;border-radius:4px;margin-right:4px;padding:0 4px;font-size:11px;display:inline-block}.item.active .mark-no{background:#fff}.ghost.tiny{text-transform:none;letter-spacing:0;padding:1px 6px;font-size:11px}.crumbs{flex-wrap:wrap;align-items:center;gap:3px;min-width:0;display:flex;overflow:hidden}.crumbs code{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.crumb{font:inherit;color:var(--muted);cursor:pointer;text-overflow:ellipsis;white-space:nowrap;background:0 0;border:0;border-radius:4px;max-width:220px;padding:2px 5px;font-size:12px;overflow:hidden}.crumb:hover{background:var(--bg);color:var(--text)}.crumb.active{color:var(--accent);background:var(--accent-soft)}.sep{color:var(--border);font-size:11px}.brand-mark{flex:none;width:22px;height:22px}.tools{flex:none;align-items:center;gap:6px;display:flex}.zoom{font-variant-numeric:tabular-nums;text-align:right;min-width:40px}.boardbar{border-bottom:1px solid var(--border);background:var(--panel);align-items:center;gap:6px;padding:6px 14px;display:flex;overflow-x:auto}.board{background:var(--bg);border:1px solid var(--border);font:inherit;color:var(--text);cursor:pointer;border-radius:6px;flex:none;align-items:baseline;gap:6px;padding:3px 9px;font-size:12px;display:inline-flex}.board:hover{border-color:var(--accent);color:var(--accent)}.board.active{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}.board-size{color:var(--muted);font-variant-numeric:tabular-nums;font-size:10px}.board.active .board-size{color:var(--accent);opacity:.75}