paintress 0.1.1 → 0.2.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
@@ -1,127 +1,203 @@
1
- # Paintress
1
+ <div align="center">
2
+ <img src="assets/icon-512-wordmark.webp" width="200" alt="paintress">
3
+ <p>Design-document operation layer for AI agents</p>
4
+ <p><a href="README.zh-CN.md">中文</a></p>
5
+ </div>
2
6
 
3
- 面向 AI agent 的设计稿操作层。用 MCP 工具读写结构化的 `.pen` 设计稿,渲染为 HTML,在浏览器里查看与标记。
7
+ **To install: paste this repository's URL into your agent and ask it to install paintress.** Agents that need a precise procedure will find one at the [end of this file](#installation-guide-for-ai-agents).
4
8
 
5
- 早期开发中,尚未发布。
9
+ ## What it is
6
10
 
7
- ## 能力
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.
8
12
 
9
- - **读写**:查询、修改、插入、删除设计稿节点,保持文件格式不变
10
- - **渲染**:设计稿 → HTML/CSS,由浏览器排版,输出可直接使用的页面
11
- - **查看**:本地服务,文件变更自动刷新
12
- - **标记**:点击节点生成短号,便于在对话中指代
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.
13
14
 
14
- ## 安装
15
+ | Part | Purpose |
16
+ | --- | --- |
17
+ | MCP tools | query, read, update, insert, delete, move, batch, validate, render, screenshot |
18
+ | CLI | the same operations plus the viewer process, for use without an agent |
19
+ | Viewer | local web server: renders a document, highlights changes, hands back clicked nodes as `#1`, `#2`, … |
20
+ | Screenshot | PNG of a whole canvas, for agents that can look at images |
15
21
 
16
- 要求 Node.js ≥ 22。
22
+ ## Requirements
23
+
24
+ Node.js 22 or newer.
25
+
26
+ ## Install
17
27
 
18
28
  ```bash
19
- npm install -g paintress # 全局安装,命令名为 paintress
20
- npx paintress --help # 或临时运行
29
+ npm install -g paintress
21
30
  ```
22
31
 
23
- > 包名待最终确定:若发布在 `@aliciai` 作用域下,把下文命令里的 `paintress` 换成 `@aliciai/paintress`,CLI 命令名不变。
32
+ Then register it with the agent(s) you use:
24
33
 
25
- ## 接入 MCP 客户端
34
+ ```bash
35
+ paintress doctor # what is installed and which clients already have the entry
36
+ paintress install # write the MCP entry into every detected client config
37
+ paintress install pi cursor # or only the ones you name
38
+ ```
26
39
 
27
- 服务端就是一条 stdio 命令:`paintress mcp`。各客户端的差异只在配置文件位置与顶层字段名。
40
+ `install` merges a single entry, preserves everything else in the file, and writes a `*.bak-<timestamp>` backup before touching it. Running it twice changes nothing.
41
+
42
+ ## CLI
43
+
44
+ ```text
45
+ paintress mcp <file>? start the stdio MCP server
46
+ paintress doc_info <file> document summary
47
+ paintress query <file> [--type t] [--name n|--regex] find nodes
48
+ paintress get <file> <id> [--depth n] read a node subtree
49
+ paintress update <file> <id> '<patch-json>' shallow-merge attributes
50
+ paintress insert <file> <parent> '<node-json>' insert a child node
51
+ paintress delete <file> <id> remove a node
52
+ paintress move <file> <id> <parent> [--index n] reparent a node
53
+ paintress batch <file> '<ops-json>' several operations at once
54
+ paintress validate <file> check against the format spec
55
+ paintress render <file> <out.html> write the canvas as HTML
56
+ paintress shot <file> <out.png> [--scale n] render to PNG
57
+
58
+ paintress web [--port n] [--idle ms] [--daemon] start the viewer
59
+ paintress ping | status | stop viewer control
60
+ paintress watch <name> <dir> | unwatch <name> register a directory to browse
61
+ paintress attach <name> keep the viewer alive while an agent works
62
+
63
+ paintress install [client...] [--dry-run] write MCP config entries
64
+ paintress doctor show client configuration state
65
+ paintress mcp-config <client> print the config snippet for one client
66
+ ```
28
67
 
29
- ### 自动写入(推荐)
68
+ ## Viewer
30
69
 
31
70
  ```bash
32
- paintress doctor # 先看各客户端是否已安装、配置是否已就位
33
- paintress install --dry-run # 预览将要写入的内容,不动磁盘
34
- paintress install # 写入所有已检测到的客户端
35
- paintress install pi cursor # 只写指定客户端
71
+ paintress web --daemon --idle 3600000
72
+ paintress watch demo ./demo
73
+ # open http://localhost:3316/?project=demo&file=hello.ptr
36
74
  ```
37
75
 
38
- 写入规则:只合并 `mcpServers.paintress`(VS Code 为 `servers.paintress`)这一条,其他键与他人条目原样保留;改动前会生成 `*.bak-<时间戳>` 备份;已有的 Codex TOML 只追加不重写。
76
+ 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`.
39
77
 
40
- 单个客户端也可以只要片段:
78
+ ## MCP clients
79
+
80
+ `paintress install` writes these; the same entries can be added by hand.
81
+
82
+ | Client | Config file | Top-level key |
83
+ | --- | --- | --- |
84
+ | Claude Desktop | `~/.config/Claude/claude_desktop_config.json` | `mcpServers` |
85
+ | Cursor | `~/.cursor/mcp.json` | `mcpServers` |
86
+ | VS Code | `~/.config/Code/User/mcp.json` | `servers` (with `"type": "stdio"`) |
87
+ | Codex | `~/.codex/config.toml` | `[mcp_servers.paintress]` |
88
+ | pi | `~/.pi/agent/mcp.json` | `mcpServers` |
41
89
 
42
90
  ```bash
43
- paintress mcp-config pi # 输出 JSON 片段
44
- paintress mcp-config codex # 输出 TOML 片段
91
+ paintress mcp-config cursor # print the exact snippet instead of editing files
45
92
  ```
46
93
 
47
- 下面是手工配置时的完整写法。
94
+ ## Skill
48
95
 
49
- ### Claude Desktop
96
+ `skills/paintress/SKILL.md` is a short instruction file for agents: when to reach for these tools, how `#1` markers map to node ids, and which tool to use for what. Copy it into your agent's skill directory if it supports one.
50
97
 
51
- `claude_desktop_config.json`:
98
+ ## Documentation
52
99
 
53
- ```json
54
- {
55
- "mcpServers": {
56
- "paintress": { "command": "npx", "args": ["-y", "paintress", "mcp"] }
57
- }
58
- }
59
- ```
100
+ - `specs/PLAN.md` — roadmap and open questions
101
+ - `format/spec.yaml` — the document format spec (the `.pen` profile is frozen; `.ptr` evolves)
102
+ - `.agents/memory/ARCHITECTURE.md` — design decisions and their rationale
103
+ - `.agents/implementations/` — per-change records with measurements
60
104
 
61
- ### Cursor
105
+ ## License
62
106
 
63
- `~/.cursor/mcp.json`(全局)或 `.cursor/mcp.json`(项目,可提交):
107
+ MIT
64
108
 
65
- ```json
66
- {
67
- "mcpServers": {
68
- "paintress": { "command": "npx", "args": ["-y", "paintress", "mcp"] }
69
- }
70
- }
71
- ```
109
+ ---
110
+
111
+ ## Installation guide for AI agents
112
+
113
+ You are an agent asked to install paintress on this machine. Work through the steps in order and report what you found, what you reused, and what you changed.
114
+
115
+ **Detection comes first.** Several agents can share one machine. A previous agent may already have installed paintress globally and registered it for itself. Installing a second copy is a bug, not thoroughness.
72
116
 
73
- ### VS Code
117
+ ### 0. Detect
74
118
 
75
- `.vscode/mcp.json`(顶层键是 `servers`,不是 `mcpServers`):
119
+ | Check | Command | What it tells you |
120
+ | --- | --- | --- |
121
+ | Binary on PATH | `command -v paintress` | a usable install already exists |
122
+ | Global npm package | `npm ls -g --depth=0 paintress` | installed globally via npm |
123
+ | Project-local | `npm ls --depth=0 paintress` | installed inside the current project |
124
+ | Client configs | `paintress doctor` | which agents already have an entry, and its state |
76
125
 
77
- ```json
78
- {
79
- "servers": {
80
- "paintress": { "type": "stdio", "command": "npx", "args": ["-y", "paintress", "mcp"] }
81
- }
82
- }
126
+ Rules:
127
+
128
+ - **Any hit means reuse.** Do not install again; go to step 2.
129
+ - Report the resolved path and version (`paintress ping` prints the daemon version when the viewer runs; `npm ls -g --depth=0 paintress` prints the package version).
130
+ - Only when every check is empty do you install (step 1).
131
+
132
+ ### 1. Install (only if step 0 found nothing)
133
+
134
+ ```bash
135
+ npm install -g paintress
83
136
  ```
84
137
 
85
- ### Codex
138
+ If global installs are not permitted in this environment:
86
139
 
87
- `~/.codex/config.toml`:
140
+ ```bash
141
+ npm install paintress # project-local; call it through npx paintress ...
142
+ npx -y paintress@latest ... # no install at all; slower on first run
143
+ ```
144
+
145
+ Prefer the global install. It is what makes the entry in every client config reusable by the next agent.
146
+
147
+ ### 2. Register the MCP server for the agent you are running in
88
148
 
89
- ```toml
90
- [mcp_servers.paintress]
91
- command = "npx"
92
- args = ["-y", "paintress", "mcp"]
149
+ ```bash
150
+ paintress doctor # list detected clients and their state
151
+ paintress install --dry-run # preview the writes
152
+ paintress install <client-id> # write only the client you are running in
93
153
  ```
94
154
 
95
- ### pi
155
+ Client ids: `claude`, `cursor`, `vscode`, `codex`, `pi`. Use `paintress install` without arguments to cover every detected client.
156
+
157
+ What the write does:
158
+
159
+ - adds or refreshes one entry named `paintress` (`mcpServers.paintress`, or `servers.paintress` for VS Code, or `[mcp_servers.paintress]` for Codex);
160
+ - leaves every other key, server and comment structure in the file alone;
161
+ - writes `*.bak-<timestamp>` next to the file before replacing it;
162
+ - is idempotent: an identical entry reports `noop` and nothing is written;
163
+ - uses the bare command `paintress` when it resolves on PATH, otherwise the absolute path of the binary.
164
+
165
+ If the agent you run in is not in that list, print the closest snippet and adapt it:
96
166
 
97
- `~/.pi/agent/mcp.json`(用户级)或 `.pi/mcp.json`(项目级):
167
+ ```bash
168
+ paintress mcp-config cursor # JSON with mcpServers
169
+ paintress mcp-config vscode # JSON with servers + type: stdio
170
+ paintress mcp-config codex # TOML table
171
+ ```
98
172
 
