@xiaobuyu/nodesign 0.0.1

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.
Files changed (372) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +140 -0
  3. package/bin/nodesign.js +176 -0
  4. package/package.json +104 -0
  5. package/server/api/README.md +45 -0
  6. package/server/api/_guard.js +75 -0
  7. package/server/api/admin.js +187 -0
  8. package/server/api/assets/docx-page.js +103 -0
  9. package/server/api/assets/helpers.js +22 -0
  10. package/server/api/assets/notes.js +144 -0
  11. package/server/api/assets.js +896 -0
  12. package/server/api/board.js +57 -0
  13. package/server/api/browse.js +138 -0
  14. package/server/api/canvas.js +444 -0
  15. package/server/api/chatai.js +324 -0
  16. package/server/api/exports/build-standalone.js +980 -0
  17. package/server/api/exports/cards.js +118 -0
  18. package/server/api/exports/docx-pdf.js +46 -0
  19. package/server/api/exports/export-page.js +142 -0
  20. package/server/api/exports/handoff.js +147 -0
  21. package/server/api/exports.js +690 -0
  22. package/server/api/instruction.js +75 -0
  23. package/server/api/local.js +115 -0
  24. package/server/api/me.js +116 -0
  25. package/server/api/memory.js +185 -0
  26. package/server/api/pending-changes.js +366 -0
  27. package/server/api/plugins.js +164 -0
  28. package/server/api/projects.js +220 -0
  29. package/server/api/publish.js +64 -0
  30. package/server/api/recent.js +73 -0
  31. package/server/api/sessions.js +589 -0
  32. package/server/api/skills.js +77 -0
  33. package/server/api/standalone-fit.js +168 -0
  34. package/server/api/turn-compose.js +180 -0
  35. package/server/api/turn-inflight.js +44 -0
  36. package/server/api/turn.js +669 -0
  37. package/server/auth/README.md +17 -0
  38. package/server/auth/middleware.js +138 -0
  39. package/server/auth/origin-guard.js +71 -0
  40. package/server/auth/session.js +108 -0
  41. package/server/auth/tier.js +113 -0
  42. package/server/auth/users-store.js +348 -0
  43. package/server/edit/README.md +29 -0
  44. package/server/engine/README.md +113 -0
  45. package/server/engine/agent/agent-shared.js +550 -0
  46. package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -0
  47. package/server/engine/agent/auto-mode-rules.js +78 -0
  48. package/server/engine/agent/context.js +418 -0
  49. package/server/engine/agent/events.js +464 -0
  50. package/server/engine/agent/hooks/canvas-validate.js +199 -0
  51. package/server/engine/agent/hooks/failure.js +120 -0
  52. package/server/engine/agent/hooks/file-events.js +75 -0
  53. package/server/engine/agent/hooks/lifecycle.js +233 -0
  54. package/server/engine/agent/hooks/post-canvas-focus.js +80 -0
  55. package/server/engine/agent/hooks/post-guidance.js +206 -0
  56. package/server/engine/agent/hooks/post-subagent-report.js +104 -0
  57. package/server/engine/agent/hooks/post-trim.js +58 -0
  58. package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -0
  59. package/server/engine/agent/hooks/pre-defaults.js +84 -0
  60. package/server/engine/agent/hooks/pre-injectors.js +277 -0
  61. package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -0
  62. package/server/engine/agent/hooks/pre-starter-files.js +61 -0
  63. package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -0
  64. package/server/engine/agent/hooks/site-validate.js +94 -0
  65. package/server/engine/agent/hooks/tool-prompts.js +31 -0
  66. package/server/engine/agent/hooks/turn-state-memory.js +56 -0
  67. package/server/engine/agent/hooks/user-prompt-submit.js +215 -0
  68. package/server/engine/agent/hooks.js +354 -0
  69. package/server/engine/agent/init-contract.js +69 -0
  70. package/server/engine/agent/isolation.js +159 -0
  71. package/server/engine/agent/model-context.js +359 -0
  72. package/server/engine/agent/model-table.js +292 -0
  73. package/server/engine/agent/plugin-loader.js +266 -0
  74. package/server/engine/agent/prompts/nodesign-prelude.md +336 -0
  75. package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -0
  76. package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -0
  77. package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -0
  78. package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -0
  79. package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -0
  80. package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -0
  81. package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -0
  82. package/server/engine/agent/prompts/tools/site-reference.md +106 -0
  83. package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -0
  84. package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -0
  85. package/server/engine/agent/session-loop.js +1024 -0
  86. package/server/engine/agent/session-model.js +164 -0
  87. package/server/engine/agent/skill.js +204 -0
  88. package/server/engine/agent/system-prompts.js +86 -0
  89. package/server/engine/agent/task-events.js +78 -0
  90. package/server/engine/agents/ds-extractor.md +132 -0
  91. package/server/engine/agents/explorer.md +190 -0
  92. package/server/engine/agents/index.js +230 -0
  93. package/server/engine/agents/schemas/design-system.json +156 -0
  94. package/server/engine/agents/schemas/tweak-schema.json +113 -0
  95. package/server/engine/agents/tweak-proposer.md +127 -0
  96. package/server/engine/agents/vision-checker.md +254 -0
  97. package/server/engine/browse/capture.js +422 -0
  98. package/server/engine/browse/card.js +137 -0
  99. package/server/engine/browse/handover.js +59 -0
  100. package/server/engine/browse/page-digest.js +119 -0
  101. package/server/engine/browse/refs.js +196 -0
  102. package/server/engine/browse/registry.js +307 -0
  103. package/server/engine/browse/screencast.js +170 -0
  104. package/server/engine/browse/state.js +121 -0
  105. package/server/engine/chatai/chat-log.js +79 -0
  106. package/server/engine/chatai/index.js +193 -0
  107. package/server/engine/chatai/openai-compat.js +152 -0
  108. package/server/engine/chatai/orchestrate.js +309 -0
  109. package/server/engine/chatai/perform.js +40 -0
  110. package/server/engine/chatai/summarize.js +79 -0
  111. package/server/engine/mcp/capability-gate.js +52 -0
  112. package/server/engine/mcp/index.js +339 -0
  113. package/server/engine/mcp/param-sanitizer.js +142 -0
  114. package/server/engine/mcp/tools/arrange-on-board.js +124 -0
  115. package/server/engine/mcp/tools/artifact-session.js +278 -0
  116. package/server/engine/mcp/tools/browse-computer.js +421 -0
  117. package/server/engine/mcp/tools/browse-find-batch.js +183 -0
  118. package/server/engine/mcp/tools/browse-screenshot.js +157 -0
  119. package/server/engine/mcp/tools/browse.js +480 -0
  120. package/server/engine/mcp/tools/build-docx.js +101 -0
  121. package/server/engine/mcp/tools/clear-pending-changes.js +99 -0
  122. package/server/engine/mcp/tools/create-on-board.js +155 -0
  123. package/server/engine/mcp/tools/crystallize-skill.js +168 -0
  124. package/server/engine/mcp/tools/deliver-files.js +0 -0
  125. package/server/engine/mcp/tools/explain-style.js +237 -0
  126. package/server/engine/mcp/tools/export-handoff.js +133 -0
  127. package/server/engine/mcp/tools/expose-tweaks.js +149 -0
  128. package/server/engine/mcp/tools/generate-image.js +842 -0
  129. package/server/engine/mcp/tools/get-computed-styles.js +164 -0
  130. package/server/engine/mcp/tools/get-pending-changes.js +205 -0
  131. package/server/engine/mcp/tools/h3box-ssh.js +84 -0
  132. package/server/engine/mcp/tools/helpers/acquire-page.js +82 -0
  133. package/server/engine/mcp/tools/helpers/motion-lab.js +554 -0
  134. package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -0
  135. package/server/engine/mcp/tools/helpers/perception-page.js +215 -0
  136. package/server/engine/mcp/tools/helpers/reference-download.js +103 -0
  137. package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -0
  138. package/server/engine/mcp/tools/helpers/rembg.js +273 -0
  139. package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -0
  140. package/server/engine/mcp/tools/highlight.js +68 -0
  141. package/server/engine/mcp/tools/list-pages.js +222 -0
  142. package/server/engine/mcp/tools/lookup-tags.js +59 -0
  143. package/server/engine/mcp/tools/navigate-to-page.js +60 -0
  144. package/server/engine/mcp/tools/organize-board.js +64 -0
  145. package/server/engine/mcp/tools/paint-still.js +434 -0
  146. package/server/engine/mcp/tools/pin-to-board.js +130 -0
  147. package/server/engine/mcp/tools/preview-deck.js +130 -0
  148. package/server/engine/mcp/tools/profile-scroll.js +305 -0
  149. package/server/engine/mcp/tools/publish-site.js +102 -0
  150. package/server/engine/mcp/tools/query-elements.js +195 -0
  151. package/server/engine/mcp/tools/read-board.js +103 -0
  152. package/server/engine/mcp/tools/read-document.js +93 -0
  153. package/server/engine/mcp/tools/read-page.js +264 -0
  154. package/server/engine/mcp/tools/read-tavern-json.js +143 -0
  155. package/server/engine/mcp/tools/record-decision.js +140 -0
  156. package/server/engine/mcp/tools/relate-on-board.js +110 -0
  157. package/server/engine/mcp/tools/remove-background.js +391 -0
  158. package/server/engine/mcp/tools/report-issue.js +115 -0
  159. package/server/engine/mcp/tools/roll-film.js +266 -0
  160. package/server/engine/mcp/tools/screenshot-docx.js +138 -0
  161. package/server/engine/mcp/tools/screenshot-url.js +195 -0
  162. package/server/engine/mcp/tools/screenshot.js +583 -0
  163. package/server/engine/mcp/tools/tier-gate.js +96 -0
  164. package/server/engine/mcp/tools/trace-motion.js +215 -0
  165. package/server/engine/mcp/tools/web-search.js +548 -0
  166. package/server/engine/motion/inventory.js +349 -0
  167. package/server/engine/perception/session.js +235 -0
  168. package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -0
  169. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -0
  170. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -0
  171. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -0
  172. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -0
  173. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -0
  174. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -0
  175. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -0
  176. package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -0
  177. package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -0
  178. package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -0
  179. package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -0
  180. package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -0
  181. package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -0
  182. package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -0
  183. package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -0
  184. package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -0
  185. package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -0
  186. package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -0
  187. package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -0
  188. package/server/engine/runs/active-runs.js +698 -0
  189. package/server/engine/runs/live-turn.js +252 -0
  190. package/server/engine/runs/store.js +364 -0
  191. package/server/engine/runs/turn-relay.js +217 -0
  192. package/server/engine/runtime/workspace.js +164 -0
  193. package/server/index.js +235 -0
  194. package/server/lib/artifact-file-path.js +80 -0
  195. package/server/lib/artifact-target.js +284 -0
  196. package/server/lib/asset-refs.js +239 -0
  197. package/server/lib/async-queue.js +104 -0
  198. package/server/lib/auto-relations.js +130 -0
  199. package/server/lib/binding-types.js +60 -0
  200. package/server/lib/board-kind-sizes.js +62 -0
  201. package/server/lib/board-relations.js +133 -0
  202. package/server/lib/browse-proxy.js +180 -0
  203. package/server/lib/canvas-id.js +36 -0
  204. package/server/lib/cover.js +227 -0
  205. package/server/lib/danbooru-tags.js +291 -0
  206. package/server/lib/doc-extract.js +156 -0
  207. package/server/lib/docx/build-from-source.js +260 -0
  208. package/server/lib/docx/build.js +490 -0
  209. package/server/lib/docx/dump-styles.js +287 -0
  210. package/server/lib/docx/fonts/nodesign-cjk.conf +164 -0
  211. package/server/lib/docx/merge-runs.js +129 -0
  212. package/server/lib/docx/numbering.js +218 -0
  213. package/server/lib/docx/order.js +152 -0
  214. package/server/lib/docx/rawzip.js +172 -0
  215. package/server/lib/docx/render.js +92 -0
  216. package/server/lib/docx/text-lint.js +225 -0
  217. package/server/lib/docx/tokens.js +335 -0
  218. package/server/lib/docx/units.js +29 -0
  219. package/server/lib/docx/xml.js +291 -0
  220. package/server/lib/docx-pages.js +187 -0
  221. package/server/lib/export-collect.js +289 -0
  222. package/server/lib/export-package.js +210 -0
  223. package/server/lib/html-srcset.js +145 -0
  224. package/server/lib/image-variant.js +460 -0
  225. package/server/lib/ingress/forward-openai-chat.js +305 -0
  226. package/server/lib/ingress/openai-chat.js +491 -0
  227. package/server/lib/ingress/session-notice.js +66 -0
  228. package/server/lib/ingress/session-routes.js +68 -0
  229. package/server/lib/ingress/slot-probe.js +130 -0
  230. package/server/lib/ingress/upstream-billing.js +52 -0
  231. package/server/lib/ingress/upstream-fail-streak.js +72 -0
  232. package/server/lib/ingress/upstream-truncation.js +48 -0
  233. package/server/lib/issues-store.js +182 -0
  234. package/server/lib/kinds/deck.js +72 -0
  235. package/server/lib/kinds/docx.js +283 -0
  236. package/server/lib/kinds/file-kinds.js +113 -0
  237. package/server/lib/kinds/index.js +202 -0
  238. package/server/lib/kinds/site.js +161 -0
  239. package/server/lib/model-ingress.js +543 -0
  240. package/server/lib/moderation.js +255 -0
  241. package/server/lib/notice-store.js +103 -0
  242. package/server/lib/plugin-install.js +104 -0
  243. package/server/lib/plugin-validator.js +721 -0
  244. package/server/lib/publish-store.js +115 -0
  245. package/server/lib/quick-summary.js +52 -0
  246. package/server/lib/quota.js +270 -0
  247. package/server/lib/rate-window.js +29 -0
  248. package/server/lib/reference-assets.js +92 -0
  249. package/server/lib/region-shot.js +135 -0
  250. package/server/lib/safe-path.js +73 -0
  251. package/server/lib/sdk-session.js +33 -0
  252. package/server/lib/showcase-store.js +92 -0
  253. package/server/lib/site-publish.js +465 -0
  254. package/server/lib/ssrf-guard.js +432 -0
  255. package/server/lib/task-scan.js +158 -0
  256. package/server/lib/tavern-json.js +154 -0
  257. package/server/lib/video-variant.js +237 -0
  258. package/server/lib/workspace-path.js +33 -0
  259. package/server/ops/fix-sdk-musl.mjs +40 -0
  260. package/server/ops/install-macos-fonts.sh +114 -0
  261. package/server/ops/macos-fonts.conf +336 -0
  262. package/server/ops/sandbox-shim/bwrap +57 -0
  263. package/server/projects/assets-summary.js +119 -0
  264. package/server/projects/auto-name.js +53 -0
  265. package/server/projects/board-store.js +640 -0
  266. package/server/projects/move-entry.js +103 -0
  267. package/server/projects/store.js +270 -0
  268. package/server/projects/ui-config.js +54 -0
  269. package/server/projects/workspace-templates.js +53 -0
  270. package/server/projects/workspace.js +1025 -0
  271. package/server/runtime/capabilities.js +153 -0
  272. package/server/runtime/local-config.js +177 -0
  273. package/server/runtime/local-env.js +88 -0
  274. package/server/runtime/platform.js +342 -0
  275. package/server/runtime/profile.js +72 -0
  276. package/server/services/rembg-launcher.js +313 -0
  277. package/server/services/rembg-service.py +334 -0
  278. package/server/shared/README.md +25 -0
  279. package/server/shared/deck.js +54 -0
  280. package/server/shared/time.js +49 -0
  281. package/server/style-pipeline/README.md +53 -0
  282. package/server/ws/broker.js +36 -0
  283. package/server/ws/browse-channel.js +177 -0
  284. package/server/ws/index.js +386 -0
  285. package/web/README.md +89 -0
  286. package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +11 -0
  287. package/web/dist/assets/DeckWindow-4G0xX7P3.js +16 -0
  288. package/web/dist/assets/DocxWindow-BXQsoPkh.js +16 -0
  289. package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  290. package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  291. package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  292. package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  293. package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  294. package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  295. package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  296. package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  297. package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  298. package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  299. package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  300. package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  301. package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  302. package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  303. package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  304. package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  305. package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  306. package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  307. package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  308. package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  309. package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  310. package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  311. package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  312. package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  313. package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  314. package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  315. package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  316. package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  317. package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  318. package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  319. package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  320. package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  321. package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  322. package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  323. package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  324. package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  325. package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  326. package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  327. package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  328. package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  329. package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  330. package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  331. package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  332. package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  333. package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  334. package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  335. package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  336. package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  337. package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  338. package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  339. package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  340. package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  341. package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  342. package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  343. package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  344. package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  345. package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  346. package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  347. package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  348. package/web/dist/assets/SiteWindow-D6eS-2gM.js +18 -0
  349. package/web/dist/assets/again-D8tfgKKO.webp +0 -0
  350. package/web/dist/assets/caveat-nd-CP6HlsNg.woff2 +0 -0
  351. package/web/dist/assets/clock-xJhvfYpS.webp +0 -0
  352. package/web/dist/assets/film-cut-BBKMwH7q.webp +0 -0
  353. package/web/dist/assets/film-still-BZ_DL9Tt.webp +0 -0
  354. package/web/dist/assets/index-C0KUf6Wd.css +1 -0
  355. package/web/dist/assets/index-Cbl3ATvp.js +2125 -0
  356. package/web/dist/assets/ink-desk-BRbxeDsR.webp +0 -0
  357. package/web/dist/assets/ink-night-Bz4BT3XH.webp +0 -0
  358. package/web/dist/assets/ink-portrait-C0DM4V8Q.webp +0 -0
  359. package/web/dist/assets/longcang-regular-C0uvTuKa.woff2 +0 -0
  360. package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
  361. package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
  362. package/web/dist/assets/pending-edit-apply-DTjo2geW.js +31 -0
  363. package/web/dist/assets/question-8H4HSqxh.webp +0 -0
  364. package/web/dist/assets/reject-BaPJqElj.webp +0 -0
  365. package/web/dist/assets/rotate-cw-LMT3x1Zj.js +11 -0
  366. package/web/dist/assets/rp-portrait-CbrGpew2.webp +0 -0
  367. package/web/dist/assets/rp-street-C4jVKjxx.webp +0 -0
  368. package/web/dist/assets/square-dashed-mouse-pointer-CBoAkfhi.js +11 -0
  369. package/web/dist/assets/tangle-DIxHVNuT.webp +0 -0
  370. package/web/dist/assets/thisone-BylZKglV.webp +0 -0
  371. package/web/dist/assets/thumb-DO1wfK1I.webp +0 -0
  372. package/web/dist/index.html +18 -0