99
- ```json
100
- {
101
- "mcpServers": {
102
- "paintress": { "command": "paintress", "args": ["mcp"] }
103
- }
104
- }
173
+ ### 3. Install the skill (recommended)
174
+
175
+ ```bash
176
+ cp "$(npm root -g)/paintress/skills/paintress/SKILL.md" <your-skill-dir>/paintress/SKILL.md
105
177
  ```
106
178
 
107
- 若 paintress 装在 `~/.pi/agent/npm` 里(该目录的 `.bin` 不在 PATH),`command` 写成 `~/.pi/agent/npm/node_modules/.bin/paintress`。
179
+ Use the skill directory your harness actually reads (`~/.pi/agent/skills/`, `~/.claude/skills/`, `.cursor/skills/`, …). If your harness has no skill mechanism, load the file into context once and keep the tool list.
108
180
 
109
- ## 查看设计稿
181
+ ### 4. Verify
110
182
 
111
183
  ```bash
112
- paintress web --idle 3600000 # 起本地查看器(默认 http://localhost:3316)
113
- paintress watch demo ./demo # 注册一个项目目录
184
+ paintress doctor # entry present, command resolvable
185
+ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}\n' | paintress mcp
114
186
  ```
115
187
 
116
- 浏览器打开 `http://localhost:3316/?project=demo&file=hello.ptr`:滚轮以光标为锚点缩放,空格 / 中键 / 空白处拖拽平移,`Ctrl/Cmd+0` 回 100%,`Shift+1` 适应窗口;多画板文件会显示画板条。
188
+ 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.
117
189
 
118
- ## 原则
190
+ ### 5. Multi-agent rules
119
191
 
120
- - 只处理明文 JSON 设计稿,不依赖任何商业设计工具的 SDK、账号或托管服务
121
- - 渲染输出标准 HTML,不使用私有格式
122
- - 不含编辑器 UI:支持查看与标记,不支持拖拽改布局
123
- - 写入前备份、原子替换,避免数据损坏
192
+ - One global install serves every agent on the machine. Do not create per-agent copies.
193
+ - Each agent adds **its own** config entry. Never rewrite another agent's file, and never remove entries you did not add.
194
+ - If `doctor` reports `noop`, another agent already registered it correctly — stop there and say so.
195
+ - Report the outcome as: reused / installed / registered (which client) / skipped (why).
124
196
 
125
- ## 许可
197
+ ### 6. Uninstall
126
198
 