@@ -0,0 +1,386 @@
1
+ /**
2
+ * server/ws/index.js — WebSocket 升级入口
3
+ *
4
+ * URL 模式:/ws/projects/:pid
5
+ *
6
+ * 流程:
7
+ * 1. http server 'upgrade' 事件触发
8
+ * 2. parse URL 取 pid
9
+ * 3. validateProjectId + getProject 校验存在
10
+ * 4. wss.handleUpgrade → 拿到 ws 对象
11
+ * 5. 订阅该 project 的 EventBus,事件 JSON.stringify 推 ws.send
12
+ * 6. ping/pong 30s 心跳
13
+ * 7. ws close → unsubscribe + clearInterval
14
+ */
15
+
16
+ import { WebSocketServer } from 'ws';
17
+ import { createBrowseWS } from './browse-channel.js';
18
+ import { URL } from 'url';
19
+ import { getSessionMessages } from '@anthropic-ai/claude-agent-sdk';
20
+ import { validateProjectId, getProject } from '../projects/store.js';
21
+ import { getSessionWorkspace, validateSessionId } from '../projects/workspace.js';
22
+ import { withConfigDir } from '../lib/sdk-session.js';
23
+ import { platform } from '../runtime/platform.js';
24
+ import { getProjectBus } from './broker.js';
25
+ import { requestUser } from '../auth/session.js';
26
+ import { originAllowed } from '../auth/origin-guard.js';
27
+ import { userOwnsProject } from '../api/_guard.js';
28
+ import {
29
+ getCurrentTurnRunId,
30
+ hasActiveQuerySession,
31
+ closeQuerySession,
32
+ markSessionActivity,
33
+ } from '../engine/runs/active-runs.js';
34
+ import { getLiveTurnSnapshot } from '../engine/runs/live-turn.js';
35
+
36
+ const HEARTBEAT_INTERVAL_MS = 30_000;
37
+ const HYDRATE_CHUNK_SIZE = 50;
38
+ const GLOBAL_CLAUDE_CONFIG_DIR = platform.claudeConfigDir;
39
+
40
+ /**
41
+ * WS 全部断开后再 N ms 仍无重连 → closeQuerySession 让 SDK subprocess 退出,
42
+ * 防止用户关 tab / 刷新后服务器孤儿 SDK process 永久占内存(每个 ~250MB RSS)。
43
+ *
44
+ * 默认 60s 给前端 ws-client.js exponential backoff(1s→2s→...→30s)足够重连窗口。
45
+ * env NODESIGN_WS_GRACE_MS 可调(生产可拉到 5min 给移动端弱网)。
46
+ */
47
+ const WS_GRACE_MS = Number(process.env.NODESIGN_WS_GRACE_MS) || 60_000;
48
+
49
+ /**
50
+ * sid → { count: 当前活跃 WS 连接数, graceTimer: 0 引用时启动的关闭定时器 }
51
+ *
52
+ * 每条带 sid 的 WS 连接 ref++,close 时 ref--;归零启 grace timer,N ms 内有新 WS
53
+ * 进同 sid 立即清 timer 续命;timer 到期再确认 0 ref 后 closeQuerySession。
54
+ *
55
+ * /work 路径(无 sid)的 WS 不进 sessionRefs(无 session 可绑)。
56
+ */
57
+ const sessionRefs = new Map();
58
+
59
+ function refSession(sid) {
60
+ if (!sid) return;
61
+ let entry = sessionRefs.get(sid);
62
+ if (!entry) {
63
+ entry = { count: 0, graceTimer: null };
64
+ sessionRefs.set(sid, entry);
65
+ }
66
+ entry.count += 1;
67
+ if (entry.graceTimer) {
68
+ clearTimeout(entry.graceTimer);
69
+ entry.graceTimer = null;
70
+ }
71
+ // WS 连上算一次活跃信号(idle scan 不会立即把刚连的 session 当僵尸关掉)
72
+ markSessionActivity(sid);
73
+ }
74
+
75
+ function unrefSession(sid) {
76
+ if (!sid) return;
77
+ const entry = sessionRefs.get(sid);
78
+ if (!entry) return;
79
+ entry.count = Math.max(0, entry.count - 1);
80
+ if (entry.count > 0) return;
81
+ // 0 ref → 启 grace timer。若 grace 期内有新 WS 进同 sid,refSession 会清掉 timer
82
+ if (entry.graceTimer) clearTimeout(entry.graceTimer);
83
+ const onGraceExpired = () => {
84
+ const cur = sessionRefs.get(sid);
85
+ if (!cur || cur.count > 0) return; // 期间有新连上,不关
86
+ // 进行中的 turn 不杀(2026-07-27):用户关 tab / 切走时后端应该把活干完,
87
+ // 前端重连后靠 hydrate + live_turn 快照接回状态。这正是"前端丢失后端工作
88
+ // 状态"要保住的那半 —— 老逻辑 60s 就 cancel 在跑的 turn,是真丢工作。
89
+ // 续一个 grace 周期再查;turn 结束后仍无订阅者才真正关(idle SDK 进程回收)。
90
+ if (getCurrentTurnRunId(sid)) {
91
+ console.info(`[ws] grace expired for sid=${sid.slice(0, 8)} but turn in flight — deferring close`);
92
+ cur.graceTimer = setTimeout(onGraceExpired, WS_GRACE_MS);
93
+ cur.graceTimer.unref?.();
94
+ return;
95
+ }
96
+ sessionRefs.delete(sid);
97
+ if (hasActiveQuerySession(sid)) {
98
+ console.info(`[ws] grace expired for sid=${sid.slice(0, 8)}, closing session (no_active_subscriber)`);
99
+ closeQuerySession(sid, 'no_active_subscriber');
100
+ }
101
+ };
102
+ entry.graceTimer = setTimeout(onGraceExpired, WS_GRACE_MS);
103
+ entry.graceTimer.unref?.();
104
+ }
105
+
106
+ export function setupWS(httpServer) {
107
+ // 浏览器画面/输入的专用通道(2026-08-18),独立 WebSocketServer
108
+ const browseWS = createBrowseWS();
109
+ // perMessageDeflate:弱网下 hydrate chunk + ring-buffer replay 帧成本是 raw size
110
+ // 量级(>2MB session 一次首屏 hydrate 估 250KB+ raw)。permessage-deflate 让
111
+ // JSON 文本压到 5-10x。threshold 1KB 跳过小帧(控制帧无收益反增 CPU)。
112
+ // env NODESIGN_WS_DISABLE_DEFLATE=1 应急关闭(CPU 紧的部署环境)。
113
+ const wss = new WebSocketServer({
114
+ noServer: true,
115
+ perMessageDeflate: process.env.NODESIGN_WS_DISABLE_DEFLATE === '1'
116
+ ? false
117
+ : { threshold: 1024 },
118
+ });
119
+
120
+ httpServer.on('upgrade', (req, socket, head) => {
121
+ // ⛔ CSRF 闸(2026-08-18):cookie 是 SameSite=Lax,而已发布站点与应用主机
122
+ // 同 eTLD+1 = 同站,Lax 照发(真跑验过,见 auth/origin-guard.js 文件头)。
123
+ // 这里**不学下面那个 4401 的握手后再关**:那套是为了让我们自己的前端看得见
124
+ // 原因好停止重连;外站页面不需要体面的错误,403 直接掐在升级前最省。
125
+ if (!originAllowed(req)) {
126
+ socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
127
+ return socket.destroy();
128
+ }
129
+
130
+ // 登录墙(2026-07-30 多用户):解析身份而非布尔。cookie 无效不再写 HTTP 401
131
+ // 拒 upgrade —— 浏览器拿不到 upgrade 阶段的状态码,只见 close 1006,前端
132
+ // ws-client 会无限指数退避重连。改为完成握手后 close(4401),前端把它列为
133
+ // fatal code → 停止重连并回登录页。
134
+ const user = requestUser(req);
135
+
136
+ let url;
137
+ try {
138
+ url = new URL(req.url || '/', 'http://x');
139
+ } catch {
140
+ return socket.destroy();
141
+ }
142
+
143
+ // ⚠️ 浏览器画面通道的分支**必须排在下面那个 404 之前** —— 那句 404 是兜底,
144
+ // 不匹配 `/ws/projects/:pid` 的路径会被它直接毙掉。
145
+ // 为什么另开一条而不是复用这条:这条是纯下行 + EventBus 广播 + 2000 条重放
146
+ // 缓冲,高频画面帧灌进去会冲爆重放缓冲、混进 hydrate 回放,而且 JSON+base64
147
+ // 帧会被 perMessageDeflate 白压一遍。详见 ws/browse-channel.js 文件头。
148
+ {
149
+ const bm = browseWS.matches(url.pathname);
150
+ if (bm) return browseWS.accept(req, socket, head, bm);
151
+ }
152
+
153
+ const m = url.pathname.match(/^\/ws\/projects\/([^/]+)$/);
154
+ if (!m) {
155
+ socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
156
+ return socket.destroy();
157
+ }
158
+
159
+ const pid = decodeURIComponent(m[1]);
160
+ try {
161
+ validateProjectId(pid);
162
+ } catch {
163
+ socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
164
+ return socket.destroy();
165
+ }
166
+
167
+ // 身份或归属不对 → 握手后 4401 关闭(同一个 code:外人不该分得清
168
+ // "没登录"和"不是你的项目")
169
+ const project = getProject(pid);
170
+ if (!user || !userOwnsProject(user, project)) {
171
+ wss.handleUpgrade(req, socket, head, (ws) => {
172
+ ws.close(4401, 'unauthorized');
173
+ });
174
+ return;
175
+ }
176
+
177
+ // ?since=N — 客户端最后看到的 EventBus seq;server 通过 buffer 回放 (since, _seq] 段
178
+ // 第一次连不带 since → since=0 → 不 replay 直接 live。
179
+ const sinceRaw = url.searchParams.get('since');
180
+ const since = sinceRaw != null ? Math.max(0, parseInt(sinceRaw, 10) || 0) : 0;
181
+
182
+ // Phase A.4:?sid=<sessionId> — 客户端当前在哪个 session 上,server 在 first connect /
183
+ // gap 时推 ws.hydrate.* 帧补完整 messages。无 sid 时(/work 路径)跳过 hydrate
184
+ // 直接 replay+live。
185
+ let sid = url.searchParams.get('sid');
186
+ if (sid) {
187
+ try { validateSessionId(sid); }
188
+ catch { sid = null; }
189
+ }
190
+
191
+ wss.handleUpgrade(req, socket, head, (ws) => {
192
+ handleProjectWS(ws, pid, since, sid);
193
+ });
194
+ });
195
+
196
+ return wss;
197
+ }
198
+
199
+ /**
200
+ * 读 jsonl 历史(异步,几百 ms)。发帧是另一个纯同步函数 —— 读完到发出之间不能
201
+ * 再有 await,否则"读到的历史"与"当时的 live turn 快照"会错位(见 handleProjectWS)。
202
+ */
203
+ async function loadHydrate(pid, sid) {
204
+ const sessionRoot = getSessionWorkspace(pid, sid);
205
+ return withConfigDir(GLOBAL_CLAUDE_CONFIG_DIR, () =>
206
+ getSessionMessages(sid, { dir: sessionRoot, includeSystemMessages: false }),
207
+ );
208
+ }
209
+
210
+ /**
211
+ * 本轮由快照权威:jsonl 里属于当前 turn 的 assistant 条目全部裁掉。
212
+ *
213
+ * SDK 按 block 逐条写 jsonl(thinking / text / tool_use 各一行),一个 turn 跑到
214
+ * 一半时前面几条已经落盘 —— 那些内容同时也在 live_turn 快照里(快照从 run.start
215
+ * 折叠整轮)。两边 id 体系不同(jsonl 是 `<uuid>:text:<n>`,快照是 `<runId>:mN`),
216
+ * 前端去不了重,正文就渲染两遍。这是用户报的"重复传"的根因,100% 复现。
217
+ *
218
+ * 只裁 assistant:user 条目(用户自己那句话)快照里没有,裁了就真丢了。掉队的
219
+ * tool_result 会因为找不到对应 tool_use 被 sessionMessagesToDisplay 自然跳过。
220
+ */
221
+ function dropInFlightTurn(messages, startedAt) {
222
+ if (!startedAt) return messages;
223
+ const cut = Date.parse(startedAt);
224
+ if (Number.isNaN(cut)) return messages;
225
+ return messages.filter(m => !(
226
+ m?.type === 'assistant' && m.timestamp && Date.parse(m.timestamp) >= cut
227
+ ));
228
+ }
229
+
230
+ /**
231
+ * Phase A.4:推 ws.hydrate.start/chunk/end 帧把 jsonl 历史 messages 同步给前端。
232
+ * 必须在 subscribeFromSeq 之前发,确保前端先 hydrate 后 apply replay/live events。
233
+ * 纯同步。
234
+ */
235
+ function sendHydrateFrames(ws, sid, messages, asOfSeq) {
236
+ if (ws.readyState !== ws.OPEN) return;
237
+ const total = messages.length;
238
+ ws.send(JSON.stringify({
239
+ type: 'ws.hydrate.start',
240
+ sessionId: sid,
241
+ total,
242
+ asOfSeq,
243
+ ts: new Date().toISOString(),
244
+ }));
245
+ for (let i = 0; i < total; i += HYDRATE_CHUNK_SIZE) {
246
+ if (ws.readyState !== ws.OPEN) return;
247
+ ws.send(JSON.stringify({
248
+ type: 'ws.hydrate.chunk',
249
+ sessionId: sid,
250
+ chunkIdx: Math.floor(i / HYDRATE_CHUNK_SIZE),
251
+ messages: messages.slice(i, i + HYDRATE_CHUNK_SIZE),
252
+ }));
253
+ }
254
+ if (ws.readyState !== ws.OPEN) return;
255
+ ws.send(JSON.stringify({ type: 'ws.hydrate.end', sessionId: sid, total }));
256
+ }
257
+
258
+ /** hydrate 读失败 fail-soft:前端收到后兜底走 HTTP Sessions.read */
259
+ function sendHydrateError(ws, sid, message) {
260
+ if (ws.readyState !== ws.OPEN) return;
261
+ try {
262
+ ws.send(JSON.stringify({
263
+ type: 'ws.hydrate.start', sessionId: sid, kind: 'error', error: message,
264
+ }));
265
+ } catch { /* ignore */ }
266
+ }
267
+
268
+ async function handleProjectWS(ws, pid, since = 0, sid = null) {
269
+ const bus = getProjectBus(pid);
270
+
271
+ // 诊断 log:用户重启服务器后报"前端收不到事件"——这条 log 让 pm2 logs 一眼看出
272
+ // WS 是不是真连上 + 客户端传的 sid 跟 server 端 active session 状态对不对。
273
+ console.info(
274
+ `[ws] connect pid=${pid} sid=${sid ? sid.slice(0, 8) : 'none'} `
275
+ + `hasActiveSession=${sid ? hasActiveQuerySession(sid) : 'n/a'} since=${since}`,
276
+ );
277
+
278
+ // sid lifecycle ref:带 sid 的 WS 连上即 ref++,close/error 时 unref。0 ref 触发
279
+ // grace timer N ms 后 closeQuerySession 让 SDK subprocess 自然退出。
280
+ refSession(sid);
281
+
282
+ // ── 连接协议(2026-07-27 重构:快照 + 尾随,每次连接都全量重建)──
283
+ //
284
+ // ① jsonl hydrate — 已完成 turn 的历史(turn 边界 flush 的持久层)
285
+ // ② ws.live_turn 快照 — 进行中 turn 的物化重建(live-turn.js 同步折叠,
286
+ // 覆盖"刷新 / 断线期间错过的全部流式内容")
287
+ // ③ 尾随订阅 — 从快照 seq 起 replay ring buffer + 切 live
288
+ //
289
+ // 老协议的 ?since= 游标 + gap 补 hydrate 废弃:ring buffer(2000) 对 token 级
290
+ // streaming 几秒断线就 gap,gap 后 hydrate 帧晚于 replay/live 到达,前端
291
+ // hydrate.end 整体替换 messages 把刚渲染的内容洗掉。新协议 hydrate 永远先发,
292
+ // 快照覆盖 ring 之外的一切,ring 只需覆盖"读 jsonl 的几百 ms"同步窗口。
293
+ // `since` 参数保留解析但忽略(旧前端兼容)。
294
+ //
295
+ // 两份历史的边界(2026-07-28):jsonl 与快照对"进行中的 turn"是重叠的 ——
296
+ // 读 jsonl 与取快照必须在**同一个同步区**里完成并一起发出,中间不能 await,
297
+ // 否则 turn 在缝里结束会让两边错位。规则:本轮由快照权威,hydrate 裁掉本轮。
298
+ const seqAtHydrateStart = bus._seq || 0;
299
+ let loaded = null;
300
+ let loadErr = null;
301
+ const t0 = Date.now();
302
+ if (sid) {
303
+ try { loaded = await loadHydrate(pid, sid); }
304
+ catch (err) { loadErr = err; }
305
+ }
306
+
307
+ // ↓↓↓ 同步区开始(不要在这里插 await)↓↓↓
308
+ const snapshot = sid ? getLiveTurnSnapshot(sid) : null;
309
+ if (sid) {
310
+ if (loadErr) {
311
+ console.warn(`[ws] hydrate failed for ${pid}/${sid}:`, loadErr.message);
312
+ sendHydrateError(ws, sid, loadErr.message);
313
+ } else {
314
+ const trimmed = snapshot ? dropInFlightTurn(loaded, snapshot.startedAt) : loaded;
315
+ console.info(
316
+ `[ws] hydrate sid=${sid.slice(0, 8)} loaded ${loaded.length} messages in ${Date.now() - t0}ms`
317
+ + (snapshot ? ` (本轮 ${loaded.length - trimmed.length} 条交给快照)` : ''),
318
+ );
319
+ sendHydrateFrames(ws, sid, trimmed, seqAtHydrateStart);
320
+ }
321
+ }
322
+ if (snapshot && ws.readyState === ws.OPEN) {
323
+ try {
324
+ ws.send(JSON.stringify({ type: 'ws.live_turn', sessionId: sid, ...snapshot }));
325
+ } catch (err) {
326
+ console.warn(`[ws] live_turn send failed for ${pid}:`, err.message);
327
+ }
328
+ }
329
+ // ↑↑↑ 同步区结束 ↑↑↑
330
+
331
+ // sid 过滤:projectBuses per-project 共享,只推本 session 事件 + 无 sessionId
332
+ // 的全局事件。sid=null(/work 路径)时 session-scoped 事件全部不推 ——
333
+ // 老行为"收全部"会把别的 session 的 delta 渲进空 chat(串台);/work 没有
334
+ // session,本来就不该收 run 流,新建 session 后前端会带 sid 重连。
335
+ const subscribeSince = snapshot ? snapshot.seq : seqAtHydrateStart;
336
+ const { unsubscribe, replayed, gap } = bus.subscribeFromSeq(subscribeSince, (event) => {
337
+ if (event.sessionId && event.sessionId !== sid) return;
338
+ if (ws.readyState === ws.OPEN) {
339
+ try {
340
+ ws.send(JSON.stringify(event));
341
+ } catch (err) {
342
+ console.warn(`[ws] send failed for ${pid}:`, err.message);
343
+ }
344
+ }
345
+ });
346
+
347
+ const heartbeat = setInterval(() => {
348
+ if (ws.readyState === ws.OPEN) {
349
+ try { ws.ping(); } catch { /* will close */ }
350
+ }
351
+ }, HEARTBEAT_INTERVAL_MS);
352
+
353
+ let cleanedUp = false;
354
+ const cleanup = () => {
355
+ if (cleanedUp) return;
356
+ cleanedUp = true;
357
+ clearInterval(heartbeat);
358
+ unsubscribe();
359
+ unrefSession(sid);
360
+ };
361
+
362
+ ws.on('close', cleanup);
363
+ ws.on('error', (err) => {
364
+ console.warn(`[ws] error for ${pid}:`, err.message);
365
+ cleanup();
366
+ });
367
+
368
+ // 确认 + replay 元信息(gap=true 客户端可决定全量 hydrate)。
369
+ // 故意放在 replay 之后发:客户端看到 ws.connected 就知道 backlog 已 drain,可切回正常 live 状态。
370
+ //
371
+ // activeRunId:sid 上当前在跑的 turn runId(无则 null)。前端用它重连后恢复
372
+ // isStreaming/currentRunId —— 否则 WS 抖动期间 run.start 已发完且 buffer 没新事件
373
+ // 时,前端永远不知道 run 还活着(stop 按钮消失,UX 表现"流没了")。
374
+ const activeRunId = sid ? getCurrentTurnRunId(sid) : null;
375
+ try {
376
+ ws.send(JSON.stringify({
377
+ type: 'ws.connected',
378
+ projectId: pid,
379
+ ts: new Date().toISOString(),
380
+ since,
381
+ replayed,
382
+ gap,
383
+ activeRunId,
384
+ }));
385
+ } catch { /* immediate close edge */ }
386
+ }
package/web/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Nodesign Web — P1 前端骨架
2
+
3
+ Nodesign 工作台前端。三栏布局:chat / canvas / context。
4
+
5
+ ## 当前阶段:P1(前端骨架)
6
+
7
+ 参考 plan:`/Users/edy/.claude/plans/plan-parallel-harbor.md`
8
+
9
+ ## 跑起来
10
+
11
+ ```bash
12
+ cd Nodesign/web
13
+ npm install
14
+ npm run dev # 起在 5174(避开 dev/ 的 5173)
15
+ ```
16
+
17
+ 打开 `http://localhost:5174/`。
18
+
19
+ ## P1 验证清单
20
+
21
+ 打开浏览器后逐项确认:
22
+
23
+ 1. ✅ Home 页(`/`)显示 5 个 mock 项目 + 两个入口卡片(自由创作 / 参照模式)
24
+ 2. ✅ 点项目进入 `/projects/proj-002`("Nodesign 内部介绍")三栏页
25
+ 3. ✅ 三栏布局:左 360px chat / 中 auto canvas / 右 340px context panel
26
+ 4. ✅ 顶栏:亮黑系 + 项目名 breadcrumb + Share/Export/⋯ 按钮
27
+ 5. ✅ Canvas iframe 加载 `/mock/deck.html`(5 页 DeskSkill 风格 self-contained HTML)
28
+ 6. ✅ Canvas Toolbar 三模式切换(Edit / Preview / Code)
29
+ 7. ✅ **Edit 模式下双击 iframe 内的文字** → 进入 contenteditable,blur 后控制台 log 出 patch
30
+ 8. ✅ Preview 模式下不能编辑(双击无效)
31
+ 9. ✅ Code 模式下 Monaco 显示 HTML 源码(只读)
32
+ 10. ✅ Inputs tab:拖文件 / 点击选文件 / 粘贴 URL(FileReader 本地预览,不发请求)
33
+ 11. ✅ SystemTab 显示 mock spec 摘要(metaphor / intent / outline 列表)
34
+ 12. ✅ ChatPanel:mock 6 条对话历史,输消息 + 发送(800ms 后伪 mock 回执)
35
+ 13. ✅ 视觉跟 dev/ 完全一致(亮黑按钮 / 深棕标题 / cubic-bezier 入退场)
36
+ 14. ✅ 路由:Home ↔ Project ↔ DesignSystemList 占位 ↔ SkillList 占位 切换流畅
37
+
38
+ ## 目录结构
39
+
40
+ ```
41
+ src/
42
+ ├── main.jsx # ReactDOM 入口
43
+ ├── App.jsx # 路由根(react-router-dom 7 createBrowserRouter)
44
+ ├── routes/
45
+ │ ├── Home.jsx
46
+ │ ├── Project.jsx # ★ 三栏组装
47
+ │ ├── DesignSystemList.jsx # 占位
48
+ │ └── SkillList.jsx # 占位
49
+ ├── components/
50
+ │ ├── layout/ # AppShell / TopBar / ThreeColumnLayout
51
+ │ ├── chat/ # ChatPanel / MessageList / Message / ChatComposer
52
+ │ ├── canvas/ # CanvasFrame / Toolbar / HtmlIframe / EditOverlay / DirectEditBridge / CodeCanvas
53
+ │ ├── context-panel/ # ContextPanel / InputsTab / SystemTab
54
+ │ └── ui/ # 12+ 从 dev/src/components/ui copy 来的通用组件
55
+ ├── lib/
56
+ │ ├── theme.js # ← 从 dev/src/constants/theme.js copy(COLOR / GAP / FONT_SIZE / cubic-bezier)
57
+ │ ├── html-utils.js # serializeAnchor / findElementByAnchor / ensureNodeId
58
+ │ └── helpers.js # newId / formatDate / timeAgo
59
+ ├── hooks/ # (P2 加:useEngineRun / useHtmlArtifact / useIframeBridge)
60
+ ├── stores/globalStore.js # zustand:toast / canvas mode / selectedAnchor
61
+ ├── mock/
62
+ │ ├── deck-spec.js # mock DeckSpec
63
+ │ └── projects.js # mock 5 个项目
64
+ └── styles/globals.css # 极小 reset
65
+
66
+ public/
67
+ └── mock/
68
+ └── deck.html # ★ 5 页 self-contained HTML(DeskSkill 风格)
69
+ ```
70
+
71
+ ## 设计原则(来自 plan-parallel-harbor)
72
+
73
+ 1. **不污染**:v0.7.5 是参考不是约束
74
+ 2. **探索 → 反向优化**:Nodesign 反向给 skill 提需求
75
+ 3. **spec 与 HTML 角色分离**:
76
+ - spec:agent 生成阶段的 commitment device + 上下文记忆,**用户不直接编辑**
77
+ - HTML:产物 + 用户局部交互的修改对象(comment / direct edit / slider / 未来 CAD 都改 HTML)
78
+ 4. **视觉沿用 DeskSkill**:亮黑 #2d2418 / 深棕 #3a2a18 / F9F8F6 / cubic-bezier(0.25, 1, 0.5, 1)
79
+ 5. **复用 dev/src**:12+ UI 组件直接搬
80
+
81
+ ## 下一阶段:P2 前端交互层
82
+
83
+ - inline comment UI(点元素弹气泡 + 评论列表)
84
+ - inputs tab 上传后真预览(FileReader 显示截图缩略图)
85
+ - Code mode 改 HTML(本地 state,blur 触发 iframe reload)
86
+ - tweak slider UI 占位(mock schema)
87
+ - 项目 CRUD(localStorage 持久化)
88
+
89
+ P3 起接后端 WS。
@@ -0,0 +1,11 @@
1
+ import{c as _,r as i,au as R,av as ee,j as t,C as r,F as y,b as $,G as u,aw as te,Z as ne,at as N,e as S,a2 as G,ax as ie,ay as se,a7 as oe}from"./index-Cbl3ATvp.js";import{A as re,R as ae}from"./rotate-cw-LMT3x1Zj.js";/**
2
+ * @license lucide-react v0.468.0 - ISC
3
+ *
4
+ * This source code is licensed under the ISC license.
5
+ * See the LICENSE file in the root directory of this source tree.
6
+ */const I=_("Play",[["polygon",{points:"6 3 20 12 6 21 6 3",key:"1oa8hb"}]]);/**
7
+ * @license lucide-react v0.468.0 - ISC
8
+ *
9
+ * This source code is licensed under the ISC license.
10
+ * See the LICENSE file in the root directory of this source tree.
11
+ */const le=_("PowerOff",[["path",{d:"M18.36 6.64A9 9 0 0 1 20.77 15",key:"dxknvb"}],["path",{d:"M6.16 6.16a9 9 0 1 0 12.68 12.68",key:"1x7qb5"}],["path",{d:"M12 2v4",key:"3427ic"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]]);function J(c,x){const h=c.getBoundingClientRect();return{rx:Math.min(1,Math.max(0,(x.clientX-h.left)/h.width)),ry:Math.min(1,Math.max(0,(x.clientY-h.top)/h.height))}}function pe({projectId:c,url:x,help:h,onClose:m,onToolbarGroups:U}){const[w,d]=i.useState("connecting"),[A,g]=i.useState(null),[j,O]=i.useState(x||""),[a,M]=i.useState(!1),[H,X]=i.useState(!1),[T,B]=i.useState(null),[v,D]=i.useState(!1),[C,V]=i.useState([]),[F,Y]=i.useState(null),[E,Z]=i.useState(!0),z=i.useRef(null),L=i.useRef(null),q=i.useRef(!1);q.current=a;const l=i.useCallback(e=>{const n=L.current;n&&n.readyState===1&&n.send(JSON.stringify(e))},[]),K=i.useCallback(async()=>{D(!0),g(null);try{const e=await R.open(c);O(e.url||""),d("connecting"),l({type:"subscribe"})}catch(e){d("error"),g((e==null?void 0:e.message)||"打不开")}finally{D(!1)}},[c,l]);i.useEffect(()=>{const e=window.location.protocol==="https:"?"wss:":"ws:",n=new WebSocket(`${e}//${window.location.host}/ws/projects/${encodeURIComponent(c)}/browser`);n.binaryType="arraybuffer",L.current=n,n.onopen=()=>{d("connecting"),n.send(JSON.stringify({type:"subscribe"}))},n.onclose=p=>{d(p.code===4401?"error":"closed"),p.code===4401&&g("没有权限")},n.onerror=()=>d("error"),n.onmessage=async p=>{var o,W;if(typeof p.data!="string"){const b=z.current;if(!b)return;try{const f=await createImageBitmap(new Blob([p.data],{type:"image/jpeg"}));(b.width!==f.width||b.height!==f.height)&&(b.width=f.width,b.height=f.height),(o=b.getContext("2d"))==null||o.drawImage(f,0,0),(W=f.close)==null||W.call(f),X(!0),d("live")}catch{}return}let s;try{s=JSON.parse(p.data)}catch{return}s.type==="subscribed"&&(d("live"),s.url&&O(s.url)),s.type==="hello"&&s.help&&B(s.help),s.type==="released"&&B(null),s.type==="idle"&&(d("idle"),g(s.reason)),s.type==="error"&&(d("error"),g(s.reason)),s.type==="closed"&&(d("closed"),g(s.reason)),s.type==="url"&&O(s.url||"")};const k=()=>l({type:document.hidden?"unsubscribe":"subscribe"});return document.addEventListener("visibilitychange",k),()=>{document.removeEventListener("visibilitychange",k);try{n.send(JSON.stringify({type:"unsubscribe"}))}catch{}n.close(),L.current=null}},[c,l]),i.useEffect(()=>{x&&O(x)},[x]),i.useEffect(()=>{let e=!0;return R.state(c).then(n=>{e&&V(Array.isArray(n==null?void 0:n.sites)?n.sites:[])}).catch(()=>{}),()=>{e=!1}},[c]),i.useEffect(()=>{var s;if(!a)return;const e=z.current;if(!e)return;const n=o=>{o.preventDefault(),l({type:"input",kind:"click",...J(e,o)})},k=o=>{o.preventDefault(),l({type:"input",kind:"wheel",...J(e,o),dx:o.deltaX,dy:o.deltaY})},p=o=>{if(!o.isComposing){if(o.key.length===1){o.preventDefault(),l({type:"input",kind:"key",text:o.key});return}["Enter","Backspace","Tab","Escape","ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(o.key)&&(o.preventDefault(),l({type:"input",kind:"key",key:o.key}))}};return e.addEventListener("click",n),e.addEventListener("wheel",k,{passive:!1}),e.addEventListener("keydown",p),(s=e.focus)==null||s.call(e),()=>{e.removeEventListener("click",n),e.removeEventListener("wheel",k),e.removeEventListener("keydown",p)}},[a,l]);const Q=i.useMemo(()=>[{id:"addr",node:t.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:6,padding:`0 ${u.sm}px`,maxWidth:420,fontFamily:$,fontSize:y.xs,color:r.text2,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",userSelect:"text"},children:[t.jsx(te,{size:12,style:{flexShrink:0,opacity:.6}}),j||"(还没打开页面)"]})},{id:"nav",items:[{id:"back",icon:re,title:"后退",onClick:()=>l({type:"nav",action:"back"})},{id:"reload",icon:ae,title:"刷新",onClick:()=>l({type:"nav",action:"reload"})}]},{id:"end",items:[{id:"end",icon:le,title:"不逛了 —— 关掉浏览器并把桌面上那张卡收走(在站点上的登录留着)",onClick:async()=>{try{await R.end(c)}catch{}m==null||m()}}]},{id:"takeover",items:[{id:"hand",icon:a?I:ee,title:a?"好了继续 —— 把控制权交回 agent(它正等着)":"我来接手 —— 点击/滚动/打字会直接发到那个浏览器里",active:a,onClick:()=>{a?(l({type:"release"}),M(!1)):M(!0)}}]}],[j,a,l,c,m]),P={connecting:"连接中…",live:null,idle:A||"agent 还没开始浏览",error:A||"出错了",closed:A||"浏览器已经关了"}[w];return t.jsxs(ne,{kind:"browse",title:"agent 的浏览器",subtitle:a?"你在接手":w==="live"?"实时":null,onClose:m,groups:Q,onToolbarGroups:U,banner:h||T?t.jsxs("span",{children:[t.jsx("b",{children:"agent 需要你帮个手"}),":",h||T,"—— 点工具栏上的",t.jsx("b",{children:"手形按钮"}),"接手,弄完再点一次(变成 ▶)把控制权交回去。 它正停在那儿等你。"]}):null,contentStyle:{background:N.paper},children:[t.jsxs("div",{style:{flex:1,minHeight:0,width:"100%",display:"flex",alignItems:"center",justifyContent:"center",padding:u.md,boxSizing:"border-box",position:"relative"},children:[t.jsx("canvas",{ref:z,tabIndex:a?0:-1,title:a?"接手中:点一下这块画面再打字(键盘只在这里生效)":void 0,style:{maxWidth:"100%",maxHeight:"100%",display:H?"block":"none",background:"#fff",borderRadius:2,boxShadow:"0 2px 12px rgba(43,39,35,.18)",cursor:a?"pointer":"default",outline:a?`2px solid ${r.accent||"#8a4b2d"}`:"none",outlineOffset:3}}),(!H||P)&&t.jsxs("div",{style:{position:"absolute",inset:0,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:u.sm,fontFamily:S,fontSize:y.sm,color:r.text2,textAlign:"center"},children:[w==="connecting"&&t.jsx(G,{size:16,style:{animation:"spin 1s linear infinite"}}),t.jsx("span",{children:P||"等第一帧…"}),(w==="idle"||w==="closed")&&t.jsxs(t.Fragment,{children:[j&&t.jsxs("button",{type:"button",disabled:v,onClick:K,style:{display:"inline-flex",alignItems:"center",gap:6,padding:`${u.xs}px ${u.md}px`,cursor:v?"default":"pointer",fontFamily:S,fontSize:y.sm,color:r.text,background:N.paper,border:`1px solid ${r.border}`,borderRadius:3,opacity:v?.6:1},children:[v?t.jsx(G,{size:13,style:{animation:"spin 1s linear infinite"}}):t.jsx(I,{size:13}),v?"正在打开…":"打开上次那一页"]}),t.jsxs("span",{style:{color:r.sub,fontSize:y.xs,maxWidth:360,lineHeight:1.7},children:[j?"浏览器空闲 5 分钟会自己休息(登录态留着)。打开之后你可以直接接手操作,agent 下次也接着用这一页。":"让 agent 去看一个站(它有 browser_navigate),这里就会亮起来。",t.jsx("br",{}),"静止的页面不会一直传帧 —— 画面不动是正常的,不是卡住了。"]})]})]})]}),!!C.length&&t.jsxs("div",{style:{flexShrink:0,borderTop:`1px solid ${r.border}`,background:r.bgCard,maxHeight:E?220:30,overflow:"hidden",transition:"max-height .18s ease"},children:[t.jsxs("button",{type:"button",onClick:()=>Z(e=>!e),style:{display:"flex",alignItems:"center",gap:6,width:"100%",padding:`4px ${u.md}px`,cursor:"pointer",background:"transparent",border:0,color:r.text2,fontFamily:S,fontSize:y.xs},children:[t.jsx(ie,{size:12}),"采到的东西 · ",C.length," 个站 · ",C.reduce((e,n)=>e+n.count,0)," 件",t.jsx(se,{size:12,style:{marginLeft:"auto",opacity:.6,transform:E?"none":"rotate(-90deg)",transition:"transform .18s ease"}})]}),E&&t.jsx("div",{style:{display:"flex",gap:u.sm,padding:`0 ${u.md}px ${u.sm}px`,overflowX:"auto"},children:C.map(e=>t.jsxs("div",{style:{flexShrink:0,width:150},children:[t.jsxs("button",{type:"button",title:`${e.dir}(${e.count} 件)`,onClick:()=>Y(F===e.site?null:e.site),style:{display:"block",width:"100%",padding:0,cursor:"pointer",background:N.paper,border:`1px solid ${F===e.site?r.text:r.border}`,borderRadius:2,overflow:"hidden"},children:[e.cover?t.jsx("img",{alt:"",loading:"lazy",src:oe.artifactFileUrl(c,e.cover),style:{width:"100%",height:84,objectFit:"cover",objectPosition:"top center",display:"block"}}):t.jsx("div",{style:{height:84,display:"flex",alignItems:"center",justifyContent:"center",color:r.sub,fontFamily:S,fontSize:y.xxs},children:"没有截图"}),t.jsx("div",{style:{padding:"3px 5px",textAlign:"left",fontFamily:$,fontSize:y.xxs,color:r.text2,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},children:e.site})]}),F===e.site&&t.jsxs("div",{style:{paddingTop:3,fontFamily:S,fontSize:y.xxs,color:r.sub,lineHeight:1.6},children:[t.jsxs("span",{style:{fontFamily:$},children:[e.dir,"/"]}),t.jsx("br",{}),e.count," 件(截图 / 调色板 / 字体 / 结构 / CSS,类别在文件名里)"]})]},e.site))})]})]})}export{pe as default};
@@ -0,0 +1,16 @@
1
+ import{c as et,r as o,j as e,C as r,G as t,a as Ht,R as $,F as p,b as E,P as tt,u as N,X as ue,d as nt,e as z,f as st,T as Dt,g as _t,h as ke,B as It,i as rt,k as ze,U as Nt,l as it,S as qt,H as Ut,m as Gt,n as lt,o as Kt,p as Vt,q as Xt,s as ot,t as at,v as dt,w as Yt,x as Zt,y as ct,z as Ct,A as Jt,E as ut,D as Qt,I as en,J as Ke,K as tn,M as nn,L as sn,N as rn,O as ln,Q as on,V as an,W as dn,Y as cn,Z as un}from"./index-Cbl3ATvp.js";import{d as Ve,a as Xe,M as xn,o as pn,t as gn,E as fn,C as hn,G as mn,p as yn,D as bn,b as wn,c as vn,P as kn,I as jn,R as Sn,e as zn}from"./pending-edit-apply-DTjo2geW.js";import{E as $n,S as En}from"./square-dashed-mouse-pointer-CBoAkfhi.js";/**
2
+ * @license lucide-react v0.468.0 - ISC
3
+ *
4
+ * This source code is licensed under the ISC license.
5
+ * See the LICENSE file in the root directory of this source tree.
6
+ */const xt=et("MessageSquare",[["path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",key:"1lielz"}]]);/**
7
+ * @license lucide-react v0.468.0 - ISC
8
+ *
9
+ * This source code is licensed under the ISC license.
10
+ * See the LICENSE file in the root directory of this source tree.
11
+ */const Ln=et("ShieldCheck",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]),Fn=1920,An=1080;function Tn({src:n,srcDoc:s,mode:i="edit",onSelect:l,onTextEdit:d,onIframeReady:f,zoom:m=1,deckW:c=Fn,deckH:y=An}){const u=o.useRef(null),a=o.useRef(!1),w=o.useRef(0),[x,v]=o.useState(!1);o.useEffect(()=>{v(!0)},[n,s]),o.useEffect(()=>{const b=u.current;if(!(!b||!a.current))return Ve(b),i==="edit"&&Xe(b,{onSelect:l,onTextEdit:d}),()=>Ve(b)},[i,l,d]),o.useEffect(()=>()=>{var b;try{const F=(b=u.current)==null?void 0:b.contentWindow;F&&typeof F.scrollY=="number"&&(w.current=F.scrollY)}catch{}},[n,s]);const k=()=>{a.current=!0;const b=u.current;if(b){if(f==null||f(b),i==="edit"&&Xe(b,{onSelect:l,onTextEdit:d}),w.current>0)try{requestAnimationFrame(()=>{var F;try{(F=b.contentWindow)==null||F.scrollTo(0,w.current)}catch{}})}catch{}requestAnimationFrame(()=>{requestAnimationFrame(()=>v(!1))})}};return e.jsx("div",{style:{flex:1,minHeight:0,overflow:"hidden",background:r.bgWhite,position:"relative",display:"flex",alignItems:"center",justifyContent:"center"},children:e.jsx("iframe",{ref:u,src:n,srcDoc:s,onLoad:k,sandbox:"allow-scripts allow-same-origin",style:{width:`${c}px`,height:`${y}px`,flexShrink:0,border:0,background:r.bgWhite,transform:m===1?"none":`scale(${m})`,transformOrigin:"center center",opacity:x?.5:1,transition:"opacity 180ms ease-out"}})})}function Pn({candidates:n,activeId:s,onSelect:i,onAdd:l,onRemove:d,onRename:f}){if(!n||n.length===0)return null;const m=n.length===1;return e.jsxs("div",{style:{flexShrink:0,height:36,borderBottom:`1px solid ${r.border}`,background:r.bgWhite,padding:`0 ${t.lg}px`,display:"flex",alignItems:"center",gap:t.xs,overflowX:"auto",overflowY:"hidden"},children:[e.jsx(Ht,{size:11,color:r.sub,style:{flexShrink:0,marginRight:t.xs}}),n.map(c=>{const y=c.id===s;return e.jsx(Wn,{candidate:c,active:y,onSelect:()=>i==null?void 0:i(c.id),onRemove:m?null:()=>d==null?void 0:d(c.id),onRename:f},c.id)}),e.jsxs("button",{onClick:l,title:"新建候选(mock 复制当前)",style:{flexShrink:0,padding:`${t.xs}px ${t.md}px`,display:"inline-flex",alignItems:"center",gap:t.xs,fontFamily:E,fontSize:p.xs,color:r.sub,background:"transparent",border:`1px dashed ${r.borderMd}`,borderRadius:$.sm,cursor:"pointer"},onMouseEnter:c=>{c.currentTarget.style.color=r.text2,c.currentTarget.style.background="rgba(0,0,0,0.03)"},onMouseLeave:c=>{c.currentTarget.style.color=r.sub,c.currentTarget.style.background="transparent"},children:[e.jsx(tt,{size:11})," 新候选"]}),e.jsx("div",{style:{flex:1}}),e.jsxs("span",{style:{fontFamily:E,fontSize:p.xs,color:r.sub,opacity:m?0:.7,flexShrink:0},children:[n.length," 个候选"]})]})}function Wn({candidate:n,active:s,onSelect:i,onRemove:l,onRename:d}){const[f,m]=o.useState(!1),c=N(w=>w.prompt),y=N(w=>w.confirm),u=async w=>{if(w.stopPropagation(),!d)return;const x=await c({title:"重命名候选",initialValue:n.label,placeholder:"候选名"});x&&x.trim()&&x!==n.label&&d(n.id,x.trim())},a=async w=>{w.stopPropagation(),await y({title:"删除候选",message:`删除候选「${n.label}」?`,confirmLabel:"删除",danger:!0})&&l()};return e.jsxs("div",{onMouseEnter:()=>m(!0),onMouseLeave:()=>m(!1),style:{flexShrink:0,position:"relative",display:"inline-flex",alignItems:"center",background:s?"rgba(45,36,24,0.06)":"transparent",borderRadius:$.sm,transition:"background 0.15s"},children:[e.jsx("button",{onClick:i,onDoubleClick:u,title:"双击重命名",style:{padding:`${t.xs}px ${l&&f?t.lg:t.md}px ${t.xs}px ${t.md}px`,fontFamily:E,fontSize:p.xs,fontWeight:s?500:400,color:s?r.text:r.text4,background:"transparent",borderRadius:$.sm,cursor:"pointer",transition:"all 0.15s"},children:n.label}),l&&f&&e.jsx("button",{onClick:a,style:{position:"absolute",right:2,top:"50%",transform:"translateY(-50%)",width:14,height:14,borderRadius:$.xs,background:"rgba(0,0,0,0.06)",color:r.sub,display:"flex",alignItems:"center",justifyContent:"center",cursor:"pointer"},onMouseEnter:w=>{w.currentTarget.style.background="rgba(184,58,42,0.15)",w.currentTarget.style.color=r.error},onMouseLeave:w=>{w.currentTarget.style.background="rgba(0,0,0,0.06)",w.currentTarget.style.color=r.sub},title:"删除候选",children:e.jsx(ue,{size:9})})]})}const de=12,Bn=8;function $e(n,s){const[i,l]=o.useState(null);return o.useLayoutEffect(()=>{const d=n==null?void 0:n.current;if(!d){l(null);return}const f=()=>{const m=d.getBoundingClientRect();if(!m.width&&!m.height){l(null);return}const c=window.innerWidth,y=window.innerHeight,u=Math.max(de,Math.min(m.left,c-s-de)),a=Math.min(m.bottom+Bn,y-de-120);l({position:"fixed",left:u,top:a,maxHeight:y-a-de})};return f(),window.addEventListener("resize",f),()=>window.removeEventListener("resize",f)},[n,s]),i||{position:"absolute",top:78,right:16}}function Mn({anchorRef:n,onClose:s,iframeDoc:i}){const l=o.useRef(null),d=$e(n,320),[f,m]=o.useState(!1),[c,y]=o.useState(null);o.useEffect(()=>{i&&u()},[i]),o.useEffect(()=>{const a=w=>{var x;l.current&&!l.current.contains(w.target)&&!((x=n==null?void 0:n.current)!=null&&x.contains(w.target))&&(s==null||s())};return window.addEventListener("mousedown",a),()=>window.removeEventListener("mousedown",a)},[s,n]);const u=()=>{i&&(m(!0),setTimeout(()=>{y(On(i)),m(!1)},400))};return e.jsxs("div",{ref:l,style:{...d,width:320,background:r.bgWhite,borderRadius:2,boxShadow:"0 2px 4px rgba(0,0,0,0.04), 0 8px 20px rgba(0,0,0,0.08), 0 24px 48px rgba(0,0,0,0.10), inset 0 1px 0 rgba(255,255,255,0.8)",zIndex:60,overflow:"hidden"},children:[e.jsxs("div",{style:{padding:`${t.md}px ${t.lg}px`,display:"flex",alignItems:"center",justifyContent:"space-between",borderBottom:`1px solid ${r.borderLt}`},children:[e.jsx("span",{style:{fontFamily:E,fontSize:p.sm,fontWeight:500,color:r.text},children:"Accessibility Review"}),e.jsx("button",{onClick:u,disabled:f,style:{padding:t.xs,color:r.sub,borderRadius:$.xs,opacity:f?.5:1},title:"重新扫描",children:e.jsx(nt,{size:11,className:f?"spin":""})})]}),e.jsxs("div",{style:{padding:t.lg,maxHeight:360,overflowY:"auto"},children:[f&&e.jsx("div",{style:{fontFamily:z,fontSize:p.sm,color:r.sub,textAlign:"center",padding:t.lg},children:"扫描中…"}),!f&&c&&e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:t.sm},children:c.map((a,w)=>e.jsx(Rn,{...a},w))})]}),e.jsx("div",{style:{padding:`${t.sm}px ${t.lg}px`,background:r.bgCard,borderTop:`1px solid ${r.borderLt}`,fontFamily:z,fontSize:p.xs,color:r.sub,lineHeight:1.5},children:"P5 接 LLM 真做 contrast / hierarchy / usability 审查;当前是启发式扫描。"})]})}function Rn({kind:n,label:s,hint:i,count:l}){const d=n==="ok",f=n==="warn",m=d?st:f?Dt:ue,c=d?r.success:f?r.warn:r.error;return e.jsxs("div",{style:{display:"flex",alignItems:"flex-start",gap:t.sm,padding:`${t.xs}px ${t.sm}px`,background:d?"transparent":"rgba(0,0,0,0.02)",borderRadius:$.sm},children:[e.jsx(m,{size:12,color:c,style:{marginTop:t.xxs,flexShrink:0}}),e.jsxs("div",{style:{flex:1},children:[e.jsxs("div",{style:{fontFamily:z,fontSize:p.sm,color:r.text2,lineHeight:1.4},children:[s," ",l!=null&&e.jsxs("span",{style:{color:r.sub,fontSize:p.xs},children:["(",l,")"]})]}),i&&e.jsx("div",{style:{fontFamily:z,fontSize:p.xs,color:r.sub,marginTop:1,lineHeight:1.4},children:i})]})]})}function On(n){const s=[],i=Array.from(n.querySelectorAll("img")),l=i.filter(u=>!u.hasAttribute("alt"));i.length===0?s.push({kind:"ok",label:"无图片元素需要 alt",hint:"没有 <img> 标签"}):l.length===0?s.push({kind:"ok",label:"图片 alt 完整",count:i.length}):s.push({kind:"warn",label:"部分图片缺 alt",count:l.length,hint:"加 alt 才能让屏幕阅读器识别"});const d=n.querySelectorAll("h1").length,f=n.querySelectorAll("h2").length;d===0&&f===0?s.push({kind:"warn",label:"没有 h1/h2 标题",hint:"层级缺失,screen reader 难导航"}):d>5?s.push({kind:"warn",label:"h1 过多",count:d,hint:"一般每页只 1 个 h1(每 section 1 个也可)"}):s.push({kind:"ok",label:"标题层级合理",hint:`h1 × ${d}, h2 × ${f}`});const m=Array.from(n.querySelectorAll("button")),c=m.filter(u=>!(u.textContent||"").trim()&&!u.getAttribute("aria-label"));m.length===0?s.push({kind:"ok",label:"无 button 需要文本",hint:"页面没有 <button>"}):c.length===0?s.push({kind:"ok",label:"按钮文本完整",count:m.length}):s.push({kind:"warn",label:"部分按钮缺文本/aria-label",count:c.length});const y=n.documentElement;return y.hasAttribute("lang")?s.push({kind:"ok",label:`<html> 标记了 lang="${y.getAttribute("lang")}"`}):s.push({kind:"warn",label:"<html> 缺 lang 属性",hint:"影响 screen reader 发音"}),s.push({kind:"ok",label:"色彩对比度(抽样)",hint:"主色块 WCAG AA pass(mock)"}),s.push({kind:"ok",label:"焦点顺序(mock)",hint:"Tab 键 traversal — P5 真模拟"}),s}function Hn({project:n,deckSpec:s,projectId:i}){var j,T,H;const l=_t(h=>{var P;return((P=h.contextByProject[i])==null?void 0:P.systemInfo)||null}),d=N(h=>h.showToast),f=N(h=>h.confirm),[m,c]=o.useState([]),[y,u]=o.useState(!1),[a,w]=o.useState(!1),x=o.useRef(null),v=o.useCallback(async()=>{if(i){u(!0);try{const{plugins:h}=await ke.listProject(i);c(h||[])}catch(h){console.error("[SystemTab] listProject plugins failed:",h)}finally{u(!1)}}},[i]);o.useEffect(()=>{v()},[v]);const k=o.useCallback(async(h,P=!1)=>{var D,_,ee,K,V;if(!(!h||!i)){w(!0);try{const L=await ke.installProject(i,h,{force:P}),q=(D=L==null?void 0:L.installed)==null?void 0:D.name;d(`已装 plugin \`${q}\`(新会话生效)`,"success"),(_=L==null?void 0:L.warnings)!=null&&_.length&&d(`警告:${L.warnings.join(";")}`,"warn"),await v()}catch(L){if(L.status===409&&((ee=L.body)!=null&&ee.existing)){await f({title:"覆盖已装 plugin?",message:`已装 \`${L.body.existing.name}@${L.body.existing.version}\`,将覆盖为 \`${L.body.incoming.name}@${L.body.incoming.version}\`。`,confirmLabel:"覆盖",cancelLabel:"取消",danger:!0})&&await k(h,!0);return}const q=(V=(K=L.body)==null?void 0:K.errors)!=null&&V.length?`:${L.body.errors.join(";")}`:"";d(`安装失败${q||`:${L.message}`}`,"error")}finally{w(!1),x.current&&(x.current.value="")}}},[i,v,d,f]),b=o.useCallback(async h=>{if(await f({title:"卸载 plugin?",message:`从 project 移除 \`${h}\`(新会话生效)。文件会被删除。`,confirmLabel:"卸载",cancelLabel:"取消",danger:!0}))try{await ke.removeProject(i,h),d(`已卸载 \`${h}\``,"success"),await v()}catch(D){d(`卸载失败:${D.message}`,"error")}},[i,v,d,f]),F=Array.isArray(l==null?void 0:l.skills)?l.skills.map(h=>typeof h=="string"?h:h.name||h.id).filter(Boolean):[],W=Array.isArray(l==null?void 0:l.plugins)?l.plugins.map(h=>typeof h=="string"?h:h.name||h.id).filter(Boolean):[];return e.jsxs("div",{style:{padding:t.lg,display:"flex",flexDirection:"column",gap:t.xl},children:[e.jsx(Q,{label:"本会话的 Skills",children:F.length===0?e.jsx("span",{style:{fontFamily:z,fontSize:p.sm,color:r.sub},children:"(等会话起动后从 SDK system_init 拉取)"}):e.jsx("div",{style:{display:"flex",flexWrap:"wrap",gap:t.xs},children:F.map(h=>e.jsx(Ye,{icon:It,label:h},h))})}),e.jsx(Q,{label:"本会话的 Plugins",children:W.length===0?e.jsx("span",{style:{fontFamily:z,fontSize:p.sm,color:r.sub},children:"—"}):e.jsx("div",{style:{display:"flex",flexWrap:"wrap",gap:t.xs},children:W.map(h=>e.jsx(Ye,{icon:rt,label:h},h))})}),e.jsxs(Q,{label:"Project 级 Plugins",children:[e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:t.xs,marginBottom:t.sm},children:y&&m.length===0?e.jsx("span",{style:{fontFamily:z,fontSize:p.sm,color:r.sub},children:"加载中…"}):m.length===0?e.jsx("span",{style:{fontFamily:z,fontSize:p.sm,color:r.sub},children:"暂无(上传 zip 安装到本 project)"}):m.map(h=>e.jsx(Dn,{plugin:h,onUninstall:()=>b(h.name)},h.name))}),e.jsx("input",{ref:x,type:"file",accept:".md,.zip,application/zip,text/markdown,text/plain",style:{display:"none"},onChange:h=>{var D;const P=(D=h.target.files)==null?void 0:D[0];P&&k(P)}}),e.jsxs("button",{onClick:()=>{var h;return(h=x.current)==null?void 0:h.click()},disabled:a,style:{display:"inline-flex",alignItems:"center",gap:t.xs,padding:`${t.xs+1}px ${t.md}px`,fontFamily:z,fontSize:p.xs,color:r.text2,background:r.bgWhite,boxShadow:ze.far,borderRadius:$.md,cursor:a?"not-allowed":"pointer",opacity:a?.6:1},children:[e.jsx(Nt,{size:11})," ",a?"安装中…":"上传 skill / plugin"]}),e.jsx("div",{style:{marginTop:t.xs,fontFamily:z,fontSize:p.xs,color:r.sub,lineHeight:1.5},children:"支持单 .md / skill zip / 完整 plugin zip。仅装到本 project;全局可见的走右上角 Skill 页面。新会话生效。"})]}),e.jsxs(Q,{label:"模型",children:[e.jsx(ce,{k:"LLM",v:(l==null?void 0:l.model)||"—"}),(l==null?void 0:l.cwd)&&e.jsx(ce,{k:"cwd",v:l.cwd})]}),s&&e.jsxs(Q,{label:"设计意图(spec)",children:[e.jsx(ce,{k:"metaphor",v:((j=s.meta)==null?void 0:j.metaphor)||"—"}),e.jsx(ce,{k:"audience",v:((T=s.meta)==null?void 0:T.audience)||"—"}),((H=s.meta)==null?void 0:H.intent)&&e.jsx("div",{style:{marginTop:t.sm,padding:t.md,background:r.bgCard,border:`1px solid ${r.borderLt}`,borderRadius:$.md,fontFamily:z,fontSize:p.xs,color:r.text2,lineHeight:1.6},children:s.meta.intent}),s.outline&&s.outline.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:{marginTop:t.lg,fontFamily:E,fontSize:p.xs,color:r.sub,textTransform:"uppercase",letterSpacing:"0.05em",marginBottom:t.sm},children:["页面 outline (",s.outline.length,")"]}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:t.xs},children:s.outline.map(h=>e.jsxs("div",{style:{padding:`${t.xs+1}px ${t.md}px`,fontFamily:z,fontSize:p.xs,background:"rgba(43,33,23,0.025)",borderRadius:$.sm,display:"flex",gap:t.sm,alignItems:"baseline"},children:[e.jsx("span",{style:{fontFamily:E,color:r.sub,minWidth:24},children:String(h.index).padStart(2,"0")}),e.jsx("span",{style:{color:r.text3,minWidth:64,fontFamily:E,fontSize:p.xs,textTransform:"uppercase"},children:h.layout}),e.jsx("span",{style:{color:r.text2,flex:1,lineHeight:1.4},children:h.intent})]},h.id))})]})]})]})}function Dn({plugin:n,onUninstall:s}){var i;return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:t.sm,padding:`${t.xs+1}px ${t.sm}px`,background:"rgba(43,33,23,0.025)",borderRadius:$.sm},children:[e.jsx(rt,{size:11,color:r.text4}),e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsxs("div",{style:{display:"flex",alignItems:"baseline",gap:t.xs},children:[e.jsx("span",{style:{fontFamily:E,fontSize:p.sm,color:r.text2},children:n.name}),e.jsx("span",{style:{fontFamily:E,fontSize:p.xs,color:r.sub},children:n.version}),e.jsxs("span",{style:{fontFamily:z,fontSize:p.xs,color:r.sub},children:["· ",((i=n.skills)==null?void 0:i.length)||0," skill"]})]}),n.description&&e.jsx("div",{style:{fontFamily:z,fontSize:p.xs,color:r.sub,lineHeight:1.4,marginTop:t.xxs,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},title:n.description,children:n.description})]}),e.jsx("button",{onClick:s,title:"卸载",style:{background:"transparent",border:"none",color:r.sub,cursor:"pointer",padding:t.xxs},children:e.jsx(it,{size:11})})]})}function Ye({icon:n,label:s}){return e.jsxs("span",{style:{display:"inline-flex",alignItems:"center",gap:t.xs,padding:`${t.xxs}px ${t.sm}px`,fontFamily:E,fontSize:p.xs,color:r.text2,background:"rgba(43,33,23,0.04)",borderRadius:$.pill},children:[e.jsx(n,{size:10,color:r.text4}),s]})}function Q({label:n,children:s}){return e.jsxs("div",{children:[e.jsx("div",{style:{fontFamily:E,fontSize:p.xs,color:r.sub,textTransform:"uppercase",letterSpacing:"0.05em",marginBottom:t.sm},children:n}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:t.xs},children:s})]})}function ce({k:n,v:s}){return e.jsxs("div",{style:{display:"flex",gap:t.md,fontFamily:z,fontSize:p.sm},children:[e.jsx("span",{style:{color:r.sub,minWidth:70},children:n}),e.jsx("span",{style:{color:r.text2,flex:1,wordBreak:"break-word"},children:s})]})}function _n({projectId:n,sessionId:s,reloadKey:i=0}){const[l,d]=o.useState(null),[f,m]=o.useState(!1),[c,y]=o.useState(null),u=o.useCallback(async()=>{if(!n||!s){d({});return}m(!0),y(null);try{const x=await qt.read(n);d((x==null?void 0:x.spec)||{})}catch(x){y(x)}finally{m(!1)}},[n,s]);o.useEffect(()=>{u()},[u,i]);const a=Array.isArray(l==null?void 0:l.decisions)?l.decisions:[],w=Array.isArray(l==null?void 0:l.history)?l.history:[];return e.jsxs("div",{style:{padding:t.lg},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:t.md},children:[e.jsxs("div",{style:{fontFamily:E,fontSize:p.sm,fontWeight:500,color:r.text},children:["DECISIONS",e.jsxs("span",{style:{color:r.sub,fontWeight:400,marginLeft:t.sm},children:["(",a.length,")"]})]}),e.jsxs("button",{onClick:u,disabled:f,title:"刷新(agent 调用 record_decision 后会自动刷新;手动按这个也行)",style:{display:"inline-flex",alignItems:"center",gap:t.xs,padding:`${t.xs}px ${t.sm}px`,background:"rgba(43,33,23,0.04)",border:"none",borderRadius:$.sm,cursor:f?"wait":"pointer",fontFamily:z,fontSize:p.sm,color:r.sub},children:[e.jsx(nt,{size:11,style:{animation:f?"rcw-spin 1s linear infinite":"none"}}),f?"...":"刷新"]})]}),c&&e.jsxs("div",{style:{padding:t.md,background:"rgba(220, 53, 69, 0.06)",border:`1px solid ${r.error}33`,borderRadius:$.md,fontFamily:z,fontSize:p.xs,color:r.error,marginBottom:t.md},children:["读取失败:",c.message]}),a.length===0&&!f&&e.jsxs("div",{style:{padding:t.lg,textAlign:"center",fontFamily:z,fontSize:p.sm,color:r.sub,lineHeight:1.6},children:["设计决策现在记在画布的任务便利贴上",e.jsx("br",{}),"(",e.jsx("code",{style:{background:"rgba(43,33,23,0.06)",padding:`1px ${t.xs}px`,borderRadius:$.xs},children:"notes/决策.md"}),", 任务区里那张黄贴纸)。",e.jsx("br",{}),"这里只显示旧会话的存量决策和压缩历史。"]}),a.map((x,v)=>e.jsx(In,{decision:x,index:a.length-v},v)).reverse(),w.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:{marginTop:t.xl,marginBottom:t.sm,fontFamily:E,fontSize:p.xs,fontWeight:500,color:r.sub,display:"flex",alignItems:"center",gap:t.xs,letterSpacing:"0.04em"},children:[e.jsx(Ut,{size:11})," COMPACT HISTORY (",w.length,")"]}),w.map((x,v)=>e.jsx(Nn,{entry:x},v)).reverse()]}),e.jsx("style",{children:`
12
+ @keyframes rcw-spin {
13
+ from { transform: rotate(0deg); }
14
+ to { transform: rotate(360deg); }
15
+ }
16
+ `})]})}function In({decision:n}){return e.jsxs("div",{style:{padding:t.md,marginBottom:t.sm,borderRadius:2,background:r.bgWhite,boxShadow:ze.far},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:t.xs,marginBottom:t.xs},children:[e.jsx(Gt,{size:11,color:r.text4}),e.jsx("div",{style:{flex:1,fontFamily:z,fontSize:p.sm,fontWeight:500,color:r.text,lineHeight:1.4},children:n.title||"(no title)"})]}),e.jsx("div",{style:{fontFamily:z,fontSize:p.xs,color:r.text4,lineHeight:1.5,marginBottom:t.xs,whiteSpace:"pre-wrap",wordBreak:"break-word"},children:n.rationale}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:t.sm,fontFamily:E,fontSize:p.xs,color:r.sub},children:[n.scope&&e.jsxs("span",{children:["scope: ",n.scope]}),e.jsx("span",{children:pt(n.ts)})]}),Array.isArray(n.alternatives)&&n.alternatives.length>0&&e.jsxs("details",{style:{marginTop:t.xs},children:[e.jsxs("summary",{style:{fontFamily:E,fontSize:p.xs,color:r.sub,cursor:"pointer"},children:["考虑过的替代 (",n.alternatives.length,")"]}),e.jsx("ul",{style:{margin:`${t.xs}px 0 0 0`,paddingLeft:t.xl,fontFamily:z,fontSize:p.xs,color:r.text4,lineHeight:1.5},children:n.alternatives.map((s,i)=>e.jsx("li",{children:s},i))})]})]})}function Nn({entry:n}){var s;return e.jsxs("div",{style:{padding:t.md,marginBottom:t.sm,borderLeft:`2px solid ${r.borderMd}`,paddingLeft:t.md},children:[e.jsxs("div",{style:{fontFamily:E,fontSize:p.xs,color:r.sub,marginBottom:t.xs,letterSpacing:"0.04em"},children:[((s=n.source)==null?void 0:s.toUpperCase())||"COMPACT"," · ",pt(n.ts),n.trigger&&n.trigger!=="auto"&&` · ${n.trigger}`]}),e.jsx("div",{style:{fontFamily:z,fontSize:p.xs,color:r.text4,lineHeight:1.5,whiteSpace:"pre-wrap"},children:n.summary})]})}function pt(n){if(!n)return"";try{return new Date(n).toLocaleString("zh-CN",{month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"})}catch{return n}}function qn({anchorRef:n,onClose:s,project:i,deckSpec:l,projectId:d,sessionId:f,decisionsReloadKey:m=0,onA11yClick:c,tweaksEnabled:y=null,onTweaksEnabledChange:u=null}){const a=N(j=>j.canvasFont),w=N(j=>j.followAgent),x=N(j=>j.setFollowAgent),v=N(j=>j.setCanvasFont),k=o.useRef(null),[b,F]=o.useState(!1),W=$e(n,360);return o.useEffect(()=>{const j=T=>{var H;k.current&&!k.current.contains(T.target)&&!((H=n==null?void 0:n.current)!=null&&H.contains(T.target))&&(s==null||s())};return window.addEventListener("mousedown",j),()=>window.removeEventListener("mousedown",j)},[s,n]),o.useEffect(()=>{const j=T=>{T.key==="Escape"&&(s==null||s())};return window.addEventListener("keydown",j),()=>window.removeEventListener("keydown",j)},[s]),e.jsxs("div",{ref:k,style:{...W,width:360,background:r.bgWhite,borderRadius:2,boxShadow:"0 2px 4px rgba(0,0,0,0.04), 0 8px 20px rgba(0,0,0,0.08), 0 24px 48px rgba(0,0,0,0.10), inset 0 1px 0 rgba(255,255,255,0.8)",zIndex:60,display:"flex",flexDirection:"column",overflow:"hidden"},children:[e.jsxs("div",{style:{padding:`${t.md}px ${t.lg}px`,display:"flex",alignItems:"center",gap:t.sm,borderBottom:`1px solid ${r.borderLt}`,flexShrink:0},children:[e.jsx(lt,{size:12,color:r.text4}),e.jsx("span",{style:{fontFamily:E,fontSize:p.sm,fontWeight:500,color:r.text},children:"System"})]}),e.jsxs("div",{style:{flex:1,minHeight:0,overflowY:"auto"},children:[c&&e.jsxs("div",{style:{padding:`${t.md}px ${t.lg}px`,borderBottom:`1px solid ${r.borderLt}`,display:"flex",flexDirection:"column",gap:t.sm},children:[e.jsx("div",{style:{fontFamily:E,fontSize:p.xs,color:r.sub,textTransform:"uppercase",letterSpacing:"0.05em"},children:"Canvas 工具"}),e.jsx("div",{style:{display:"flex",gap:t.sm},children:e.jsxs("button",{onClick:()=>{c(),s==null||s()},style:Un,title:"无障碍审查(mock)",children:[e.jsx(Ln,{size:11})," A11y"]})})]}),u&&e.jsxs("div",{style:{padding:`${t.md}px ${t.lg}px`,borderBottom:`1px solid ${r.borderLt}`,display:"flex",alignItems:"center",gap:t.sm},children:[e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{fontFamily:z,fontSize:p.sm,color:r.text},children:"Tweaks 模式"}),e.jsx("div",{style:{fontFamily:z,fontSize:p.xs,color:r.sub,marginTop:2,lineHeight:1.5},children:y?"开:agent 会主动把核心参数做成控件让你拖":"关:不暴露控件,改样式走对话"})]}),e.jsx(Ze,{checked:!!y,onChange:u})]}),e.jsxs("div",{style:{padding:`${t.md}px ${t.lg}px`,borderBottom:`1px solid ${r.borderLt}`,display:"flex",alignItems:"center",gap:t.sm},children:[e.jsxs("div",{style:{flex:1,minWidth:0},children:[e.jsx("div",{style:{fontFamily:z,fontSize:p.sm,color:r.text},children:"镜头跟随 agent"}),e.jsx("div",{style:{fontFamily:z,fontSize:p.xs,color:r.sub,marginTop:2,lineHeight:1.5},children:w?"开:agent 换到哪个产物,镜头就飞过去(你操作后 8 秒内不抢)":"关:镜头只听你的;agent 在动什么看光圈"})]}),e.jsx(Ze,{checked:w,onChange:x})]}),e.jsxs("div",{style:{padding:`${t.md}px ${t.lg}px`,borderBottom:`1px solid ${r.borderLt}`},children:[e.jsx("div",{style:{fontFamily:z,fontSize:p.sm,color:r.text},children:"画布手写字体"}),e.jsx("div",{style:{fontFamily:z,fontSize:p.xs,color:r.sub,marginTop:2,marginBottom:t.sm},children:"用「文字」工具(T)在画布上写字时用这个"}),e.jsx("div",{style:{display:"flex",gap:t.xs,flexWrap:"wrap"},children:Object.entries(Kt).map(([j,T])=>e.jsx("button",{onClick:()=>v({...a,font:j}),style:{padding:`${t.xs}px ${t.sm}px`,borderRadius:$.md,cursor:"pointer",fontFamily:Vt[j],fontSize:p.sm,border:`1px solid ${a.font===j?r.text:r.borderLt}`,background:a.font===j?r.text:"transparent",color:a.font===j?r.bg:r.text2},children:T},j))}),e.jsx("div",{style:{display:"flex",gap:t.xs,marginTop:t.sm},children:Object.entries(Xt).map(([j,T])=>e.jsx("button",{onClick:()=>v({...a,size:j}),style:{padding:`${t.xs}px ${t.sm}px`,borderRadius:$.md,cursor:"pointer",fontFamily:z,fontSize:p.xs,border:`1px solid ${a.size===j?r.text:r.borderLt}`,background:a.size===j?r.text:"transparent",color:a.size===j?r.bg:r.text2},children:T},j))})]}),e.jsx(Hn,{project:i,deckSpec:l,projectId:d}),e.jsxs("div",{style:{borderTop:`1px solid ${r.borderLt}`,padding:`${t.md}px ${t.lg}px 0`},children:[e.jsxs("button",{onClick:()=>F(j=>!j),style:{display:"flex",alignItems:"center",gap:t.xs,width:"100%",padding:`${t.xs}px 0`,background:"transparent",border:"none",fontFamily:E,fontSize:p.xs,color:r.sub,textTransform:"uppercase",letterSpacing:"0.05em",cursor:"pointer"},children:[e.jsx(ot,{size:11,style:{transform:b?"rotate(90deg)":"rotate(0deg)",transition:"transform 0.12s ease"}}),"项目档案 — Decisions / History"]}),!b&&e.jsx("div",{style:{fontFamily:z,fontSize:p.xs,color:r.sub,padding:`${t.xs}px 0 ${t.md}px ${t.lg}px`,lineHeight:1.5},children:"展开看 agent 记录的设计决策 + compact 摘要历史。"})]}),b&&e.jsx("div",{style:{background:"rgba(0,0,0,0.015)",margin:`0 ${t.lg}px ${t.md}px`,border:`1px solid ${r.borderLt}`,borderRadius:$.md},children:e.jsx(_n,{projectId:d,sessionId:f,reloadKey:m})})]}),e.jsx("div",{style:{padding:`${t.sm}px ${t.lg}px`,background:r.bgCard,borderTop:`1px solid ${r.borderLt}`,fontFamily:z,fontSize:p.xs,color:r.sub,lineHeight:1.5,flexShrink:0},children:"spec 不可在此编辑 — 改 spec 跟 agent 说,触发新 run。"})]})}function Ze({checked:n,onChange:s,title:i}){return e.jsx("button",{onClick:()=>s==null?void 0:s(!n),title:i,style:{width:28,height:16,padding:0,border:"none",borderRadius:$.lg,background:n?r.text:"rgba(0,0,0,0.18)",position:"relative",cursor:"pointer",transition:"background 0.15s",flexShrink:0},children:e.jsx("span",{style:{position:"absolute",top:2,left:n?14:2,width:12,height:12,borderRadius:$.round,background:r.bgWhite,boxShadow:at.crispSm,transition:"left 0.15s"}})})}const Un={padding:`${t.xs+1}px ${t.md}px`,fontFamily:E,fontSize:p.xs,color:r.text4,background:"rgba(0,0,0,0.04)",border:"none",borderRadius:$.sm,display:"inline-flex",alignItems:"center",gap:t.xs,cursor:"pointer"};function Gn({comments:n=[],iframeDoc:s,iframeRef:i,anchorRef:l,onClose:d,onResolveComment:f,onDeleteComment:m,onSelectAnchor:c}){const y=o.useRef(null),u=$e(l,380);o.useEffect(()=>{const x=v=>{var k;y.current&&!y.current.contains(v.target)&&!((k=l==null?void 0:l.current)!=null&&k.contains(v.target))&&(d==null||d())};return window.addEventListener("mousedown",x),()=>window.removeEventListener("mousedown",x)},[d,l]),o.useEffect(()=>{const x=v=>{v.key==="Escape"&&(d==null||d())};return window.addEventListener("keydown",x),()=>window.removeEventListener("keydown",x)},[d]);const a=o.useMemo(()=>{const x=new Map;for(const k of n){const b=s&&k.anchor?dt(k.anchor,s.body):null,F=b?Yt(b):null,W=(F==null?void 0:F.index)??null;x.has(W)||x.set(W,[]),x.get(W).push({comment:k,el:b})}return[...x.entries()].sort((k,b)=>k[0]===null?1:b[0]===null?-1:k[0]-b[0])},[n,s]),w=x=>{if(x.el)try{x.el.scrollIntoView({behavior:"smooth",block:"center"})}catch{}c&&c(x.comment.anchor)};return e.jsxs("div",{ref:y,style:{...u,width:380,background:r.bgWhite,borderRadius:2,boxShadow:"0 2px 4px rgba(0,0,0,0.04), 0 8px 20px rgba(0,0,0,0.08), 0 24px 48px rgba(0,0,0,0.10), inset 0 1px 0 rgba(255,255,255,0.8)",zIndex:60,display:"flex",flexDirection:"column",overflow:"hidden"},children:[e.jsxs("div",{style:{padding:`${t.md}px ${t.lg}px`,display:"flex",alignItems:"center",gap:t.sm,borderBottom:`1px solid ${r.borderLt}`,flexShrink:0},children:[e.jsx(xt,{size:12,color:r.text4}),e.jsxs("span",{style:{fontFamily:E,fontSize:p.sm,fontWeight:500,color:r.text,flex:1},children:["全部评论 ",n.length>0&&`(${n.length})`]}),e.jsx("button",{onClick:d,title:"关闭",style:{padding:t.xs,color:r.text5,borderRadius:$.xs,background:"transparent",border:"none",cursor:"pointer"},children:e.jsx(ue,{size:12})})]}),e.jsx("div",{style:{flex:1,minHeight:0,overflowY:"auto"},children:n.length===0?e.jsxs("div",{style:{padding:`${t.lg+t.md}px ${t.lg}px`,fontFamily:z,fontSize:p.sm,color:r.sub,textAlign:"center",lineHeight:1.6},children:["还没有评论。",e.jsx("br",{}),"点击画布上任意元素 → 在弹出的浮卡里写评论。"]}):a.map(([x,v])=>e.jsxs("div",{style:{padding:`${t.md}px ${t.lg}px`,borderBottom:`1px solid ${r.borderLt}`},children:[e.jsxs("div",{style:{fontFamily:E,fontSize:p.xs,color:r.sub,textTransform:"uppercase",letterSpacing:"0.05em",marginBottom:t.sm},children:[x===null?"未定位":`第 ${x+1} 页`," · ",v.length," 条"]}),e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:t.xs},children:v.map(({comment:k,el:b})=>e.jsx(Kn,{comment:k,available:!!b,onClick:()=>w({comment:k,el:b}),onResolve:()=>f==null?void 0:f(k.id),onDelete:()=>m==null?void 0:m(k.id)},k.id))})]},x??"orphan"))}),e.jsx("div",{style:{padding:`${t.sm}px ${t.lg}px`,background:r.bgCard,borderTop:`1px solid ${r.borderLt}`,fontFamily:z,fontSize:p.xs,color:r.sub,lineHeight:1.5,flexShrink:0},children:"点击评论 → 跳到对应元素;evaluator 切到 edit 模式后可在浮卡续写。"})]})}function Kn({comment:n,available:s,onClick:i,onResolve:l,onDelete:d}){var m,c,y;const f=n.status==="resolved";return e.jsxs("div",{style:{padding:`${t.sm}px ${t.md}px`,background:r.bgWhite,boxShadow:ze.far,borderRadius:$.md,display:"flex",flexDirection:"column",gap:t.xs,opacity:f?.5:1,cursor:s?"pointer":"default",transition:"background 0.1s"},onClick:s?i:void 0,onMouseEnter:u=>{s&&(u.currentTarget.style.background="rgba(0,0,0,0.02)")},onMouseLeave:u=>{u.currentTarget.style.background=r.bgWhite},children:[e.jsx("div",{style:{fontFamily:z,fontSize:p.sm,color:r.text2,lineHeight:1.5,textDecoration:f?"line-through":"none",whiteSpace:"pre-wrap",wordBreak:"break-word"},children:n.text}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:t.xs,fontFamily:E,fontSize:p.xs,color:r.sub},children:[s?e.jsxs(e.Fragment,{children:[e.jsx(xn,{size:9}),e.jsx("span",{children:((m=n.anchor)==null?void 0:m.dataId)||((y=(c=n.anchor)==null?void 0:c.path)==null?void 0:y.split(">").pop())||"元素"})]}):e.jsx("span",{style:{fontStyle:"italic"},children:"元素已不在"}),e.jsx("span",{style:{flex:1}}),e.jsx("button",{onClick:u=>{u.stopPropagation(),l()},title:f?"取消解决":"标记解决",style:{padding:t.xxs,color:r.sub,borderRadius:$.xs,background:"transparent",border:"none",cursor:"pointer"},children:e.jsx(st,{size:11})}),e.jsx("button",{onClick:u=>{u.stopPropagation(),d()},title:"删除",style:{padding:t.xxs,color:r.sub,borderRadius:$.xs,background:"transparent",border:"none",cursor:"pointer"},children:e.jsx(it,{size:11})})]})]})}function Vn({iframeDoc:n,active:s}){const[i,l]=o.useState([]),[d,f]=o.useState(0);o.useEffect(()=>{if(!s||!n){l([]);return}let c=null;const y=()=>{const a=Array.from(n.querySelectorAll("section[data-page]"));if(l(a),c&&c.disconnect(),!!a.length)try{c=new(n.defaultView||window).IntersectionObserver(w=>{const x=w.filter(k=>k.isIntersecting).sort((k,b)=>b.intersectionRatio-k.intersectionRatio)[0];if(!x)return;const v=a.indexOf(x.target);v>=0&&f(v)},{threshold:[.35,.6]}),a.forEach(w=>c.observe(w))}catch{}};y();let u=null;try{u=new(n.defaultView||window).MutationObserver(()=>y()),n.body&&u.observe(n.body,{childList:!0,subtree:!0})}catch{}return()=>{c==null||c.disconnect(),u==null||u.disconnect()}},[n,s]);const m=o.useCallback(c=>{f(y=>{const u=Math.max(0,Math.min(i.length-1,y+c)),a=i[u];if(a)try{a.scrollIntoView({behavior:"smooth",block:"start"})}catch{}return u})},[i]);return o.useEffect(()=>{if(!s||i.length<2)return;const c=u=>{if(u.key!=="ArrowLeft"&&u.key!=="ArrowRight")return;const a=u.target;a&&(a.tagName==="INPUT"||a.tagName==="TEXTAREA"||a.isContentEditable)||(u.preventDefault(),m(u.key==="ArrowRight"?1:-1))};window.addEventListener("keydown",c);let y=null;try{y=n,y==null||y.addEventListener("keydown",c)}catch{}return()=>{window.removeEventListener("keydown",c);try{y==null||y.removeEventListener("keydown",c)}catch{}}},[s,i.length,m,n]),!s||i.length<2?null:e.jsxs("div",{style:{position:"absolute",bottom:16,left:"50%",transform:"translateX(-50%)",display:"flex",alignItems:"center",gap:t.xxs,background:"rgba(28,24,18,0.86)",borderRadius:$.pill,padding:t.xs,boxShadow:"0 6px 22px rgba(0,0,0,0.22)",zIndex:30,backdropFilter:"blur(6px)"},children:[e.jsx(Ce,{onClick:()=>m(-1),disabled:d===0,icon:Zt,title:"上一页(←)"}),e.jsxs("span",{style:{fontFamily:E,fontSize:p.sm,color:ct.ink,padding:`0 ${t.md}px`,minWidth:52,textAlign:"center",userSelect:"none"},children:[d+1," / ",i.length]}),e.jsx(Ce,{onClick:()=>m(1),disabled:d===i.length-1,icon:ot,title:"下一页(→)"})]})}function Ce({onClick:n,disabled:s,icon:i,title:l}){return e.jsx("button",{onClick:n,disabled:s,title:l,style:{display:"flex",alignItems:"center",justifyContent:"center",width:26,height:26,borderRadius:$.round,border:"none",background:"transparent",color:s?"rgba(232,226,210,0.3)":ct.ink,cursor:s?"default":"pointer",transition:"background 0.15s"},onMouseEnter:d=>{s||(d.currentTarget.style.background="rgba(255,255,255,0.12)")},onMouseLeave:d=>{d.currentTarget.style.background="transparent"},children:e.jsx(i,{size:15})})}function Xn({edits:n=[],onApply:s,onUndo:i,onClearAll:l,canUndo:d=!1,isRunning:f=!1}){const m=n.length;if(m===0)return null;const c=Yn(n);return e.jsxs("div",{style:{position:"absolute",bottom:16,left:"50%",transform:"translateX(-50%)",background:"rgba(45, 36, 24, 0.94)",color:r.btnText,borderRadius:$.lg,padding:`${t.sm}px ${t.lg}px`,display:"flex",alignItems:"center",gap:t.md,fontFamily:E,fontSize:p.xs,boxShadow:"0 8px 32px rgba(0,0,0,0.25), 0 2px 8px rgba(0,0,0,0.18)",zIndex:50,backdropFilter:"blur(8px)",WebkitBackdropFilter:"blur(8px)"},children:[e.jsx("span",{style:{padding:`${t.xxs}px ${t.md}px`,background:"rgba(245, 240, 232, 0.15)",borderRadius:$.xl,fontWeight:600,minWidth:22,textAlign:"center"},children:m}),e.jsxs("span",{style:{color:"#dfd6c6"},children:[c," 待应用"]}),e.jsx("div",{style:{width:1,height:18,background:"rgba(245,240,232,0.2)"}}),e.jsxs("button",{onClick:i,disabled:!d,title:d?"撤销最近一次 (Cmd+Z)":"无可撤销操作",style:{...Je,opacity:d?1:.4,cursor:d?"pointer":"not-allowed"},children:[e.jsx(Ct,{size:11})," 撤销"]}),e.jsxs("button",{onClick:l,title:"全部撤销 — 把所有 pending 编辑清掉",style:Je,children:[e.jsx(ue,{size:11})," 全部撤销"]}),e.jsxs("button",{onClick:s,disabled:f,title:f?"agent 正在跑,等当前 run 结束":"让 agent 把这些调整落到源代码",style:{...Zn,opacity:f?.6:1,cursor:f?"wait":"pointer"},children:[e.jsx(Jt,{size:11})," 应用"]})]})}function Yn(n){const s={move:0,style:0,dup:0,del:0};for(const l of n)l.kind==="pending-move"?s.move++:l.kind==="pending-style"?s.style++:l.kind==="pending-duplicate"?s.dup++:l.kind==="pending-delete"&&s.del++;const i=[];return s.move&&i.push(`${s.move} 拖动`),s.dup&&i.push(`${s.dup} 复制`),s.style&&i.push(`${s.style} 样式`),s.del&&i.push(`${s.del} 删除`),i.join(" · ")||"调整"}const Je={display:"inline-flex",alignItems:"center",gap:t.xs,padding:`${t.xs}px ${t.base}px`,fontFamily:"inherit",fontSize:p.xs,color:r.btnText,background:"transparent",border:"1px solid rgba(245,240,232,0.25)",borderRadius:$.sm},Zn={display:"inline-flex",alignItems:"center",gap:t.xs,padding:"5px 14px",fontFamily:"inherit",fontSize:p.xs,fontWeight:600,color:r.btn,background:r.btnText,border:"none",borderRadius:$.sm,boxShadow:"0 1px 2px rgba(0,0,0,0.15)"},Qe=ut.purple,Cn=Qt(ut.purple,.18);function Jn({edits:n=[],iframeRef:s,zoom:i=1}){const[,l]=o.useState(0);o.useEffect(()=>{if(n.length===0)return;const x=s==null?void 0:s.current;if(!x)return;const v=x.contentWindow;if(!v)return;let k=null;const b=()=>{k||(k=requestAnimationFrame(()=>{k=null,l(F=>F+1)}))};try{v.addEventListener("scroll",b,{passive:!0,capture:!0}),v.addEventListener("resize",b),window.addEventListener("resize",b)}catch{}return()=>{try{v.removeEventListener("scroll",b,{capture:!0}),v.removeEventListener("resize",b),window.removeEventListener("resize",b)}catch{}k&&cancelAnimationFrame(k)}},[n.length,s]);const d=o.useMemo(()=>n.filter(x=>(x.kind==="pending-move"||x.kind==="pending-duplicate")&&x.reactMount===!0),[n]);if(d.length===0||!(s!=null&&s.current))return null;const f=s.current,m=f.contentDocument;if(!m)return null;const c=f.getBoundingClientRect(),y=pn(f);if(!y)return null;const u=f.contentWindow,a=(u==null?void 0:u.innerWidth)??c.width/i,w=(u==null?void 0:u.innerHeight)??c.height/i;return e.jsx(e.Fragment,{children:d.map((x,v)=>{var h;const k=dt(x.anchor,m.body);if(!k)return null;const b=k.getBoundingClientRect();if(b.bottom<=0||b.top>=w||b.right<=0||b.left>=a)return null;const{top:F,left:W}=gn(y,b.left,b.top,i),j=b.width*i,T=b.height*i,H=(h=x.aiContext)!=null&&h.targetContainerTag?`${x.aiContext.targetContainerTag}`:"目标容器";return e.jsxs("div",{style:{position:"absolute",top:0,left:0,pointerEvents:"none"},children:[e.jsx("div",{style:{position:"absolute",pointerEvents:"none",top:F-3,left:W-3,width:j+6,height:T+6,border:`2px dashed ${Qe}`,borderRadius:$.sm,boxShadow:`0 0 0 3px ${Cn}`,zIndex:9}}),e.jsxs("div",{style:{position:"absolute",pointerEvents:"none",top:F-12,left:W+j-8,transform:"translateX(-100%)",padding:`${t.xxs}px ${t.sm}px`,fontFamily:E,fontSize:p.xs,lineHeight:"14px",fontWeight:500,color:r.bgWhite,background:Qe,borderRadius:$.xs,whiteSpace:"nowrap",boxShadow:at.crisp,zIndex:11,maxWidth:Math.max(120,j),overflow:"hidden",textOverflow:"ellipsis"},children:[x.kind==="pending-duplicate"?"复制到":"移到"," <",H,">"]})]},v)})})}const je={"16:9":{w:1920,h:1080},"16:10":{w:1920,h:1200},"9:16":{w:1080,h:1920},"4:3":{w:1440,h:1080}},Se="16:9";function Qn(n){if(!n||typeof n!="string")return Se;const s=n.match(/<div\b[^>]*class\s*=\s*['"][^'"]*__nd-deck-wrap[^'"]*['"][^>]*data-deck-aspect\s*=\s*['"]([^'"]+)['"]/i)||n.match(/<div\b[^>]*data-deck-aspect\s*=\s*['"]([^'"]+)['"][^>]*class\s*=\s*['"][^'"]*__nd-deck-wrap[^'"]*['"]/i);return s&&je[s[1]]?s[1]:Se}function ss({tab:n,onTabChange:s,onClose:i,artifactExports:l=null,onExport:d=null,onToolbarGroups:f=null,title:m="幻灯",htmlSrc:c,htmlContent:y,selectedAnchor:u,onSelectChange:a,onTextEdit:w,onIframeReady:x,candidates:v,activeCandidateId:k,onSelectCandidate:b,onAddCandidate:F,onRemoveCandidate:W,onRenameCandidate:j,project:T,deckSpec:H,projectId:h,sessionId:P,decisionsReloadKey:D,comments:_=[],onAddComment:ee,onResolveComment:K,onDeleteComment:V,onRegionComment:L=null,tweaksAvailable:q=!1,pendingEdits:xe=[],onCommitMove:U,onCommitFreePosition:pe,onSubmitDragNote:gt,lastPendingEditId:ft=null,onApplyPendingEdits:ht,onUndoPending:mt,onClearAllPending:yt,canUndoPending:bt=!1,isStreaming:G=!1}){var Me,Re,Oe,He,De,_e,Ie,Ne,qe,Ue;const{panels:ge,setPanelVisible:wt}=en(),fe=!!((Me=ge==null?void 0:ge.tweaks)!=null&&Me.visible),vt=()=>wt("tweaks",!fe),[X,Ee]=o.useState(!1);o.useEffect(()=>{G&&n==="drag"&&(s==null||s("edit"))},[G,n,s]);const kt=o.useRef(null),[he,jt]=o.useState(!1),[Le,St]=o.useState(null),[zt,Fe]=o.useState(!1),[Y,te]=o.useState("fit"),[Z,$t]=o.useState({width:0,height:0}),[Ae,Et]=o.useState(0),[me,ne]=o.useState(""),[ye,se]=o.useState(!1),[I,Te]=o.useState(null),[be,re]=o.useState(!1),[C,ie]=o.useState(!1),[J,le]=o.useState(!1),[oe,Pe]=o.useState(!0),O=o.useRef(null),we=o.useRef(null),We=o.useRef(null);o.useEffect(()=>{if(!h||!P)return;let g=!1;return Ke.read(h).then(({config:S})=>{g||S&&typeof S.tweaks_mode_enabled=="boolean"&&Pe(S.tweaks_mode_enabled)}).catch(()=>{}),()=>{g=!0}},[h,P]);const Lt=o.useCallback(g=>{Pe(g),!(!h||!P)&&Ke.patch(h,{tweaks_mode_enabled:g}).catch(()=>{})},[h,P]);o.useEffect(()=>{const g=O.current;if(!g)return;const S=()=>{const M=g.getBoundingClientRect();$t(R=>R.width===M.width&&R.height===M.height?R:{width:M.width,height:M.height})};S();let A=null;try{A=new ResizeObserver(S),A.observe(g)}catch{}return window.addEventListener("resize",S),()=>{A&&A.disconnect(),window.removeEventListener("resize",S)}},[n]);const Be=Qn(me),ae=je[Be]||je[Se],B=Y==="fit"?Z.width>0&&Z.height>0?Math.min(Z.width/ae.w,Z.height/ae.h):1:Y,Ft=v&&v.length>=1;o.useEffect(()=>{se(!1),Te(null)},[k]),o.useEffect(()=>{const g=A=>{var R;if(A.key!=="Escape")return;const M=A.target;((R=M==null?void 0:M.getAttribute)==null?void 0:R.call(M,"contenteditable"))!=="true"&&(C||be||J||(u?a==null||a(null):i==null||i()))};window.addEventListener("keydown",g);let S=null;try{S=I,S==null||S.addEventListener("keydown",g)}catch{}return()=>{window.removeEventListener("keydown",g);try{S==null||S.removeEventListener("keydown",g)}catch{}}},[u,I,a,i,C,be,J]),o.useEffect(()=>{const g=S=>{const A=S==null?void 0:S.data;if(!(!A||typeof A!="object")&&A.type==="canvas-page-change"&&Number.isFinite(A.page))try{window.dispatchEvent(new CustomEvent("nd-canvas-page-change",{detail:{page:A.page}}))}catch{}};return window.addEventListener("message",g),()=>window.removeEventListener("message",g)},[]),o.useEffect(()=>{if(!c){ne(y||""),se(!1);return}fetch(c).then(g=>g.text()).then(g=>{ne(g),se(!1)}).catch(()=>ne("<!-- 无法加载源码 -->"))},[c,y,Ae]);const At=g=>{a==null||a((g==null?void 0:g.anchor)||null)},Tt=g=>{w==null||w(g),console.log("[direct edit]",g)},Pt=o.useCallback(g=>{ne(g),se(!0)},[]),Wt=o.useCallback(g=>{try{Te(g.contentDocument)}catch{}x==null||x(g)},[x]),Bt=()=>{Et(g=>g+1),a==null||a(null)},Mt=()=>{re(!1),ie(!1),le(g=>!g)},Rt=()=>{re(!1),le(!1),ie(g=>!g)},ve=Array.isArray(_)?_.filter(g=>g.status!=="resolved").length:0,Ot=o.useMemo(()=>[cn({kind:"deck",exports:l,onExport:d}),{id:"mode",type:"mode",value:n,onChange:g=>{s==null||s(g),a==null||a(null)},items:[{id:"preview",icon:$n,label:"预览",title:"看成品(左右键翻页)"},{id:"edit",icon:tn,label:"编辑",title:"双击文字直接改 · 单击元素弹评论卡"},{id:"drag",icon:nn,label:"拖拽",disabled:G,title:G?"agent 正在跑,拖拽暂停以免和它抢同一份源码":"拖动元素调布局"},{id:"region",icon:En,label:"圈选",disabled:!L,title:L?"框一块地方说事 —— 框住谁、当时长什么样、你想说什么,一起交给 agent":"要先开一个会话才能把圈选交给 agent"},{id:"code",icon:sn,label:"源码",title:"看/改这一份 HTML"}]},n==="drag"&&{id:"dragmode",items:[{id:"free",icon:rn,label:X?"自由":"嵌入",active:X,title:X?"自由摆放 · 松手落到像素位置(P 切回嵌入)":"嵌入 · 松手按 DOM 树插进容器(P 切自由摆放)",onClick:()=>Ee(g=>!g)}]},n!=="code"&&{id:"zoom",items:[{id:"zoomOut",icon:ln,title:"缩小",onClick:()=>te(Math.max(.25,B-.1))},{id:"zoomLevel",label:`${Math.round(B*100)}%`,active:Y==="fit",title:Y==="fit"?"当前自适应窗口":"点一下自适应窗口",onClick:()=>te("fit")},{id:"zoomIn",icon:tt,title:"放大",onClick:()=>te(Math.min(3,B+.1))},{id:"fit",icon:on,title:"自适应窗口",onClick:()=>te("fit")}]},{id:"actions",items:[{id:"tweaks",icon:an,active:fe,disabled:!oe,title:oe?q?"Tweaks:拖控件实时改样式":"Tweaks(agent 还没暴露参数 — 跟它说一句让它 expose_tweaks)":"Tweaks 模式已关(在 System 里开)",onClick:vt},{id:"comment",icon:xt,label:ve>0?String(ve):void 0,active:J,btnRef:We,title:"这一份上已有的评论",onClick:Mt},{id:"reload",icon:dn,title:"重载(agent 改完没刷新时用)",onClick:Bt},{id:"system",icon:lt,active:C,btnRef:we,title:"System:项目档案 / Tweaks 模式 / A11y",onClick:Rt}]}].filter(Boolean),[n,G,X,Y,B,fe,oe,q,J,C,ve,L,l,d]);return e.jsxs(un,{kind:"deck",title:m,subtitle:Be,onClose:i,escToClose:!1,groups:Ot,onToolbarGroups:f,headerExtra:Ft?e.jsx(Pn,{candidates:v,activeId:k,onSelect:b,onAdd:F,onRemove:W,onRename:j}):null,children:[(n==="edit"||n==="preview"||n==="drag"||n==="region")&&e.jsxs("div",{ref:O,style:{flex:1,minHeight:0,position:"relative",display:"flex",flexDirection:"column"},children:[e.jsx(Tn,{src:ye?void 0:c,srcDoc:ye?me:c?void 0:y,mode:n==="drag"||n==="region"?"preview":n,onSelect:At,onTextEdit:Tt,onIframeReady:Wt,zoom:B,deckW:ae.w,deckH:ae.h},`${k||"default"}-${Ae}-${ye?"doc":"src"}`),n==="edit"&&u&&e.jsx(fn,{selectedAnchor:u,iframeRef:{current:(Re=O.current)==null?void 0:Re.querySelector("iframe")},zoom:B}),e.jsx(hn,{comments:_,iframeRef:{current:(Oe=O.current)==null?void 0:Oe.querySelector("iframe")},zoom:B,onSelectAnchor:g=>{n!=="edit"&&(s==null||s("edit")),a==null||a(g)}}),e.jsx(Jn,{edits:xe,iframeRef:{current:(He=O.current)==null?void 0:He.querySelector("iframe")},zoom:B}),e.jsx(Vn,{iframeDoc:I,active:n==="preview"}),e.jsx(mn,{active:n==="drag"&&!he,iframeRef:{current:(De=O.current)==null?void 0:De.querySelector("iframe")},zoom:B,pickDragSource:yn,isDragging:he}),e.jsx(bn,{active:n==="drag",iframeRef:{current:(_e=O.current)==null?void 0:_e.querySelector("iframe")},zoom:B,freeMode:X,onFreeModeChange:Ee,apiRef:kt,onDraggingChange:jt,onSelectionChange:g=>{St(g),g&&Fe(!0)},onCommitMove:(g,S)=>{var M;if(S.duplicate){const R=(M=S.sourceEl)==null?void 0:M.cloneNode(!0);let Ge=()=>{};if(R&&S.targetContainer){try{R.removeAttribute("data-anchor")}catch{}S.beforeEl&&S.beforeEl.parentNode===S.targetContainer?S.targetContainer.insertBefore(R,S.beforeEl):S.targetContainer.appendChild(R),Ge=()=>{try{R.remove()}catch{}}}U==null||U(g,Ge);return}const A=vn({iframeDoc:I,sourceEl:S.sourceEl,targetContainer:S.targetContainer,beforeEl:S.beforeEl});U==null||U(g,A==null?void 0:A.revert)},onCommitFreePosition:(g,S)=>{const A=wn({sourceEl:S.sourceEl,parentEl:S.parentEl,styleDelta:g.styleDelta,runtimeLocks:g.runtimeLocks,parentNeedsRelative:g.parentNeedsRelative});pe==null||pe(g,A==null?void 0:A.revert)}}),e.jsx(kn,{active:n==="drag"&&zt&&!he&&!!Le,iframeRef:{current:(Ie=O.current)==null?void 0:Ie.querySelector("iframe")},zoom:B,sourceEl:Le,hasPendingEditId:!!ft,onSubmit:gt,onDismiss:()=>Fe(!1)}),n==="edit"&&u&&I&&e.jsx(jn,{selectedAnchor:u,iframeDoc:I,iframeRef:{current:(Ne=O.current)==null?void 0:Ne.querySelector("iframe")},iframeRect:Z,zoom:B,comments:_,onClose:()=>a==null?void 0:a(null),onAddComment:ee,onResolveComment:K,onDeleteComment:V}),e.jsx(Sn,{active:n==="region"&&!!L,iframeRef:{current:(qe=O.current)==null?void 0:qe.querySelector("iframe")},zoom:B,onSubmit:g=>L==null?void 0:L(g),onExit:()=>s==null?void 0:s("preview")}),e.jsx(Xn,{edits:xe,onApply:ht,onUndo:mt,onClearAll:yt,canUndo:bt,isRunning:G})]}),n==="code"&&e.jsx(zn,{value:me,onChange:Pt,readOnly:!1}),be&&e.jsx(Mn,{anchorRef:we,onClose:()=>re(!1),iframeDoc:I}),C&&e.jsx(qn,{anchorRef:we,onClose:()=>ie(!1),project:T,deckSpec:H,projectId:h,sessionId:P,decisionsReloadKey:D,onA11yClick:()=>{ie(!1),re(!0)},tweaksEnabled:oe,onTweaksEnabledChange:Lt}),J&&e.jsx(Gn,{comments:_,iframeDoc:I,iframeRef:{current:(Ue=O.current)==null?void 0:Ue.querySelector("iframe")},anchorRef:We,onClose:()=>le(!1),onResolveComment:K,onDeleteComment:V,onSelectAnchor:g=>{le(!1),n!=="edit"&&(s==null||s("edit")),a==null||a(g)}})]})}export{ss as default};