127
- MIT
199
+ ```bash
200
+ npm uninstall -g paintress
201
+ ```
202
+
203
+ Then delete the `paintress` entry from each client config, or restore the newest `*.bak-<timestamp>` next to it.
@@ -0,0 +1,203 @@
1
+ <div align="center">
2
+ <img src="assets/icon-512-wordmark.webp" width="200" alt="paintress">
3
+ <p>面向 AI agent 的设计稿操作层</p>
4
+ <p><a href="README.md">English</a></p>
5
+ </div>
6
+
7
+ **安装方式:把本仓库地址贴给您的 agent,让它装 paintress。** 需要精确流程的 agent 见[文末的安装手册](#给-ai-agent-的安装手册)。
8
+
9
+ ## 这是什么
10
+
11
+ paintress 读写结构化的设计稿——带公开 schema 的 JSON 文件(`.pen`,以及它自己写出的 `.ptr`)。它把设计稿渲染成 HTML,起一个本地查看器,并把同一套操作同时暴露为 MCP 工具和 CLI。
12
+
13
+ 它不是编辑器:没有画布、没有拖拽、没有属性面板。人在浏览器里看设计稿并点击节点指认目标,agent 负责读写。
14
+
15
+ | 组成 | 作用 |
16
+ | --- | --- |
17
+ | MCP 工具 | 查询、读取、更新、插入、删除、移动、批量、校验、渲染、截图 |
18
+ | CLI | 同样的操作,外加查看器进程;不接 agent 也能用 |
19
+ | 查看器 | 本地 web 服务:渲染设计稿、高亮变更、把点击的节点变成 `#1`、`#2`… |
20
+ | 截图 | 整张画布的 PNG,给能看图的 agent 用 |
21
+
22
+ ## 环境要求
23
+
24
+ Node.js 22 及以上。
25
+
26
+ ## 安装
27
+
28
+ ```bash
29
+ npm install -g paintress
30
+ ```
31
+
32
+ 然后把 MCP 条目写进您使用的客户端:
33
+
34
+ ```bash
35
+ paintress doctor # 查看已装情况、哪些客户端已有条目
36
+ paintress install # 写入所有检测到的客户端配置
37
+ paintress install pi cursor # 或只写指定的客户端
38
+ ```
39
+
40
+ `install` 只合并 `paintress` 一条,文件里其他内容保持原样,改动前写 `*.bak-<时间戳>` 备份;重复执行不会改变任何东西。
41
+
42
+ ## CLI
43
+
44
+ ```text
45
+ paintress mcp <file>? 启动 stdio MCP server
46
+ paintress doc_info <file> 文档摘要
47
+ paintress query <file> [--type t] [--name n|--regex] 条件查询节点
48
+ paintress get <file> <id> [--depth n] 读取节点子树
49
+ paintress update <file> <id> '<patch-json>' 浅合并更新属性
50
+ paintress insert <file> <parent> '<node-json>' 插入子节点
51
+ paintress delete <file> <id> 删除节点
52
+ paintress move <file> <id> <parent> [--index n] 移动节点
53
+ paintress batch <file> '<ops-json>' 一次执行多个操作
54
+ paintress validate <file> 按格式规格校验
55
+ paintress render <file> <out.html> 渲染为 HTML
56
+ paintress shot <file> <out.png> [--scale n] 渲染为 PNG
57
+
58
+ paintress web [--port n] [--idle ms] [--daemon] 启动查看器
59
+ paintress ping | status | stop 查看器控制
60
+ paintress watch <name> <dir> | unwatch <name> 注册/移除要浏览的目录
61
+ paintress attach <name> agent 工作期间保持查看器存活
62
+
63
+ paintress install [client...] [--dry-run] 写入 MCP 配置条目
64
+ paintress doctor 查看各客户端配置状态
65
+ paintress mcp-config <client> 打印单个客户端的配置片段
66
+ ```
67
+
68
+ ## 查看器
69
+
70
+ ```bash
71
+ paintress web --daemon --idle 3600000
72
+ paintress watch demo ./demo
73
+ # 打开 http://localhost:3316/?project=demo&file=hello.ptr
74
+ ```
75
+
76
+ 画布操作:滚轮以指针为锚点缩放,空格 / 中键 / 空白处拖拽平移,`Ctrl/Cmd+0` 回到 100%,`Shift+1` 适应窗口。多画板文件会显示画板条。通过 CLI 或 MCP 做的修改会在查看器里高亮,点击会让节点获得短号,agent 用 `paintress_marks` 读回。
77
+
78
+ ## MCP 客户端
79
+
80
+ `paintress install` 会写入下面这些位置,也可以手动添加。
81
+
82
+ | 客户端 | 配置文件 | 顶层键 |
83
+ | --- | --- | --- |
84
+ | Claude Desktop | `~/.config/Claude/claude_desktop_config.json` | `mcpServers` |
85
+ | Cursor | `~/.cursor/mcp.json` | `mcpServers` |
86
+ | VS Code | `~/.config/Code/User/mcp.json` | `servers`(条目需 `"type": "stdio"`) |
87
+ | Codex | `~/.codex/config.toml` | `[mcp_servers.paintress]` |
88
+ | pi | `~/.pi/agent/mcp.json` | `mcpServers` |
89
+
90
+ ```bash
91
+ paintress mcp-config cursor # 只打印片段,不改文件
92
+ ```
93
+
94
+ ## Skill
95
+
96
+ `skills/paintress/SKILL.md` 是给 agent 的简短说明:什么时候用这些工具、`#1` 怎么映射到节点 id、哪个工具做什么。支持 skill 机制的客户端把它复制进对应目录即可。
97
+
98
+ ## 文档
99
+
100
+ - `specs/PLAN.md` —— 路线与未决问题
101
+ - `format/spec.yaml` —— 文件格式规格(`.pen` 层冻结,`.ptr` 层演进)
102
+ - `.agents/memory/ARCHITECTURE.md` —— 设计决策与理由
103
+ - `.agents/implementations/` —— 每次变更的记录与实测数据
104
+
105
+ ## 许可
106
+
107
+ MIT
108
+
109
+ ---
110
+
111
+ ## 给 AI agent 的安装手册
112
+
113
+ 您是被要求在本机安装 paintress 的 agent。请按顺序执行,并汇报:发现了什么、复用了什么、改动了什么。
114
+
115
+ **先检测。** 一台机器上可能有多个 agent,之前的 agent 可能已经全局安装过 paintress 并给自己注册过。再装一份是错误,不是周全。
116
+
117
+ ### 0. 检测
118
+
119
+ | 检查项 | 命令 | 含义 |
120
+ | --- | --- | --- |
121
+ | PATH 上是否已有 | `command -v paintress` | 已有可用安装 |
122
+ | 是否全局安装 | `npm ls -g --depth=0 paintress` | 通过 npm 装在全局 |
123
+ | 是否项目内安装 | `npm ls --depth=0 paintress` | 装在当前项目里 |
124
+ | 客户端配置 | `paintress doctor` | 哪些 agent 已有条目、状态如何 |
125
+
126
+ 规则:
127
+
128
+ - **命中任意一项就复用**,不要重装,直接进入第 2 步。
129
+ - 向用户报告解析到的路径与版本(查看器运行时 `paintress ping` 会打印版本;包版本用 `npm ls -g --depth=0 paintress`)。
130
+ - 只有在全部检查都为空时,才执行第 1 步。
131
+
132
+ ### 1. 安装(仅在检测全空时)
133
+
134
+ ```bash
135
+ npm install -g paintress
136
+ ```
137
+
138
+ 如果环境不允许全局安装:
139
+
140
+ ```bash
141
+ npm install paintress # 项目内安装,之后用 npx paintress ...
142
+ npx -y paintress@latest ... # 完全不安装,首次运行较慢
143
+ ```
144
+
145
+ 优先全局安装:只有全局安装,下一个 agent 才能直接复用。
146
+
147
+ ### 2. 给「当前所在的 agent」注册 MCP
148
+
149
+ ```bash
150
+ paintress doctor # 列出检测到的客户端与状态
151
+ paintress install --dry-run # 预览将要写入的内容
152
+ paintress install <client-id> # 只写当前所在的客户端
153
+ ```
154
+
155
+ 客户端 id:`claude`、`cursor`、`vscode`、`codex`、`pi`。不带参数执行 `paintress install` 会覆盖所有检测到的客户端。
156
+
157
+ 写入行为:
158
+
159
+ - 新增或更新名为 `paintress` 的一条(`mcpServers.paintress`;VS Code 为 `servers.paintress`;Codex 为 `[mcp_servers.paintress]`);
160
+ - 文件里其他键、其他 server、既有排版一律不动;
161
+ - 替换前在同目录写 `*.bak-<时间戳>` 备份;
162
+ - 幂等:条目已一致时返回 `noop`,不写磁盘;
163
+ - 当 `paintress` 能在 PATH 上解析时写裸命令,否则写可执行文件的绝对路径。
164
+
165
+ 如果当前 agent 不在列表里,打印最接近的片段再自行适配:
166
+
167
+ ```bash
168
+ paintress mcp-config cursor # JSON,顶层 mcpServers
169
+ paintress mcp-config vscode # JSON,顶层 servers + type: stdio
170
+ paintress mcp-config codex # TOML 表
171
+ ```
172
+
173
+ ### 3. 安装 skill(建议)
174
+
175
+ ```bash
176
+ cp "$(npm root -g)/paintress/skills/paintress/SKILL.md" <技能目录>/paintress/SKILL.md
177
+ ```
178
+
179
+ 使用您的 harness 真正读取的技能目录(`~/.pi/agent/skills/`、`~/.claude/skills/`、`.cursor/skills/` 等)。如果 harness 没有 skill 机制,把该文件读入上下文一次,并记住工具清单。
180
+
181
+ ### 4. 验证
182
+
183
+ ```bash
184
+ paintress doctor # 条目存在、命令可解析
185
+ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}\n' | paintress mcp
186
+ ```
187
+
188
+ 第二条命令应当返回包含 `"serverInfo": {"name": "paintress", …}` 的 `result`。会话重启后,对任意 `.pen` 文件调用 `paintress_doc_info` 是一次端到端验证。
189
+
190
+ ### 5. 多 agent 规则
191
+
192
+ - 一份全局安装服务本机所有 agent,不要为每个 agent 复制一份。
193
+ - 每个 agent 只添加**自己**的配置条目;不要重写其他 agent 的文件,也不要删除不是自己添加的条目。
194
+ - 如果 `doctor` 显示 `noop`,说明已有人正确注册过——就此停下并说明情况。
195
+ - 汇报格式:复用 / 新装 / 注册(哪个客户端)/ 跳过(原因)。
196
+
197
+ ### 6. 卸载
198
+
199
+ ```bash
200
+ npm uninstall -g paintress
201
+ ```
202
+
203
+ 然后从各客户端配置里删掉 `paintress` 条目,或恢复同目录下最新的 `*.bak-<时间戳>`。
Binary file
Binary file
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ width="540"
4
+ height="942"
5
+ viewBox="0 0 540 942"
6
+ role="img"
7
+ aria-label="paintress"
8
+ version="1.1"
9
+ id="svg1"
10
+ sodipodi:docname="logo-v2.svg"
11
+ inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
12
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ xmlns:svg="http://www.w3.org/2000/svg">
16
+ <defs
17
+ id="defs1" />
18
+ <sodipodi:namedview
19
+ id="namedview1"
20
+ pagecolor="#ffffff"
21
+ bordercolor="#000000"
22
+ borderopacity="0.25"
23
+ inkscape:showpageshadow="2"
24
+ inkscape:pageopacity="0.0"
25
+ inkscape:pagecheckerboard="0"
26
+ inkscape:deskcolor="#d1d1d1"
27
+ inkscape:zoom="0.73036093"
28
+ inkscape:cx="186.2093"
29
+ inkscape:cy="335.45058"
30
+ inkscape:window-width="2048"
31
+ inkscape:window-height="1130"
32
+ inkscape:window-x="2451"
33
+ inkscape:window-y="-6"
34
+ inkscape:window-maximized="1"
35
+ inkscape:current-layer="svg1" />
36
+ <title
37
+ id="title1">paintress</title>
38
+ <path
39
+ d="m 197,705 c -8.26,9.44 -8.26,9.44 -11.5,15 -0.19,0.31 -0.37,0.63 -0.56,0.95 -6.83,11.6 -11.92,23.92 -14.94,37.05 -0.12,0.49 -0.23,0.98 -0.35,1.48 -7.26,31.72 2.27,62.42 16.35,90.52 h 2 c -0.06,-0.52 -0.12,-1.04 -0.18,-1.57 -3.18,-27.37 -1.65,-51.92 15.93,-74.55 7.05,-8.67 15.37,-15.67 25.25,-20.88 -0.29,0.35 -0.58,0.7 -0.88,1.06 -21.52,26.11 -31.93,55.49 -28.92,89.48 1.98,19.12 9.45,35.88 18.8,52.46 h 3 c -0.07,-0.48 -0.14,-0.97 -0.21,-1.47 -0.67,-4.85 -1.03,-9.63 -1.04,-14.53 v -1.09 c 0.07,-25.16 10.74,-46.44 28.25,-63.91 0.34,-0.34 0.67,-0.68 1.02,-1.03 0.66,-0.66 1.32,-1.31 1.98,-1.97 v -2 c 0.29,-0.13 0.29,-0.13 1.77,-0.8 2.31,-1.16 4.03,-2.66 5.88,-4.44 0.34,-0.32 0.68,-0.63 1.02,-0.96 1.1,-1.03 2.18,-2.07 3.27,-3.11 0.76,-0.72 1.52,-1.45 2.27,-2.17 2.27,-2.16 4.53,-4.34 6.79,-6.52 0.17,-0.16 0.17,-0.16 1.01,-0.97 C 286.1,778.4 286.1,778.4 291,771 c -6.34,17.73 -16.66,32.22 -28.15,46.89 -16.46,21.04 -31.06,42.93 -31.1,70.61 v 1.33 c 0.04,16.47 4.61,33.21 15.25,46.17 0.98,0.83 1.87,1.39 3,2 0.02,-0.19 0.02,-0.19 0.15,-1.17 2.45,-18.03 9.3,-33.78 20.85,-47.83 0.36,-0.44 0.71,-0.87 1.08,-1.32 2.51,-3.02 5.18,-5.87 7.92,-8.68 0.27,-0.28 0.55,-0.56 0.83,-0.85 2.23,-2.29 4.48,-4.57 6.73,-6.84 0.16,-0.15 0.16,-0.15 0.94,-0.94 3.39,-3.41 6.84,-6.71 10.48,-9.85 4.1,-3.54 8,-7.26 11.83,-11.08 l 1.04,-1.04 C 331.59,828.6 346.77,804 351,776 c 0.03,-0.2 0.03,-0.2 0.21,-1.24 C 354.78,752.34 348.58,728.23 337,709 l -3,-4 z"
40
+ style="fill:#908030;fill-rule:evenodd;fill-opacity:1"
41
+ id="path1-8" />
42
+ <path
43
+ d="m 278.25,4.75 c -0.54,0.04 -1.07,0.09 -1.62,0.13 -32.98,2.74 -65.41,11.83 -94.6,27.56 -1.09,0.59 -2.18,1.17 -3.27,1.76 -9.58,5.13 -18.92,10.46 -27.76,16.8 -0.33,0.24 -0.67,0.48 -1.01,0.72 -19.41,13.9 -37.87,28.97 -55.51,45.05 -1.41,1.28 -2.83,2.53 -4.26,3.77 -4.6,3.96 -9,8.08 -13.27,12.38 -1.01,1.02 -2.02,2.02 -3.04,3.03 -4.51,4.51 -8.78,9.18 -12.91,14.05 -0.26,0.31 -0.52,0.62 -0.79,0.94 C 51.53,141.19 43.49,151.85 36,163 c -0.36,0.54 -0.73,1.08 -1.1,1.64 C 15.55,193.82 -1.65,230.99 5.25,266.8 9.67,288.42 20.73,307.65 34,325 c 0.34,0.45 0.68,0.91 1.04,1.37 4.36,5.74 9.12,11.09 14.03,16.37 2.45,2.63 4.83,5.29 7.12,8.07 2.24,2.69 4.71,4.97 7.38,7.23 2.11,1.79 4.14,3.62 6.13,5.54 13.36,12.86 28.46,23.82 44.3,33.42 0.58,0.35 1.15,0.7 1.73,1.05 5.19,3.16 10.41,6.27 15.66,9.35 28.21,16.59 53.61,34.5 62.54,67.99 1.54,6.13 2.75,12.29 3.07,18.61 0.03,0.46 0.05,0.93 0.08,1.4 0.68,13.34 -1.85,26.5 -4.08,39.6 1.85,-0.43 2.8,-0.76 4.04,-2.24 0.31,-0.52 0.62,-1.04 0.94,-1.58 0.17,-0.28 0.35,-0.57 0.53,-0.87 0.57,-0.95 1.12,-1.91 1.68,-2.87 0.39,-0.68 0.79,-1.35 1.19,-2.03 4.24,-7.28 8.03,-14.78 11.62,-22.41 0.14,-0.3 0.14,-0.3 0.85,-1.79 4.41,-9.46 7.6,-19.11 10.15,-29.21 0.08,-0.33 0.17,-0.66 0.26,-0.99 6.3,-24.54 5.51,-55.54 -7.26,-78.01 -4.49,-7.28 -11.39,-15.32 -19.92,-17.75 -6.41,-1.5 -12.87,-1.49 -19.36,-0.55 -0.6,0.09 -1.19,0.18 -1.8,0.26 -7.7,1.21 -15.19,3.27 -22.7,5.29 -3.09,0.83 -6.18,1.65 -9.27,2.46 -1.06,0.28 -2.12,0.57 -3.18,0.86 -10.28,2.74 -20.91,4.34 -30.77,-0.57 -3.76,-2.39 -4.96,-5.81 -6,-10 -0.28,-2.27 -0.36,-4.53 -0.44,-6.81 -0.4,-7.22 -1.09,-13.77 -6.18,-19.19 -1.931839,-2.97898 -2.97,-5.84 -3.21,-9.89 -0.561751,-9.12845 -0.405349,-4.8236 -3.1,-23.27 -0.12,-3.08 -1.22,-5.4 -3.07,-7.84 -2.19,-1.66 -4.54,-2.86 -7,-4.06 -7.85,-3.87 -7.85,-3.87 -9.62,-7.53 -0.89,-3.3 -0.51,-6.41 1.16,-9.37 1.41,-2.15 3,-4.12 4.64,-6.09 1.51,-1.84 2.98,-3.71 4.44,-5.59 0.3,-0.37 0.59,-0.74 0.89,-1.12 8.66,-11.08 15.69,-23.33 15.68,-37.68 v -1.85 c -0.05,-4.27 -0.5,-8.37 -1.19,-12.59 -4.35,-27.27 3.54,-56.47 19.58,-78.85 2.05,-2.82 4.2,-5.57 6.42,-8.27 0.03,0.22 0.03,0.22 0.2,1.32 4.32,28.44 16.11,51.38 36.06,71.91 0.76,0.79 1.51,1.58 2.27,2.38 7.9,8.31 16.26,15.56 25.47,22.39 0.37,0.27 0.74,0.55 1.11,0.83 4.85,3.6 9.71,7.18 14.58,10.74 13.79,10.11 26.89,20.68 39.31,32.43 0.37,0.34 0.73,0.69 1.11,1.04 5.52,5.18 10.26,10.99 14.89,16.96 0.39,0.5 0.78,1.01 1.18,1.53 22.65,29.58 36.75,65.17 38.82,102.47 0.02,0.28 0.02,0.28 0.1,1.68 2.35,45.29 -22.98,81.38 -49.23,115.57 -4.25,5.54 -8.44,11.12 -12.61,16.72 -1.3,1.74 -2.6,3.49 -3.91,5.22 -33.66,44.8 -33.66,44.8 -42.35,70.81 -0.18,0.52 -0.35,1.04 -0.53,1.57 -3.43,10.53 -2.77,21.5 -2.47,32.43 l 1,1 h 155 c 2.23,-6.7 2.23,-6.7 2.79,-10.04 0.13,-0.74 0.27,-1.48 0.4,-2.23 0.07,-0.38 0.14,-0.76 0.21,-1.16 4.16,-22.24 14.72,-40.98 28.6,-58.57 0.68,-0.87 1.35,-1.74 2.03,-2.61 4.41,-5.64 9.11,-10.97 13.94,-16.25 2.37,-2.61 4.68,-5.27 6.99,-7.94 0.97,-1.12 1.95,-2.22 2.93,-3.33 C 422.55,515.4 441.2,488.4 451,457 c 0.17,-0.54 0.34,-1.07 0.51,-1.62 8.54,-27.92 4.9,-58.56 -8.05,-84.44 -0.46,-0.94 -0.91,-1.88 -1.36,-2.82 -11.64,-24.3 -31.52,-46.39 -52.62,-62.94 -1.27,-1.01 -2.52,-2.05 -3.76,-3.1 -16.39,-13.88 -35.02,-25.85 -53.67,-36.48 -1.17,-0.67 -2.35,-1.35 -3.53,-2.03 -11.51,-6.64 -23.14,-13.02 -34.95,-19.11 -7.64,-3.95 -15.19,-8.03 -22.73,-12.18 -4.03,-2.21 -8.06,-4.42 -12.15,-6.51 C 234.47,213.38 212.24,198.14 192,180 c -0.36,-0.32 -0.73,-0.64 -1.1,-0.98 -12.8,-11.46 -22.69,-27.28 -30.34,-42.52 -0.17,-0.33 -0.33,-0.67 -0.51,-1.01 -2.03,-4.06 -3.75,-8.13 -5.05,-12.49 0.26,0.37 0.51,0.73 0.78,1.11 27.73,39.61 64.59,62.16 136.67,91.89 32.87,13.2 65.64,27.22 94.55,48 0.34,0.25 0.69,0.49 1.04,0.74 18.15,13 35.43,27.37 48.96,45.26 0.22,0.29 0.45,0.59 0.67,0.89 25.44,33.61 39.25,71.89 33.72,114.45 -0.57,3.94 -1.42,7.8 -2.39,11.66 -0.1,0.39 -0.19,0.78 -0.29,1.18 -3.25,12.86 -7.87,24.93 -13.71,36.82 -0.16,0.32 -0.32,0.65 -0.48,0.98 -11.57,23.61 -27.36,44.24 -44.52,64.02 5.28,0 9.9,-6.86 13.59,-10.54 l 4.92,-4.89 c 13.59,-13.51 13.59,-13.51 18.83,-19.78 1.05,-1.25 2.14,-2.48 3.22,-3.71 33.2,-38.29 57.35,-83.87 82.75,-188.96 0.04,-0.3 0.04,-0.3 0.25,-1.85 1.92,-14.6 2.68,-29.12 2.69,-43.83 v -1.21 C 536.28,216.18 525.54,167.08 481,96 474.35027,82.502457 457.163,66.047388 442.4,54.389598 429.97756,44.580059 423.21007,38.671449 403.0602,29.91592 382.91033,21.160391 355.82,11.19 331,7 330.62,6.94 330.24,6.87 329.84,6.8 312.74,3.95 295.51,3.36 278.25,4.75 Z M 190,655 c 1.15,10.37 1.15,10.37 2.16,15.5 0.1,0.56 0.21,1.12 0.32,1.69 0.22,1.16 0.45,2.33 0.68,3.49 l 1.02,5.34 c 0.22,1.14 0.44,2.28 0.66,3.41 0.1,0.53 0.21,1.06 0.31,1.61 0.05,0.25 0.05,0.25 0.29,1.49 0.09,0.43 0.17,0.86 0.26,1.3 C 196,690 196,690 197,692 h 137 c 2.56,-12.29 5.09,-24.59 7,-37 z"
44
+ style="fill:#205080;fill-opacity:1;fill-rule:evenodd"
45
+ id="path1"
46
+ sodipodi:nodetypes="ccccccccccccccccccccccccccccccsccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccszcccccccccccccccc" />
47
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paintress",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Design-document operation layer for AI agents: read, query, edit, render",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,9 @@
26
26
  },
27
27
  "files": [
28
28
  "dist",
29
- "ui/dist"
29
+ "ui/dist",
30
+ "skills",
31
+ "assets"
30
32
  ],
31
33
  "scripts": {
32
34
  "build": "tsdown && pnpm --dir ui build",
@@ -52,5 +54,13 @@
52
54
  "tsdown": "^0.12.0",
53
55
  "typescript": "^5.7.0",
54
56
  "vitest": "^3.0.0"
55
- }
57
+ },
58
+ "keywords": [
59
+ "mcp",
60
+ "model-context-protocol",
61
+ "ai-agents",
62
+ "design",
63
+ "renderer",
64
+ "cli"
65
+ ]
56
66
  }
@@ -0,0 +1,53 @@
1
+ ---
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.
4
+ ---
5
+
6
+ # paintress
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.
9
+
10
+ ## Check before installing
11
+
12
+ ```bash
13
+ command -v paintress # already on PATH?
14
+ npm ls -g --depth=0 paintress # installed globally?
15
+ ```
16
+
17
+ If either prints something, **do not install again**. Use the existing binary. `paintress doctor` shows which agents already have the MCP entry and whether it is current.
18
+
19
+ ## Tools
20
+
21
+ | Tool | What it does |
22
+ | --- | --- |
23
+ | `paintress_doc_info` | document summary: version, format flavor, node counts, variables, top-level nodes |
24
+ | `paintress_query` | find nodes by type / name (exact or regex) / id / reusable |
25
+ | `paintress_get` | fetch one node's subtree; `depth` controls how far down |
26
+ | `paintress_update` | shallow-merge attributes on one node (`id` and `type` are immutable) |
27
+ | `paintress_insert` | insert a child node; returns the generated `id` |
28
+ | `paintress_delete` | remove a node |
29
+ | `paintress_move` | reparent a node, optional index |
30
+ | `paintress_batch` | several operations in one call |
31
+ | `paintress_validate` | check the document against the format spec |
32
+ | `paintress_render` | write the canvas as a standalone HTML file |
33
+ | `paintress_screenshot` | render to PNG (headless Chromium) |
34
+ | `paintress_marks` | read the user's click markers (`#1 → node id`) |
35
+
36
+ ## Working rules
37
+
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.
43
+
44
+ ## Viewer
45
+
46
+ The human-facing viewer is a separate process:
47
+
48
+ ```bash
49
+ paintress web --daemon # http://localhost:3316
50
+ paintress watch <name> <dir> # register a directory
51
+ ```
52
+
53
+ Ask the user to open the URL and click the nodes they mean; then read `paintress_marks` to learn which ids those clicks became.
@@ -1,6 +1,6 @@
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=M,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(()=>F(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[ie.bind(n),e=>(typeof e==`function`&&(e=f&&f.running&&f.sources.has(n)?e(n.tValue):e(n.value)),D(n,e))]}function b(e,t,n){O(k(e,t,!1,c))}function x(e,t,n){s=se;let r=k(e,t,!1,c),i=E&&re(E);i&&(r.suspense=i),n&&n.render||(r.user=!0),h?h.push(r):O(r)}function S(e,t,n){n=n?Object.assign({},a,n):a;let r=k(e,t,!0,0);return r.observers=null,r.observerSlots=null,r.comparator=n.equals||void 0,O(r),ie.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,ne]=y(!1);function re(e){let t;return d&&d.context&&(t=d.context[e.id])!==void 0?t:e.defaultValue}var E;function ie(){let e=f&&f.running;if(this.sources&&(e?this.tState:this.state)){if((e?this.tState:this.state)===c)O(this);else{let e=m;m=null,j(()=>N(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 D(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&&P(n)),r?n.tState=c:n.state=c)}if(m.length>1e6)throw m=[],Error()},!1)}return t}function O(e){if(!e.fn)return;F(e);let t=g;ae(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,ae(e,e.tValue,t),p=d=null},!1)})}function ae(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(F),e.tOwned=void 0):(e.state=c,e.owned&&e.owned.forEach(F),e.owned=null)),e.updatedAt=n+1,R(t)}finally{p=a,d=i}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&`observers`in e?D(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 k(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 N(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)O(e);else if((t?e.tState:e.state)===l){let t=m;m=null,j(()=>N(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 oe(n),t}catch(e){n||(h=null),m=null,R(e)}}function oe(e){if(m&&=(M(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)F(e);for(let t of e){if(t.value=t.tValue,t.owned)for(let e=0,n=t.owned.length;e<n;e++)F(t.owned[e]);t.tOwned&&(t.owned=t.tOwned),delete t.tValue,delete t.tOwned,t.tState=0}ne(!1)},!1)}else if(f.running){f.running=!1,f.effects.push.apply(f.effects,h),h=null,ne(!0);return}}let n=h;h=null,n.length&&j(()=>s(n),!1),t&&t()}function M(e){for(let t=0;t<e.length;t++)A(e[t])}function se(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 N(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&&N(i,t)}}}function P(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&&P(r))}}function F(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--)F(e.tOwned[t]);delete e.tOwned}if(f&&f.running&&e.pure)I(e,!0);else if(e.owned){for(t=e.owned.length-1;t>=0;t--)F(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 I(e,t){if(t||(e.tState=0,f.disposed.add(e)),e.owned)for(let t=0;t<e.owned.length;t++)I(e.owned[t])}function ce(e){return e instanceof Error?e:Error(typeof e==`string`?e:`Unknown error`,{cause:e})}function L(e,t,n){try{for(let n of t)n(e)}catch(e){R(e,n&&n.owner||null)}}function R(e,t=d){let n=o&&t&&t.context&&t.context[o],r=ce(e);if(!n)throw r;h?h.push({fn(){L(r,n,t)},state:c}):L(r,n,t)}var le=Symbol(`fallback`);function z(e){for(let t=0;t<e.length;t++)e[t]()}function ue(e,t,n={}){let r=[],a=[],o=[],s=0,c=t.length>1?[]:null;return T(()=>z(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&&(z(o),o=[],r=[],a=[],s=0,c&&=[]),n.fallback&&(r=[le],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 B(e,t){return C(()=>e(t||{}))}var de=e=>`Stale read from <${e}>.`;function V(e){let t=`fallback`in e&&{fallback:()=>e.fallback};return S(ue(()=>e.each,e.children,t||void 0))}function H(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 de(`Show`);return n()})):a}return e.fallback},void 0,void 0)}var fe=e=>S(()=>e());function pe(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 me=`_$DX_DELEGATE`;function U(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[me]||(t[me]=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,ve))}}function ge(e,t,n){K(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 q(e,t,r,n);b(r=>q(e,t(),r,n),r)}function K(t){return!!e.context&&!e.done&&(!t||t.isConnected)}function ve(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 q(e,t,n,r,i){let a=K(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=X(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=X(e,n,r)}else if(o===`function`)return b(()=>{let i=t();for(;typeof i==`function`;)i=i();n=q(e,i,n,r)}),()=>n;else if(Array.isArray(t)){let o=[],c=n&&Array.isArray(n);if(J(o,t,n,i))return b(()=>n=q(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=X(e,n,r),s)return n}else c?n.length===0?Y(e,o,r):pe(e,n,o):(n&&X(e),Y(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=X(e,n,r,t);X(e,n,null,t)}else n==null||n===``||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}return n}function J(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=J(e,o,s)||i;else if(c===`function`){if(r){for(;typeof o==`function`;)o=o();i=J(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 Y(e,t,n=null){for(let r=0,i=t.length;r<i;r++)e.insertBefore(t[r],n)}function X(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 Z(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 ye(){return Z(`/api/projects`)}function be(e){return Z(`/api/projects/${encodeURIComponent(e)}/files`)}function xe(e,t){return`/render/${encodeURIComponent(e)}/${t.split(`/`).map(encodeURIComponent).join(`/`)}?embed=1`}function Se(e,t){return`/events?project=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`}function Ce(e){return Z(`/api/marks?project=${encodeURIComponent(e)}`)}async function we(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 Te(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 Ee(e,t){return Z(`/api/changes?project=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`)}var De=.1,Oe=.02;function Q(e,t=De){return Math.min(4,Math.max(t,e))}function ke(e,t,n,r,i=De){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 Ae(e,t,n,r=De){return ke(e,Q(t,r)/e.z,n.width/2,n.height/2,r)}function je(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 Me(e,t,n){return{z:e.z,tx:e.tx+t,ty:e.ty+n}}function Ne(e){return`translate(${e.tx}px, ${e.ty}px) scale(${e.z})`}function Pe(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 Fe=3,Ie=.0015;function Le({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=Ne(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){n(e.deltaY>0?-1:1,{x:e.clientX,y:e.clientY});return}p();let t=1;e.deltaMode===1?t=16:e.deltaMode===2&&(t=u().height),f(ke(a,Math.exp(-e.deltaY*t*Ie),e.clientX,e.clientY))}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)<Fe)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(Me(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(Ae(a,1,u())),!0):e.shiftKey&&!e.ctrlKey&&!e.metaKey&&(e.key===`!`||e.code===`Digit1`)?(T(),!0):!1}function T(){f(je({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(je(e,u())),zoomToPercent:e=>f(Ae(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 Re=W(`<span class=badge>v`),ze=W(`<ul class=list>`),Be=W(`<section class="section files"><h2>设计稿 <span class=hint>depth `),Ve=W(`<section class="section marks"><h2>标记<button class="ghost tiny">清空</button></h2><ul class=list>`),He=W(`<span class=hint>pid `),Ue=W(`<div class=error>`),We=W(`<code>/`),Ge=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>重新渲染`),Ke=W(`<div class=boardbar><button class=board>全部`),qe=W(`<iframe class=preview title="design preview"sandbox="allow-same-origin allow-scripts">`),Je=W(`<div class=app><aside class=sidebar><header class=sidebar-head><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>`),Ye=W(`<p class=empty>还没有注册项目<br><code>paintress watch &lt;name&gt; &lt;dir&gt;`),Xe=W(`<li><button class=item><span class=item-name></span><span class=item-meta> 个设计稿`),Ze=W(`<p class=empty>没有 .ptr / .pen 文件`),Qe=W(`<li><button class=item><span class=item-name>`),$e=W(`<li><button class=item><span class=item-name><b class=mark-no>#</b> </span><span class=item-meta>`),et=W(`<p>从左侧选择一个设计稿`),tt=W(`<div class=placeholder>`),nt=W(`<p>从左侧选择项目`),rt=W(`<span class=sep>/`),it=W(`<button class=crumb>`),at=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 ot(){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),[ne,re]=y(``),[E,ie]=y([]),[D,O]=y(null),[ae,k]=y(1),A,j=null,oe;function M(){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 se(){try{let e=await ye();t(e.projects),_({pid:e.pid,version:e.version}),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}}async function N(e){try{l((await Ce(e)).marks)}catch{l([])}}async function P(e){r(e),s(null),d(null),p([]);try{let t=await be(e);a(t.files),h(null)}catch(e){a([]),h(e instanceof Error?e.message:String(e))}N(e),M()}function F(e){s(e),d(null),p([]),M()}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?(j?.setSpace(!0),t.preventDefault()):j?.handleKey(t)===!0&&t.preventDefault())},n=e=>{e.key===` `&&j?.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 se();let e=r.get(`project`);if(e){await P(e);let t=r.get(`file`);t&&F(t)}})()});let I=()=>A?.contentDocument??null;function ce(){let e=I();if(!e||e.getElementById(`ptr-style`))return;let t=e.createElement(`style`);t.id=`ptr-style`,t.textContent=`[data-ptr-selected]{outline:2px solid #2563eb !important;outline-offset:-2px;}
2
- [data-ptr-hover]:not([data-ptr-selected]){outline:1px dashed #60a5fa !important;outline-offset:-2px;}
3
- [data-ptr-changed]{box-shadow:inset 0 0 0 2px #f59e0b !important;animation:ptr-pulse 1.4s ease-in-out 3;}
4
- @keyframes ptr-pulse{0%,100%{box-shadow:inset 0 0 0 2px #f59e0b}50%{box-shadow:inset 0 0 0 2px #fde68a}}
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=M,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(()=>F(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[ie.bind(n),e=>(typeof e==`function`&&(e=f&&f.running&&f.sources.has(n)?e(n.tValue):e(n.value)),D(n,e))]}function b(e,t,n){O(k(e,t,!1,c))}function x(e,t,n){s=se;let r=k(e,t,!1,c),i=E&&re(E);i&&(r.suspense=i),n&&n.render||(r.user=!0),h?h.push(r):O(r)}function S(e,t,n){n=n?Object.assign({},a,n):a;let r=k(e,t,!0,0);return r.observers=null,r.observerSlots=null,r.comparator=n.equals||void 0,O(r),ie.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,ne]=y(!1);function re(e){let t;return d&&d.context&&(t=d.context[e.id])!==void 0?t:e.defaultValue}var E;function ie(){let e=f&&f.running;if(this.sources&&(e?this.tState:this.state)){if((e?this.tState:this.state)===c)O(this);else{let e=m;m=null,j(()=>N(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 D(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&&P(n)),r?n.tState=c:n.state=c)}if(m.length>1e6)throw m=[],Error()},!1)}return t}function O(e){if(!e.fn)return;F(e);let t=g;ae(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,ae(e,e.tValue,t),p=d=null},!1)})}function ae(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(F),e.tOwned=void 0):(e.state=c,e.owned&&e.owned.forEach(F),e.owned=null)),e.updatedAt=n+1,R(t)}finally{p=a,d=i}(!e.updatedAt||e.updatedAt<=n)&&(e.updatedAt!=null&&`observers`in e?D(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 k(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 N(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)O(e);else if((t?e.tState:e.state)===l){let t=m;m=null,j(()=>N(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 oe(n),t}catch(e){n||(h=null),m=null,R(e)}}function oe(e){if(m&&=(M(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)F(e);for(let t of e){if(t.value=t.tValue,t.owned)for(let e=0,n=t.owned.length;e<n;e++)F(t.owned[e]);t.tOwned&&(t.owned=t.tOwned),delete t.tValue,delete t.tOwned,t.tState=0}ne(!1)},!1)}else if(f.running){f.running=!1,f.effects.push.apply(f.effects,h),h=null,ne(!0);return}}let n=h;h=null,n.length&&j(()=>s(n),!1),t&&t()}function M(e){for(let t=0;t<e.length;t++)A(e[t])}function se(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 N(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&&N(i,t)}}}function P(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&&P(r))}}function F(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--)F(e.tOwned[t]);delete e.tOwned}if(f&&f.running&&e.pure)I(e,!0);else if(e.owned){for(t=e.owned.length-1;t>=0;t--)F(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 I(e,t){if(t||(e.tState=0,f.disposed.add(e)),e.owned)for(let t=0;t<e.owned.length;t++)I(e.owned[t])}function ce(e){return e instanceof Error?e:Error(typeof e==`string`?e:`Unknown error`,{cause:e})}function L(e,t,n){try{for(let n of t)n(e)}catch(e){R(e,n&&n.owner||null)}}function R(e,t=d){let n=o&&t&&t.context&&t.context[o],r=ce(e);if(!n)throw r;h?h.push({fn(){L(r,n,t)},state:c}):L(r,n,t)}var le=Symbol(`fallback`);function z(e){for(let t=0;t<e.length;t++)e[t]()}function ue(e,t,n={}){let r=[],a=[],o=[],s=0,c=t.length>1?[]:null;return T(()=>z(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&&(z(o),o=[],r=[],a=[],s=0,c&&=[]),n.fallback&&(r=[le],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 B(e,t){return C(()=>e(t||{}))}var de=e=>`Stale read from <${e}>.`;function V(e){let t=`fallback`in e&&{fallback:()=>e.fallback};return S(ue(()=>e.each,e.children,t||void 0))}function H(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 de(`Show`);return n()})):a}return e.fallback},void 0,void 0)}var fe=e=>S(()=>e());function pe(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 me=`_$DX_DELEGATE`;function U(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[me]||(t[me]=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,ve))}}function ge(e,t,n){K(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 q(e,t,r,n);b(r=>q(e,t(),r,n),r)}function K(t){return!!e.context&&!e.done&&(!t||t.isConnected)}function ve(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 q(e,t,n,r,i){let a=K(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=X(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=X(e,n,r)}else if(o===`function`)return b(()=>{let i=t();for(;typeof i==`function`;)i=i();n=q(e,i,n,r)}),()=>n;else if(Array.isArray(t)){let o=[],c=n&&Array.isArray(n);if(J(o,t,n,i))return b(()=>n=q(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=X(e,n,r),s)return n}else c?n.length===0?Y(e,o,r):pe(e,n,o):(n&&X(e),Y(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=X(e,n,r,t);X(e,n,null,t)}else n==null||n===``||!e.firstChild?e.appendChild(t):e.replaceChild(t,e.firstChild);n=t}return n}function J(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=J(e,o,s)||i;else if(c===`function`){if(r){for(;typeof o==`function`;)o=o();i=J(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 Y(e,t,n=null){for(let r=0,i=t.length;r<i;r++)e.insertBefore(t[r],n)}function X(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 Z(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 ye(){return Z(`/api/projects`)}function be(e){return Z(`/api/projects/${encodeURIComponent(e)}/files`)}function xe(e,t){return`/render/${encodeURIComponent(e)}/${t.split(`/`).map(encodeURIComponent).join(`/`)}?embed=1`}function Se(e,t){return`/events?project=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`}function Ce(e){return Z(`/api/marks?project=${encodeURIComponent(e)}`)}async function we(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 Te(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 Ee(e,t){return Z(`/api/changes?project=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`)}var De=.1,Oe=.02;function Q(e,t=De){return Math.min(4,Math.max(t,e))}function ke(e,t,n,r,i=De){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 Ae(e,t,n,r=De){return ke(e,Q(t,r)/e.z,n.width/2,n.height/2,r)}function je(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 Me(e,t,n){return{z:e.z,tx:e.tx+t,ty:e.ty+n}}function Ne(e){return`translate(${e.tx}px, ${e.ty}px) scale(${e.z})`}function Pe(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 Fe=3,Ie=.0015;function Le({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=Ne(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){n(e.deltaY>0?-1:1,{x:e.clientX,y:e.clientY});return}p();let t=1;e.deltaMode===1?t=16:e.deltaMode===2&&(t=u().height),f(ke(a,Math.exp(-e.deltaY*t*Ie),e.clientX,e.clientY))}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)<Fe)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(Me(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(Ae(a,1,u())),!0):e.shiftKey&&!e.ctrlKey&&!e.metaKey&&(e.key===`!`||e.code===`Digit1`)?(T(),!0):!1}function T(){f(je({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(je(e,u())),zoomToPercent:e=>f(Ae(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 Re=W(`<span class=badge>v`),ze=W(`<ul class=list>`),Be=W(`<section class="section files"><h2>设计稿 <span class=hint>depth `),Ve=W(`<section class="section marks"><h2>标记<button class="ghost tiny">清空</button></h2><ul class=list>`),He=W(`<span class=hint>pid `),Ue=W(`<div class=error>`),We=W(`<code>/`),Ge=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>重新渲染`),Ke=W(`<div class=boardbar><button class=board>全部`),qe=W(`<iframe class=preview title="design preview"sandbox="allow-same-origin allow-scripts">`),Je=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>`),Ye=W(`<p class=empty>还没有注册项目<br><code>paintress watch &lt;name&gt; &lt;dir&gt;`),Xe=W(`<li><button class=item><span class=item-name></span><span class=item-meta> 个设计稿`),Ze=W(`<p class=empty>没有 .ptr / .pen 文件`),Qe=W(`<li><button class=item><span class=item-name>`),$e=W(`<li><button class=item><span class=item-name><b class=mark-no>#</b> </span><span class=item-meta>`),et=W(`<p>从左侧选择一个设计稿`),tt=W(`<div class=placeholder>`),nt=W(`<p>从左侧选择项目`),rt=W(`<span class=sep>/`),it=W(`<button class=crumb>`),at=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 ot(){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),[ne,re]=y(``),[E,ie]=y([]),[D,O]=y(null),[ae,k]=y(1),A,j=null,oe;function M(){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 se(){try{let e=await ye();t(e.projects),_({pid:e.pid,version:e.version}),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}}async function N(e){try{l((await Ce(e)).marks)}catch{l([])}}async function P(e){r(e),s(null),d(null),p([]);try{let t=await be(e);a(t.files),h(null)}catch(e){a([]),h(e instanceof Error?e.message:String(e))}N(e),M()}function F(e){s(e),d(null),p([]),M()}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?(j?.setSpace(!0),t.preventDefault()):j?.handleKey(t)===!0&&t.preventDefault())},n=e=>{e.key===` `&&j?.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 se();let e=r.get(`project`);if(e){await P(e);let t=r.get(`file`);t&&F(t)}})()});let I=()=>A?.contentDocument??null;function ce(){let e=I();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
5
  html{cursor:default;}
6
- html,body{overflow:hidden;}`,e.head.appendChild(t)}function L(){let e=I();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 R(e){return e.parentElement?.closest(`[data-node-id]`)??null}function le(e){return e.querySelector(`:scope > [data-node-id]`)}function z(e){if(j?.consumeDragClick()===!0){e.preventDefault(),e.stopPropagation();return}let t=e.target?.closest(`[data-node-id]`);t&&(e.preventDefault(),e.stopPropagation(),J(e.altKey?R(t)??t:t))}function ue(e,t){let n=I();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&&Y(e);return}let a=(e===1?R(i):le(i))?.dataset.nodeId;a!==void 0&&Y(a)}function de(e){let t=I();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 pe(){let e=n(),t=o();if(e&&t)try{let n=await Ee(e,t);if(!n.ok||n.ids.length===0)return;let r=I();if(!r)return;for(let e of n.ids)r.querySelector(`[data-node-id="${$(e)}"]`)?.setAttribute(`data-ptr-changed`,``)}catch{}}function me(){let e=I();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 U(){let e=n(),t=o();return e&&t?`paintress:viewport:${e}/${t}`:null}function W(e){let t=U();t!==null&&(clearTimeout(oe),oe=setTimeout(()=>{try{localStorage.setItem(t,JSON.stringify(e))}catch{}},300))}function he(){let e=I();e&&(ce(),j?.destroy(),j=Le({doc:e,onChange:e=>{k(e.z),W(e)},onLevelStep:ue}),ie(me()),O(null),K(),e.addEventListener(`click`,z,!0),e.addEventListener(`mousemove`,de,!0),L(),pe())}function K(){if(!j)return;let e=U(),t=e===null?null:localStorage.getItem(e),n=t===null?null:Pe(t);n===null?j.fitAll():j.set(n)}function ve(e){if(O(e),!j)return;if(e===null){j.fitAll();return}let t=E().find(t=>t.id===e);t&&j.focusBox({x:t.x,y:t.y,w:t.w,h:t.h})}function q(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 J(e){let t=e.dataset.nodeId??``;if(t===``)return;let r=q(e);d(t),p(r),L();let i=n();if(i)try{await we({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 Ce(i)).marks),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}}function Y(e){d(e),L();let t=I(),n=t?.querySelector(`[data-node-id="${$(e)}"]`);if(!n||!t){p([]);return}p(q(n));let r=t.querySelector(`.ptr-canvas`);if(!r||!j){n.scrollIntoView({block:`center`});return}let i=n.getBoundingClientRect(),a=r.getBoundingClientRect(),o=j.get().z;j.centerOn({x:(i.left-a.left)/o,y:(i.top-a.top)/o,w:i.width/o,h:i.height/o})}async function X(){let e=n();if(e)try{await Te(e),l([]),d(null),p([]),L()}catch(e){h(e instanceof Error?e.message:String(e))}}x(w([n,o,v],()=>{let e=n(),t=o();re(e&&t?`${xe(e,t)}&r=${v()}`:``)}));let Z=null;return x(w([n,o,C],()=>{Z?.close(),Z=null;let e=n(),t=o();C()&&e&&t&&(Z=new EventSource(Se(e,t)),Z.addEventListener(`change`,()=>S(e=>e+1)))})),T(()=>Z?.close()),(()=>{var t=Je(),r=t.firstChild,a=r.firstChild;a.firstChild;var s=a.nextSibling;s.firstChild;var l=s.nextSibling,d=l.firstChild,p=d.firstChild,h=d.nextSibling,_=r.nextSibling;return G(a,B(H,{get when(){return g().version},get children(){var e=Re();return e.firstChild,G(e,()=>g().version,null),e}}),null),G(s,B(H,{get when(){return e().length>0},get fallback(){return Ye()},get children(){var t=ze();return G(t,B(V,{get each(){return e()},children:e=>(()=>{var t=Xe(),r=t.firstChild,i=r.firstChild,a=i.nextSibling,o=a.firstChild;return r.$$click=()=>void P(e.name),G(i,()=>e.name),G(a,()=>e.fileCount,o),b(()=>r.classList.toggle(`active`,n()===e.name)),t})()})),t}}),null),G(r,B(H,{get when(){return n()},get children(){var t=Be(),r=t.firstChild.firstChild.nextSibling;return r.firstChild,G(r,()=>e().find(e=>e.name===n())?.depth??`-`,null),G(t,B(H,{get when(){return i().length>0},get fallback(){return Ze()},get children(){var e=ze();return G(e,B(V,{get each(){return i()},children:e=>(()=>{var t=Qe(),n=t.firstChild,r=n.firstChild;return n.$$click=()=>F(e),G(r,e),b(()=>n.classList.toggle(`active`,o()===e)),t})()})),e}}),null),t}}),l),G(r,B(H,{get when(){return c().length>0},get children(){var e=Ve(),t=e.firstChild,n=t.firstChild.nextSibling,r=t.nextSibling;return n.$$click=()=>void X(),G(r,B(V,{get each(){return c()},children:e=>(()=>{var t=$e(),n=t.firstChild,r=n.firstChild,i=r.firstChild;i.firstChild,i.nextSibling;var a=r.nextSibling;return n.$$click=()=>Y(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 se(),G(l,B(H,{get when(){return g().pid},get children(){var e=He();return e.firstChild,G(e,()=>g().pid,null),e}}),null),G(_,B(H,{get when(){return m()},get children(){var e=Ue();return G(e,m),e}}),null),G(_,B(H,{get when(){return fe(()=>n()!==null)()&&o()!==null},get fallback(){return(()=>{var e=tt();return G(e,B(H,{get when(){return n()},get fallback(){return nt()},get children(){return et()}})),e})()},get children(){return[(()=>{var e=Ge(),t=e.firstChild,r=t.nextSibling.firstChild,i=r.firstChild,a=r.nextSibling,s=a.nextSibling,c=s.nextSibling;return G(t,B(H,{get when(){return f().length===0},get fallback(){return B(V,{get each(){return f()},children:(e,t)=>[B(H,{get when(){return t()>0},get children(){return rt()}}),(()=>{var t=it();return t.$$click=()=>Y(e.id),G(t,()=>e.name??e.type),b(()=>t.classList.toggle(`active`,u()===e.id)),t})()]})},get children(){var e=We(),t=e.firstChild;return G(e,n,t),G(e,o,null),e}})),G(r,()=>Math.round(ae()*100),i),a.$$click=()=>j?.fitAll(),s.$$click=()=>j?.zoomToPercent(1),c.$$click=()=>S(e=>e+1),e})(),B(H,{get when(){return E().length>1},get children(){var e=Ke(),t=e.firstChild;return t.$$click=()=>ve(null),G(e,B(V,{get each(){return E()},children:e=>(()=>{var t=at(),n=t.firstChild,r=n.firstChild;return t.$$click=()=>ve(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`,D()===e.id)),t})()}),null),b(()=>t.classList.toggle(`active`,D()===null)),e}}),(()=>{var e=qe();return e.addEventListener(`load`,he),_e(e=>{A=e},e),b(()=>ge(e,`src`,ne())),e})()]}}),null),b(()=>p.checked=C()),t})()}he([`click`]);var st=document.getElementById(`root`);if(!st)throw Error(`缺少 #root 容器`);U(()=>B(ot,{}),st);
6
+ html,body{overflow:hidden;}`,e.head.appendChild(t)}function L(){let e=I();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 R(e){return e.parentElement?.closest(`[data-node-id]`)??null}function le(e){return e.querySelector(`:scope > [data-node-id]`)}function z(e){if(j?.consumeDragClick()===!0){e.preventDefault(),e.stopPropagation();return}let t=e.target?.closest(`[data-node-id]`);t&&(e.preventDefault(),e.stopPropagation(),J(e.altKey?R(t)??t:t))}function ue(e,t){let n=I();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&&Y(e);return}let a=(e===1?R(i):le(i))?.dataset.nodeId;a!==void 0&&Y(a)}function de(e){let t=I();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 pe(){let e=n(),t=o();if(e&&t)try{let n=await Ee(e,t);if(!n.ok||n.ids.length===0)return;let r=I();if(!r)return;for(let e of n.ids)r.querySelector(`[data-node-id="${$(e)}"]`)?.setAttribute(`data-ptr-changed`,``)}catch{}}function me(){let e=I();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 U(){let e=n(),t=o();return e&&t?`paintress:viewport:${e}/${t}`:null}function W(e){let t=U();t!==null&&(clearTimeout(oe),oe=setTimeout(()=>{try{localStorage.setItem(t,JSON.stringify(e))}catch{}},300))}function he(){let e=I();e&&(ce(),j?.destroy(),j=Le({doc:e,onChange:e=>{k(e.z),W(e)},onLevelStep:ue}),ie(me()),O(null),K(),e.addEventListener(`click`,z,!0),e.addEventListener(`mousemove`,de,!0),L(),pe())}function K(){if(!j)return;let e=U(),t=e===null?null:localStorage.getItem(e),n=t===null?null:Pe(t);n===null?j.fitAll():j.set(n)}function ve(e){if(O(e),!j)return;if(e===null){j.fitAll();return}let t=E().find(t=>t.id===e);t&&j.focusBox({x:t.x,y:t.y,w:t.w,h:t.h})}function q(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 J(e){let t=e.dataset.nodeId??``;if(t===``)return;let r=q(e);d(t),p(r),L();let i=n();if(i)try{await we({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 Ce(i)).marks),h(null)}catch(e){h(e instanceof Error?e.message:String(e))}}function Y(e){d(e),L();let t=I(),n=t?.querySelector(`[data-node-id="${$(e)}"]`);if(!n||!t){p([]);return}p(q(n));let r=t.querySelector(`.ptr-canvas`);if(!r||!j){n.scrollIntoView({block:`center`});return}let i=n.getBoundingClientRect(),a=r.getBoundingClientRect(),o=j.get().z;j.centerOn({x:(i.left-a.left)/o,y:(i.top-a.top)/o,w:i.width/o,h:i.height/o})}async function X(){let e=n();if(e)try{await Te(e),l([]),d(null),p([]),L()}catch(e){h(e instanceof Error?e.message:String(e))}}x(w([n,o,v],()=>{let e=n(),t=o();re(e&&t?`${xe(e,t)}&r=${v()}`:``)}));let Z=null;return x(w([n,o,C],()=>{Z?.close(),Z=null;let e=n(),t=o();C()&&e&&t&&(Z=new EventSource(Se(e,t)),Z.addEventListener(`change`,()=>S(e=>e+1)))})),T(()=>Z?.close()),(()=>{var t=Je(),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,B(H,{get when(){return g().version},get children(){var e=Re();return e.firstChild,G(e,()=>g().version,null),e}}),null),G(s,B(H,{get when(){return e().length>0},get fallback(){return Ye()},get children(){var t=ze();return G(t,B(V,{get each(){return e()},children:e=>(()=>{var t=Xe(),r=t.firstChild,i=r.firstChild,a=i.nextSibling,o=a.firstChild;return r.$$click=()=>void P(e.name),G(i,()=>e.name),G(a,()=>e.fileCount,o),b(()=>r.classList.toggle(`active`,n()===e.name)),t})()})),t}}),null),G(r,B(H,{get when(){return n()},get children(){var t=Be(),r=t.firstChild.firstChild.nextSibling;return r.firstChild,G(r,()=>e().find(e=>e.name===n())?.depth??`-`,null),G(t,B(H,{get when(){return i().length>0},get fallback(){return Ze()},get children(){var e=ze();return G(e,B(V,{get each(){return i()},children:e=>(()=>{var t=Qe(),n=t.firstChild,r=n.firstChild;return n.$$click=()=>F(e),G(r,e),b(()=>n.classList.toggle(`active`,o()===e)),t})()})),e}}),null),t}}),l),G(r,B(H,{get when(){return c().length>0},get children(){var e=Ve(),t=e.firstChild,n=t.firstChild.nextSibling,r=t.nextSibling;return n.$$click=()=>void X(),G(r,B(V,{get each(){return c()},children:e=>(()=>{var t=$e(),n=t.firstChild,r=n.firstChild,i=r.firstChild;i.firstChild,i.nextSibling;var a=r.nextSibling;return n.$$click=()=>Y(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 se(),G(l,B(H,{get when(){return g().pid},get children(){var e=He();return e.firstChild,G(e,()=>g().pid,null),e}}),null),G(_,B(H,{get when(){return m()},get children(){var e=Ue();return G(e,m),e}}),null),G(_,B(H,{get when(){return fe(()=>n()!==null)()&&o()!==null},get fallback(){return(()=>{var e=tt();return G(e,B(H,{get when(){return n()},get fallback(){return nt()},get children(){return et()}})),e})()},get children(){return[(()=>{var e=Ge(),t=e.firstChild,r=t.nextSibling.firstChild,i=r.firstChild,a=r.nextSibling,s=a.nextSibling,c=s.nextSibling;return G(t,B(H,{get when(){return f().length===0},get fallback(){return B(V,{get each(){return f()},children:(e,t)=>[B(H,{get when(){return t()>0},get children(){return rt()}}),(()=>{var t=it();return t.$$click=()=>Y(e.id),G(t,()=>e.name??e.type),b(()=>t.classList.toggle(`active`,u()===e.id)),t})()]})},get children(){var e=We(),t=e.firstChild;return G(e,n,t),G(e,o,null),e}})),G(r,()=>Math.round(ae()*100),i),a.$$click=()=>j?.fitAll(),s.$$click=()=>j?.zoomToPercent(1),c.$$click=()=>S(e=>e+1),e})(),B(H,{get when(){return E().length>1},get children(){var e=Ke(),t=e.firstChild;return t.$$click=()=>ve(null),G(e,B(V,{get each(){return E()},children:e=>(()=>{var t=at(),n=t.firstChild,r=n.firstChild;return t.$$click=()=>ve(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`,D()===e.id)),t})()}),null),b(()=>t.classList.toggle(`active`,D()===null)),e}}),(()=>{var e=qe();return e.addEventListener(`load`,he),_e(e=>{A=e},e),b(()=>ge(e,`src`,ne())),e})()]}}),null),b(()=>p.checked=C()),t})()}he([`click`]);var st=document.getElementById(`root`);if(!st)throw Error(`缺少 #root 容器`);U(()=>B(ot,{}),st);
@@ -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;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}
@@ -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-D1dPL6Y0.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-D--yd1D2.css">
7
+ <script type="module" crossorigin src="/assets/index-CMZ78plm.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-lyiZ9W-B.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ width="540"
4
+ height="942"
5
+ viewBox="0 0 540 942"
6
+ role="img"
7
+ aria-label="paintress"
8
+ version="1.1"
9
+ id="svg1"
10
+ sodipodi:docname="logo-v2.svg"
11
+ inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
12
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ xmlns:svg="http://www.w3.org/2000/svg">
16
+ <defs
17
+ id="defs1" />
18
+ <sodipodi:namedview
19
+ id="namedview1"
20
+ pagecolor="#ffffff"
21
+ bordercolor="#000000"
22
+ borderopacity="0.25"
23
+ inkscape:showpageshadow="2"
24
+ inkscape:pageopacity="0.0"
25
+ inkscape:pagecheckerboard="0"
26
+ inkscape:deskcolor="#d1d1d1"
27
+ inkscape:zoom="0.73036093"
28
+ inkscape:cx="186.2093"
29
+ inkscape:cy="335.45058"
30
+ inkscape:window-width="2048"
31
+ inkscape:window-height="1130"
32
+ inkscape:window-x="2451"
33
+ inkscape:window-y="-6"
34
+ inkscape:window-maximized="1"
35
+ inkscape:current-layer="svg1" />
36
+ <title
37
+ id="title1">paintress</title>
38
+ <path
39
+ d="m 197,705 c -8.26,9.44 -8.26,9.44 -11.5,15 -0.19,0.31 -0.37,0.63 -0.56,0.95 -6.83,11.6 -11.92,23.92 -14.94,37.05 -0.12,0.49 -0.23,0.98 -0.35,1.48 -7.26,31.72 2.27,62.42 16.35,90.52 h 2 c -0.06,-0.52 -0.12,-1.04 -0.18,-1.57 -3.18,-27.37 -1.65,-51.92 15.93,-74.55 7.05,-8.67 15.37,-15.67 25.25,-20.88 -0.29,0.35 -0.58,0.7 -0.88,1.06 -21.52,26.11 -31.93,55.49 -28.92,89.48 1.98,19.12 9.45,35.88 18.8,52.46 h 3 c -0.07,-0.48 -0.14,-0.97 -0.21,-1.47 -0.67,-4.85 -1.03,-9.63 -1.04,-14.53 v -1.09 c 0.07,-25.16 10.74,-46.44 28.25,-63.91 0.34,-0.34 0.67,-0.68 1.02,-1.03 0.66,-0.66 1.32,-1.31 1.98,-1.97 v -2 c 0.29,-0.13 0.29,-0.13 1.77,-0.8 2.31,-1.16 4.03,-2.66 5.88,-4.44 0.34,-0.32 0.68,-0.63 1.02,-0.96 1.1,-1.03 2.18,-2.07 3.27,-3.11 0.76,-0.72 1.52,-1.45 2.27,-2.17 2.27,-2.16 4.53,-4.34 6.79,-6.52 0.17,-0.16 0.17,-0.16 1.01,-0.97 C 286.1,778.4 286.1,778.4 291,771 c -6.34,17.73 -16.66,32.22 -28.15,46.89 -16.46,21.04 -31.06,42.93 -31.1,70.61 v 1.33 c 0.04,16.47 4.61,33.21 15.25,46.17 0.98,0.83 1.87,1.39 3,2 0.02,-0.19 0.02,-0.19 0.15,-1.17 2.45,-18.03 9.3,-33.78 20.85,-47.83 0.36,-0.44 0.71,-0.87 1.08,-1.32 2.51,-3.02 5.18,-5.87 7.92,-8.68 0.27,-0.28 0.55,-0.56 0.83,-0.85 2.23,-2.29 4.48,-4.57 6.73,-6.84 0.16,-0.15 0.16,-0.15 0.94,-0.94 3.39,-3.41 6.84,-6.71 10.48,-9.85 4.1,-3.54 8,-7.26 11.83,-11.08 l 1.04,-1.04 C 331.59,828.6 346.77,804 351,776 c 0.03,-0.2 0.03,-0.2 0.21,-1.24 C 354.78,752.34 348.58,728.23 337,709 l -3,-4 z"
40
+ style="fill:#908030;fill-rule:evenodd;fill-opacity:1"
41
+ id="path1-8" />
42
+ <path
43
+ d="m 278.25,4.75 c -0.54,0.04 -1.07,0.09 -1.62,0.13 -32.98,2.74 -65.41,11.83 -94.6,27.56 -1.09,0.59 -2.18,1.17 -3.27,1.76 -9.58,5.13 -18.92,10.46 -27.76,16.8 -0.33,0.24 -0.67,0.48 -1.01,0.72 -19.41,13.9 -37.87,28.97 -55.51,45.05 -1.41,1.28 -2.83,2.53 -4.26,3.77 -4.6,3.96 -9,8.08 -13.27,12.38 -1.01,1.02 -2.02,2.02 -3.04,3.03 -4.51,4.51 -8.78,9.18 -12.91,14.05 -0.26,0.31 -0.52,0.62 -0.79,0.94 C 51.53,141.19 43.49,151.85 36,163 c -0.36,0.54 -0.73,1.08 -1.1,1.64 C 15.55,193.82 -1.65,230.99 5.25,266.8 9.67,288.42 20.73,307.65 34,325 c 0.34,0.45 0.68,0.91 1.04,1.37 4.36,5.74 9.12,11.09 14.03,16.37 2.45,2.63 4.83,5.29 7.12,8.07 2.24,2.69 4.71,4.97 7.38,7.23 2.11,1.79 4.14,3.62 6.13,5.54 13.36,12.86 28.46,23.82 44.3,33.42 0.58,0.35 1.15,0.7 1.73,1.05 5.19,3.16 10.41,6.27 15.66,9.35 28.21,16.59 53.61,34.5 62.54,67.99 1.54,6.13 2.75,12.29 3.07,18.61 0.03,0.46 0.05,0.93 0.08,1.4 0.68,13.34 -1.85,26.5 -4.08,39.6 1.85,-0.43 2.8,-0.76 4.04,-2.24 0.31,-0.52 0.62,-1.04 0.94,-1.58 0.17,-0.28 0.35,-0.57 0.53,-0.87 0.57,-0.95 1.12,-1.91 1.68,-2.87 0.39,-0.68 0.79,-1.35 1.19,-2.03 4.24,-7.28 8.03,-14.78 11.62,-22.41 0.14,-0.3 0.14,-0.3 0.85,-1.79 4.41,-9.46 7.6,-19.11 10.15,-29.21 0.08,-0.33 0.17,-0.66 0.26,-0.99 6.3,-24.54 5.51,-55.54 -7.26,-78.01 -4.49,-7.28 -11.39,-15.32 -19.92,-17.75 -6.41,-1.5 -12.87,-1.49 -19.36,-0.55 -0.6,0.09 -1.19,0.18 -1.8,0.26 -7.7,1.21 -15.19,3.27 -22.7,5.29 -3.09,0.83 -6.18,1.65 -9.27,2.46 -1.06,0.28 -2.12,0.57 -3.18,0.86 -10.28,2.74 -20.91,4.34 -30.77,-0.57 -3.76,-2.39 -4.96,-5.81 -6,-10 -0.28,-2.27 -0.36,-4.53 -0.44,-6.81 -0.4,-7.22 -1.09,-13.77 -6.18,-19.19 -1.931839,-2.97898 -2.97,-5.84 -3.21,-9.89 -0.561751,-9.12845 -0.405349,-4.8236 -3.1,-23.27 -0.12,-3.08 -1.22,-5.4 -3.07,-7.84 -2.19,-1.66 -4.54,-2.86 -7,-4.06 -7.85,-3.87 -7.85,-3.87 -9.62,-7.53 -0.89,-3.3 -0.51,-6.41 1.16,-9.37 1.41,-2.15 3,-4.12 4.64,-6.09 1.51,-1.84 2.98,-3.71 4.44,-5.59 0.3,-0.37 0.59,-0.74 0.89,-1.12 8.66,-11.08 15.69,-23.33 15.68,-37.68 v -1.85 c -0.05,-4.27 -0.5,-8.37 -1.19,-12.59 -4.35,-27.27 3.54,-56.47 19.58,-78.85 2.05,-2.82 4.2,-5.57 6.42,-8.27 0.03,0.22 0.03,0.22 0.2,1.32 4.32,28.44 16.11,51.38 36.06,71.91 0.76,0.79 1.51,1.58 2.27,2.38 7.9,8.31 16.26,15.56 25.47,22.39 0.37,0.27 0.74,0.55 1.11,0.83 4.85,3.6 9.71,7.18 14.58,10.74 13.79,10.11 26.89,20.68 39.31,32.43 0.37,0.34 0.73,0.69 1.11,1.04 5.52,5.18 10.26,10.99 14.89,16.96 0.39,0.5 0.78,1.01 1.18,1.53 22.65,29.58 36.75,65.17 38.82,102.47 0.02,0.28 0.02,0.28 0.1,1.68 2.35,45.29 -22.98,81.38 -49.23,115.57 -4.25,5.54 -8.44,11.12 -12.61,16.72 -1.3,1.74 -2.6,3.49 -3.91,5.22 -33.66,44.8 -33.66,44.8 -42.35,70.81 -0.18,0.52 -0.35,1.04 -0.53,1.57 -3.43,10.53 -2.77,21.5 -2.47,32.43 l 1,1 h 155 c 2.23,-6.7 2.23,-6.7 2.79,-10.04 0.13,-0.74 0.27,-1.48 0.4,-2.23 0.07,-0.38 0.14,-0.76 0.21,-1.16 4.16,-22.24 14.72,-40.98 28.6,-58.57 0.68,-0.87 1.35,-1.74 2.03,-2.61 4.41,-5.64 9.11,-10.97 13.94,-16.25 2.37,-2.61 4.68,-5.27 6.99,-7.94 0.97,-1.12 1.95,-2.22 2.93,-3.33 C 422.55,515.4 441.2,488.4 451,457 c 0.17,-0.54 0.34,-1.07 0.51,-1.62 8.54,-27.92 4.9,-58.56 -8.05,-84.44 -0.46,-0.94 -0.91,-1.88 -1.36,-2.82 -11.64,-24.3 -31.52,-46.39 -52.62,-62.94 -1.27,-1.01 -2.52,-2.05 -3.76,-3.1 -16.39,-13.88 -35.02,-25.85 -53.67,-36.48 -1.17,-0.67 -2.35,-1.35 -3.53,-2.03 -11.51,-6.64 -23.14,-13.02 -34.95,-19.11 -7.64,-3.95 -15.19,-8.03 -22.73,-12.18 -4.03,-2.21 -8.06,-4.42 -12.15,-6.51 C 234.47,213.38 212.24,198.14 192,180 c -0.36,-0.32 -0.73,-0.64 -1.1,-0.98 -12.8,-11.46 -22.69,-27.28 -30.34,-42.52 -0.17,-0.33 -0.33,-0.67 -0.51,-1.01 -2.03,-4.06 -3.75,-8.13 -5.05,-12.49 0.26,0.37 0.51,0.73 0.78,1.11 27.73,39.61 64.59,62.16 136.67,91.89 32.87,13.2 65.64,27.22 94.55,48 0.34,0.25 0.69,0.49 1.04,0.74 18.15,13 35.43,27.37 48.96,45.26 0.22,0.29 0.45,0.59 0.67,0.89 25.44,33.61 39.25,71.89 33.72,114.45 -0.57,3.94 -1.42,7.8 -2.39,11.66 -0.1,0.39 -0.19,0.78 -0.29,1.18 -3.25,12.86 -7.87,24.93 -13.71,36.82 -0.16,0.32 -0.32,0.65 -0.48,0.98 -11.57,23.61 -27.36,44.24 -44.52,64.02 5.28,0 9.9,-6.86 13.59,-10.54 l 4.92,-4.89 c 13.59,-13.51 13.59,-13.51 18.83,-19.78 1.05,-1.25 2.14,-2.48 3.22,-3.71 33.2,-38.29 57.35,-83.87 82.75,-188.96 0.04,-0.3 0.04,-0.3 0.25,-1.85 1.92,-14.6 2.68,-29.12 2.69,-43.83 v -1.21 C 536.28,216.18 525.54,167.08 481,96 474.35027,82.502457 457.163,66.047388 442.4,54.389598 429.97756,44.580059 423.21007,38.671449 403.0602,29.91592 382.91033,21.160391 355.82,11.19 331,7 330.62,6.94 330.24,6.87 329.84,6.8 312.74,3.95 295.51,3.36 278.25,4.75 Z M 190,655 c 1.15,10.37 1.15,10.37 2.16,15.5 0.1,0.56 0.21,1.12 0.32,1.69 0.22,1.16 0.45,2.33 0.68,3.49 l 1.02,5.34 c 0.22,1.14 0.44,2.28 0.66,3.41 0.1,0.53 0.21,1.06 0.31,1.61 0.05,0.25 0.05,0.25 0.29,1.49 0.09,0.43 0.17,0.86 0.26,1.3 C 196,690 196,690 197,692 h 137 c 2.56,-12.29 5.09,-24.59 7,-37 z"
44
+ style="fill:#205080;fill-opacity:1;fill-rule:evenodd"
45
+ id="path1"
46
+ sodipodi:nodetypes="ccccccccccccccccccccccccccccccsccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccszcccccccccccccccc" />
47
+ </svg>
@@ -1 +0,0 @@
1
- :root{--bg:#f8fafc;--panel:#fff;--border:#e2e8f0;--text:#1e293b;--muted:#64748b;--accent:#2563eb;--accent-soft:#eff6ff;--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:baseline;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{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{background:#fff;border:0;flex:1;width:100%}.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}.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}