synabun 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +205 -662
- package/LICENSE-COMMERCIAL.md +33 -0
- package/NOTICE +17 -0
- package/README.md +175 -853
- package/THIRD-PARTY-LICENSES.md +136 -0
- package/benchmarks/browser-live-results.json +79 -0
- package/benchmarks/browser-v2-results.json +258 -0
- package/benchmarks/fixtures/browser/README.md +57 -0
- package/benchmarks/fixtures/browser/page-state.json +132 -0
- package/benchmarks/fixtures/browser/social.json +92 -0
- package/benchmarks/fixtures/browser/target.json +319 -0
- package/benchmarks/fixtures/desktop/README.md +58 -0
- package/benchmarks/fixtures/desktop/abstention.json +59 -0
- package/benchmarks/fixtures/desktop/adversarial.json +22 -0
- package/benchmarks/fixtures/desktop/forbidden.json +93 -0
- package/benchmarks/fixtures/desktop/generate.mjs +1023 -0
- package/benchmarks/fixtures/desktop/target.json +136 -0
- package/bin/synabun.js +30 -0
- package/docs/whatsapp.md +325 -0
- package/hooks/claude-code/post-plan.mjs +613 -391
- package/hooks/claude-code/post-remember.mjs +246 -122
- package/hooks/claude-code/pre-compact.mjs +171 -110
- package/hooks/claude-code/pre-task.mjs +133 -0
- package/hooks/claude-code/pre-websearch.mjs +15 -4
- package/hooks/claude-code/prompt-origin.mjs +94 -0
- package/hooks/claude-code/prompt-submit.mjs +1244 -861
- package/hooks/claude-code/session-start.mjs +114 -156
- package/hooks/claude-code/shared.mjs +1228 -368
- package/hooks/claude-code/state.mjs +167 -0
- package/hooks/claude-code/stop.mjs +515 -140
- package/hooks/claude-code/subagent-start.mjs +154 -0
- package/hooks/claude-code/subagent-stop.mjs +102 -0
- package/hooks/claude-code/task-gate.mjs +150 -0
- package/launcher.mjs +40 -0
- package/lib/claude-hooks.js +137 -0
- package/lib/claude-usage-audit.js +141 -0
- package/lib/client-config-repair.js +145 -0
- package/lib/data-home-migration.js +502 -0
- package/lib/desktop-pwa.js +206 -0
- package/lib/external-tools.js +221 -0
- package/lib/first-launch-protection.js +245 -0
- package/lib/packaged-runtime.js +110 -0
- package/lib/paths.d.ts +21 -0
- package/lib/paths.js +103 -0
- package/lib/start-launcher.js +1309 -0
- package/mcp-server/dist/config.d.ts +22 -0
- package/mcp-server/dist/config.d.ts.map +1 -0
- package/mcp-server/dist/config.js +109 -0
- package/mcp-server/dist/config.js.map +1 -0
- package/mcp-server/dist/http.d.ts +30 -0
- package/mcp-server/dist/http.d.ts.map +1 -0
- package/mcp-server/dist/http.js +276 -0
- package/mcp-server/dist/http.js.map +1 -0
- package/mcp-server/dist/index.d.ts +48 -0
- package/mcp-server/dist/index.d.ts.map +1 -0
- package/mcp-server/dist/index.js +538 -0
- package/mcp-server/dist/index.js.map +1 -0
- package/mcp-server/dist/preload-http.d.ts +3 -0
- package/mcp-server/dist/preload-http.d.ts.map +1 -0
- package/mcp-server/dist/preload-http.js +28 -0
- package/mcp-server/dist/preload-http.js.map +1 -0
- package/mcp-server/dist/preload.d.ts +3 -0
- package/mcp-server/dist/preload.d.ts.map +1 -0
- package/mcp-server/dist/preload.js +32 -0
- package/mcp-server/dist/preload.js.map +1 -0
- package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts +19 -0
- package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts.map +1 -0
- package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js +403 -0
- package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js.map +1 -0
- package/mcp-server/dist/scripts/setup-embeddings.d.ts +3 -0
- package/mcp-server/dist/scripts/setup-embeddings.d.ts.map +1 -0
- package/mcp-server/dist/scripts/setup-embeddings.js +15 -0
- package/mcp-server/dist/scripts/setup-embeddings.js.map +1 -0
- package/mcp-server/dist/services/assistant-judgments.d.ts +79 -0
- package/mcp-server/dist/services/assistant-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/assistant-judgments.js +128 -0
- package/mcp-server/dist/services/assistant-judgments.js.map +1 -0
- package/mcp-server/dist/services/browser-assist-gate.d.ts +314 -0
- package/mcp-server/dist/services/browser-assist-gate.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-assist-gate.js +344 -0
- package/mcp-server/dist/services/browser-assist-gate.js.map +1 -0
- package/mcp-server/dist/services/browser-assist.d.ts +104 -0
- package/mcp-server/dist/services/browser-assist.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-assist.js +282 -0
- package/mcp-server/dist/services/browser-assist.js.map +1 -0
- package/mcp-server/dist/services/browser-bench.d.ts +772 -0
- package/mcp-server/dist/services/browser-bench.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-bench.js +424 -0
- package/mcp-server/dist/services/browser-bench.js.map +1 -0
- package/mcp-server/dist/services/browser-judgments.d.ts +196 -0
- package/mcp-server/dist/services/browser-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-judgments.js +363 -0
- package/mcp-server/dist/services/browser-judgments.js.map +1 -0
- package/mcp-server/dist/services/browser-risk.d.ts +166 -0
- package/mcp-server/dist/services/browser-risk.d.ts.map +1 -0
- package/mcp-server/dist/services/browser-risk.js +458 -0
- package/mcp-server/dist/services/browser-risk.js.map +1 -0
- package/mcp-server/dist/services/categories.d.ts +61 -0
- package/mcp-server/dist/services/categories.d.ts.map +1 -0
- package/mcp-server/dist/services/categories.js +377 -0
- package/mcp-server/dist/services/categories.js.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts +49 -0
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -0
- package/mcp-server/dist/services/codex-config-heal.js +346 -0
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -0
- package/mcp-server/dist/services/desktop-assist-gate.d.ts +198 -0
- package/mcp-server/dist/services/desktop-assist-gate.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-assist-gate.js +202 -0
- package/mcp-server/dist/services/desktop-assist-gate.js.map +1 -0
- package/mcp-server/dist/services/desktop-assist.d.ts +104 -0
- package/mcp-server/dist/services/desktop-assist.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-assist.js +266 -0
- package/mcp-server/dist/services/desktop-assist.js.map +1 -0
- package/mcp-server/dist/services/desktop-bench.d.ts +553 -0
- package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-bench.js +311 -0
- package/mcp-server/dist/services/desktop-bench.js.map +1 -0
- package/mcp-server/dist/services/desktop-client.d.ts +23 -0
- package/mcp-server/dist/services/desktop-client.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-client.js +79 -0
- package/mcp-server/dist/services/desktop-client.js.map +1 -0
- package/mcp-server/dist/services/desktop-judgments.d.ts +125 -0
- package/mcp-server/dist/services/desktop-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-judgments.js +177 -0
- package/mcp-server/dist/services/desktop-judgments.js.map +1 -0
- package/mcp-server/dist/services/desktop-risk.d.ts +283 -0
- package/mcp-server/dist/services/desktop-risk.d.ts.map +1 -0
- package/mcp-server/dist/services/desktop-risk.js +605 -0
- package/mcp-server/dist/services/desktop-risk.js.map +1 -0
- package/mcp-server/dist/services/discord.d.ts +112 -0
- package/mcp-server/dist/services/discord.d.ts.map +1 -0
- package/mcp-server/dist/services/discord.js +477 -0
- package/mcp-server/dist/services/discord.js.map +1 -0
- package/mcp-server/dist/services/edit-stale.d.ts +81 -0
- package/mcp-server/dist/services/edit-stale.d.ts.map +1 -0
- package/mcp-server/dist/services/edit-stale.js +358 -0
- package/mcp-server/dist/services/edit-stale.js.map +1 -0
- package/mcp-server/dist/services/embedding-service.d.ts +16 -0
- package/mcp-server/dist/services/embedding-service.d.ts.map +1 -0
- package/mcp-server/dist/services/embedding-service.js +126 -0
- package/mcp-server/dist/services/embedding-service.js.map +1 -0
- package/mcp-server/dist/services/embedding-worker.d.ts +2 -0
- package/mcp-server/dist/services/embedding-worker.d.ts.map +1 -0
- package/mcp-server/dist/services/embedding-worker.js +52 -0
- package/mcp-server/dist/services/embedding-worker.js.map +1 -0
- package/mcp-server/dist/services/fb-regions.d.ts +71 -0
- package/mcp-server/dist/services/fb-regions.d.ts.map +1 -0
- package/mcp-server/dist/services/fb-regions.js +211 -0
- package/mcp-server/dist/services/fb-regions.js.map +1 -0
- package/mcp-server/dist/services/file-checksums.d.ts +22 -0
- package/mcp-server/dist/services/file-checksums.d.ts.map +1 -0
- package/mcp-server/dist/services/file-checksums.js +55 -0
- package/mcp-server/dist/services/file-checksums.js.map +1 -0
- package/mcp-server/dist/services/file-excerpt.d.ts +24 -0
- package/mcp-server/dist/services/file-excerpt.d.ts.map +1 -0
- package/mcp-server/dist/services/file-excerpt.js +65 -0
- package/mcp-server/dist/services/file-excerpt.js.map +1 -0
- package/mcp-server/dist/services/identity.d.ts +124 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -0
- package/mcp-server/dist/services/identity.js +241 -0
- package/mcp-server/dist/services/identity.js.map +1 -0
- package/mcp-server/dist/services/local-embeddings.d.ts +2 -0
- package/mcp-server/dist/services/local-embeddings.d.ts.map +1 -0
- package/mcp-server/dist/services/local-embeddings.js +3 -0
- package/mcp-server/dist/services/local-embeddings.js.map +1 -0
- package/mcp-server/dist/services/memory-client.d.ts +4 -0
- package/mcp-server/dist/services/memory-client.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-client.js +27 -0
- package/mcp-server/dist/services/memory-client.js.map +1 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +135 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-judge-backfill.js +510 -0
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -0
- package/mcp-server/dist/services/memory-judgments.d.ts +362 -0
- package/mcp-server/dist/services/memory-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-judgments.js +674 -0
- package/mcp-server/dist/services/memory-judgments.js.map +1 -0
- package/mcp-server/dist/services/memory-maintenance.d.ts +108 -0
- package/mcp-server/dist/services/memory-maintenance.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-maintenance.js +403 -0
- package/mcp-server/dist/services/memory-maintenance.js.map +1 -0
- package/mcp-server/dist/services/memory-retrieval.d.ts +66 -0
- package/mcp-server/dist/services/memory-retrieval.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-retrieval.js +412 -0
- package/mcp-server/dist/services/memory-retrieval.js.map +1 -0
- package/mcp-server/dist/services/memory-schema.d.ts +16 -0
- package/mcp-server/dist/services/memory-schema.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-schema.js +282 -0
- package/mcp-server/dist/services/memory-schema.js.map +1 -0
- package/mcp-server/dist/services/memory-sessions.d.ts +24 -0
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-sessions.js +110 -0
- package/mcp-server/dist/services/memory-sessions.js.map +1 -0
- package/mcp-server/dist/services/memory-triage.d.ts +60 -0
- package/mcp-server/dist/services/memory-triage.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-triage.js +189 -0
- package/mcp-server/dist/services/memory-triage.js.map +1 -0
- package/mcp-server/dist/services/memory-verification.d.ts +24 -0
- package/mcp-server/dist/services/memory-verification.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-verification.js +92 -0
- package/mcp-server/dist/services/memory-verification.js.map +1 -0
- package/mcp-server/dist/services/memory-writer.d.ts +26 -0
- package/mcp-server/dist/services/memory-writer.d.ts.map +1 -0
- package/mcp-server/dist/services/memory-writer.js +126 -0
- package/mcp-server/dist/services/memory-writer.js.map +1 -0
- package/mcp-server/dist/services/neural-interface.d.ts +439 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -0
- package/mcp-server/dist/services/neural-interface.js +1135 -0
- package/mcp-server/dist/services/neural-interface.js.map +1 -0
- package/mcp-server/dist/services/profiles.d.ts +120 -0
- package/mcp-server/dist/services/profiles.d.ts.map +1 -0
- package/mcp-server/dist/services/profiles.js +387 -0
- package/mcp-server/dist/services/profiles.js.map +1 -0
- package/mcp-server/dist/services/secret-gate.d.ts +29 -0
- package/mcp-server/dist/services/secret-gate.d.ts.map +1 -0
- package/mcp-server/dist/services/secret-gate.js +43 -0
- package/mcp-server/dist/services/secret-gate.js.map +1 -0
- package/mcp-server/dist/services/server-instructions.d.ts +10 -0
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -0
- package/mcp-server/dist/services/server-instructions.js +101 -0
- package/mcp-server/dist/services/server-instructions.js.map +1 -0
- package/mcp-server/dist/services/sqlite.d.ts +235 -0
- package/mcp-server/dist/services/sqlite.d.ts.map +1 -0
- package/mcp-server/dist/services/sqlite.js +1185 -0
- package/mcp-server/dist/services/sqlite.js.map +1 -0
- package/mcp-server/dist/services/typesafe-bench.d.ts +99 -0
- package/mcp-server/dist/services/typesafe-bench.d.ts.map +1 -0
- package/mcp-server/dist/services/typesafe-bench.js +218 -0
- package/mcp-server/dist/services/typesafe-bench.js.map +1 -0
- package/mcp-server/dist/services/typesafe-config.d.ts +342 -0
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -0
- package/mcp-server/dist/services/typesafe-config.js +746 -0
- package/mcp-server/dist/services/typesafe-config.js.map +1 -0
- package/mcp-server/dist/services/typesafe.d.ts +213 -0
- package/mcp-server/dist/services/typesafe.d.ts.map +1 -0
- package/mcp-server/dist/services/typesafe.js +330 -0
- package/mcp-server/dist/services/typesafe.js.map +1 -0
- package/mcp-server/dist/services/vector-cache.d.ts +68 -0
- package/mcp-server/dist/services/vector-cache.d.ts.map +1 -0
- package/mcp-server/dist/services/vector-cache.js +271 -0
- package/mcp-server/dist/services/vector-cache.js.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +305 -0
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -0
- package/mcp-server/dist/services/whiteboard-geometry.js +666 -0
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -0
- package/mcp-server/dist/tools/agents.d.ts +212 -0
- package/mcp-server/dist/tools/agents.d.ts.map +1 -0
- package/mcp-server/dist/tools/agents.js +407 -0
- package/mcp-server/dist/tools/agents.js.map +1 -0
- package/mcp-server/dist/tools/bluesky.d.ts +39 -0
- package/mcp-server/dist/tools/bluesky.d.ts.map +1 -0
- package/mcp-server/dist/tools/bluesky.js +1144 -0
- package/mcp-server/dist/tools/bluesky.js.map +1 -0
- package/mcp-server/dist/tools/browser-advanced.d.ts +58 -0
- package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-advanced.js +106 -0
- package/mcp-server/dist/tools/browser-advanced.js.map +1 -0
- package/mcp-server/dist/tools/browser-batch.d.ts +99 -0
- package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-batch.js +187 -0
- package/mcp-server/dist/tools/browser-batch.js.map +1 -0
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts +21 -0
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-cheatsheet.js +144 -0
- package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -0
- package/mcp-server/dist/tools/browser-interact.d.ts +239 -0
- package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-interact.js +300 -0
- package/mcp-server/dist/tools/browser-interact.js.map +1 -0
- package/mcp-server/dist/tools/browser-navigate.d.ts +90 -0
- package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-navigate.js +142 -0
- package/mcp-server/dist/tools/browser-navigate.js.map +1 -0
- package/mcp-server/dist/tools/browser-observe.d.ts +561 -0
- package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser-observe.js +2009 -0
- package/mcp-server/dist/tools/browser-observe.js.map +1 -0
- package/mcp-server/dist/tools/browser.d.ts +34 -0
- package/mcp-server/dist/tools/browser.d.ts.map +1 -0
- package/mcp-server/dist/tools/browser.js +112 -0
- package/mcp-server/dist/tools/browser.js.map +1 -0
- package/mcp-server/dist/tools/card-tools.d.ts +82 -0
- package/mcp-server/dist/tools/card-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/card-tools.js +137 -0
- package/mcp-server/dist/tools/card-tools.js.map +1 -0
- package/mcp-server/dist/tools/card.d.ts +8 -0
- package/mcp-server/dist/tools/card.d.ts.map +1 -0
- package/mcp-server/dist/tools/card.js +16 -0
- package/mcp-server/dist/tools/card.js.map +1 -0
- package/mcp-server/dist/tools/category.d.ts +32 -0
- package/mcp-server/dist/tools/category.d.ts.map +1 -0
- package/mcp-server/dist/tools/category.js +290 -0
- package/mcp-server/dist/tools/category.js.map +1 -0
- package/mcp-server/dist/tools/choice.d.ts +160 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -0
- package/mcp-server/dist/tools/choice.js +80 -0
- package/mcp-server/dist/tools/choice.js.map +1 -0
- package/mcp-server/dist/tools/computer.d.ts +71 -0
- package/mcp-server/dist/tools/computer.d.ts.map +1 -0
- package/mcp-server/dist/tools/computer.js +174 -0
- package/mcp-server/dist/tools/computer.js.map +1 -0
- package/mcp-server/dist/tools/discord-channel.d.ts +44 -0
- package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-channel.js +236 -0
- package/mcp-server/dist/tools/discord-channel.js.map +1 -0
- package/mcp-server/dist/tools/discord-guild.d.ts +20 -0
- package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-guild.js +138 -0
- package/mcp-server/dist/tools/discord-guild.js.map +1 -0
- package/mcp-server/dist/tools/discord-member.d.ts +26 -0
- package/mcp-server/dist/tools/discord-member.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-member.js +122 -0
- package/mcp-server/dist/tools/discord-member.js.map +1 -0
- package/mcp-server/dist/tools/discord-message.d.ts +36 -0
- package/mcp-server/dist/tools/discord-message.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-message.js +184 -0
- package/mcp-server/dist/tools/discord-message.js.map +1 -0
- package/mcp-server/dist/tools/discord-onboarding.d.ts +32 -0
- package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-onboarding.js +179 -0
- package/mcp-server/dist/tools/discord-onboarding.js.map +1 -0
- package/mcp-server/dist/tools/discord-role.d.ts +34 -0
- package/mcp-server/dist/tools/discord-role.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-role.js +178 -0
- package/mcp-server/dist/tools/discord-role.js.map +1 -0
- package/mcp-server/dist/tools/discord-thread.d.ts +28 -0
- package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-thread.js +88 -0
- package/mcp-server/dist/tools/discord-thread.js.map +1 -0
- package/mcp-server/dist/tools/discord-webhook.d.ts +32 -0
- package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord-webhook.js +154 -0
- package/mcp-server/dist/tools/discord-webhook.js.map +1 -0
- package/mcp-server/dist/tools/discord.d.ts +7 -0
- package/mcp-server/dist/tools/discord.d.ts.map +1 -0
- package/mcp-server/dist/tools/discord.js +25 -0
- package/mcp-server/dist/tools/discord.js.map +1 -0
- package/mcp-server/dist/tools/fb-groups.d.ts +98 -0
- package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/fb-groups.js +369 -0
- package/mcp-server/dist/tools/fb-groups.js.map +1 -0
- package/mcp-server/dist/tools/forget.d.ts +14 -0
- package/mcp-server/dist/tools/forget.d.ts.map +1 -0
- package/mcp-server/dist/tools/forget.js +26 -0
- package/mcp-server/dist/tools/forget.js.map +1 -0
- package/mcp-server/dist/tools/git-tools.d.ts +24 -0
- package/mcp-server/dist/tools/git-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/git-tools.js +107 -0
- package/mcp-server/dist/tools/git-tools.js.map +1 -0
- package/mcp-server/dist/tools/git.d.ts +7 -0
- package/mcp-server/dist/tools/git.d.ts.map +1 -0
- package/mcp-server/dist/tools/git.js +11 -0
- package/mcp-server/dist/tools/git.js.map +1 -0
- package/mcp-server/dist/tools/gsc/experience.d.ts +135 -0
- package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/experience.js +351 -0
- package/mcp-server/dist/tools/gsc/experience.js.map +1 -0
- package/mcp-server/dist/tools/gsc/helpers.d.ts +78 -0
- package/mcp-server/dist/tools/gsc/helpers.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/helpers.js +258 -0
- package/mcp-server/dist/tools/gsc/helpers.js.map +1 -0
- package/mcp-server/dist/tools/gsc/indexing.d.ts +121 -0
- package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/indexing.js +357 -0
- package/mcp-server/dist/tools/gsc/indexing.js.map +1 -0
- package/mcp-server/dist/tools/gsc/inspect.d.ts +78 -0
- package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/inspect.js +321 -0
- package/mcp-server/dist/tools/gsc/inspect.js.map +1 -0
- package/mcp-server/dist/tools/gsc/navigate.d.ts +44 -0
- package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/navigate.js +197 -0
- package/mcp-server/dist/tools/gsc/navigate.js.map +1 -0
- package/mcp-server/dist/tools/gsc/performance.d.ts +84 -0
- package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/performance.js +274 -0
- package/mcp-server/dist/tools/gsc/performance.js.map +1 -0
- package/mcp-server/dist/tools/gsc/settings.d.ts +153 -0
- package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc/settings.js +425 -0
- package/mcp-server/dist/tools/gsc/settings.js.map +1 -0
- package/mcp-server/dist/tools/gsc.d.ts +7 -0
- package/mcp-server/dist/tools/gsc.d.ts.map +1 -0
- package/mcp-server/dist/tools/gsc.js +51 -0
- package/mcp-server/dist/tools/gsc.js.map +1 -0
- package/mcp-server/dist/tools/image-tools.d.ts +24 -0
- package/mcp-server/dist/tools/image-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/image-tools.js +80 -0
- package/mcp-server/dist/tools/image-tools.js.map +1 -0
- package/mcp-server/dist/tools/image.d.ts +10 -0
- package/mcp-server/dist/tools/image.d.ts.map +1 -0
- package/mcp-server/dist/tools/image.js +14 -0
- package/mcp-server/dist/tools/image.js.map +1 -0
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +97 -0
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/leonardo-browser-tools.js +228 -0
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -0
- package/mcp-server/dist/tools/leonardo.d.ts +13 -0
- package/mcp-server/dist/tools/leonardo.d.ts.map +1 -0
- package/mcp-server/dist/tools/leonardo.js +21 -0
- package/mcp-server/dist/tools/leonardo.js.map +1 -0
- package/mcp-server/dist/tools/loop.d.ts +30 -0
- package/mcp-server/dist/tools/loop.d.ts.map +1 -0
- package/mcp-server/dist/tools/loop.js +401 -0
- package/mcp-server/dist/tools/loop.js.map +1 -0
- package/mcp-server/dist/tools/memories.d.ts +56 -0
- package/mcp-server/dist/tools/memories.d.ts.map +1 -0
- package/mcp-server/dist/tools/memories.js +171 -0
- package/mcp-server/dist/tools/memories.js.map +1 -0
- package/mcp-server/dist/tools/morelogin.d.ts +24 -0
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -0
- package/mcp-server/dist/tools/morelogin.js +79 -0
- package/mcp-server/dist/tools/morelogin.js.map +1 -0
- package/mcp-server/dist/tools/profile.d.ts +17 -0
- package/mcp-server/dist/tools/profile.d.ts.map +1 -0
- package/mcp-server/dist/tools/profile.js +114 -0
- package/mcp-server/dist/tools/profile.js.map +1 -0
- package/mcp-server/dist/tools/recall.d.ts +71 -0
- package/mcp-server/dist/tools/recall.d.ts.map +1 -0
- package/mcp-server/dist/tools/recall.js +147 -0
- package/mcp-server/dist/tools/recall.js.map +1 -0
- package/mcp-server/dist/tools/reflect.d.ts +56 -0
- package/mcp-server/dist/tools/reflect.d.ts.map +1 -0
- package/mcp-server/dist/tools/reflect.js +157 -0
- package/mcp-server/dist/tools/reflect.js.map +1 -0
- package/mcp-server/dist/tools/remember.d.ts +62 -0
- package/mcp-server/dist/tools/remember.d.ts.map +1 -0
- package/mcp-server/dist/tools/remember.js +201 -0
- package/mcp-server/dist/tools/remember.js.map +1 -0
- package/mcp-server/dist/tools/response.d.ts +23 -0
- package/mcp-server/dist/tools/response.d.ts.map +1 -0
- package/mcp-server/dist/tools/response.js +24 -0
- package/mcp-server/dist/tools/response.js.map +1 -0
- package/mcp-server/dist/tools/restore.d.ts +14 -0
- package/mcp-server/dist/tools/restore.d.ts.map +1 -0
- package/mcp-server/dist/tools/restore.js +23 -0
- package/mcp-server/dist/tools/restore.js.map +1 -0
- package/mcp-server/dist/tools/style-guide-tools.d.ts +51 -0
- package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/style-guide-tools.js +231 -0
- package/mcp-server/dist/tools/style-guide-tools.js.map +1 -0
- package/mcp-server/dist/tools/style-guide.d.ts +8 -0
- package/mcp-server/dist/tools/style-guide.d.ts.map +1 -0
- package/mcp-server/dist/tools/style-guide.js +12 -0
- package/mcp-server/dist/tools/style-guide.js.map +1 -0
- package/mcp-server/dist/tools/sync.d.ts +20 -0
- package/mcp-server/dist/tools/sync.d.ts.map +1 -0
- package/mcp-server/dist/tools/sync.js +156 -0
- package/mcp-server/dist/tools/sync.js.map +1 -0
- package/mcp-server/dist/tools/tictactoe-tools.d.ts +18 -0
- package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/tictactoe-tools.js +78 -0
- package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -0
- package/mcp-server/dist/tools/tictactoe.d.ts +7 -0
- package/mcp-server/dist/tools/tictactoe.d.ts.map +1 -0
- package/mcp-server/dist/tools/tictactoe.js +11 -0
- package/mcp-server/dist/tools/tictactoe.js.map +1 -0
- package/mcp-server/dist/tools/utils.d.ts +19 -0
- package/mcp-server/dist/tools/utils.d.ts.map +1 -0
- package/mcp-server/dist/tools/utils.js +54 -0
- package/mcp-server/dist/tools/utils.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts +88 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.js +337 -0
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +326 -0
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/whiteboard-tools.js +222 -0
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard.d.ts +8 -0
- package/mcp-server/dist/tools/whiteboard.d.ts.map +1 -0
- package/mcp-server/dist/tools/whiteboard.js +16 -0
- package/mcp-server/dist/tools/whiteboard.js.map +1 -0
- package/mcp-server/dist/tools/youtube/analytics.d.ts +46 -0
- package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/analytics.js +83 -0
- package/mcp-server/dist/tools/youtube/analytics.js.map +1 -0
- package/mcp-server/dist/tools/youtube/dedup.d.ts +35 -0
- package/mcp-server/dist/tools/youtube/dedup.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/dedup.js +56 -0
- package/mcp-server/dist/tools/youtube/dedup.js.map +1 -0
- package/mcp-server/dist/tools/youtube/download.d.ts +31 -0
- package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/download.js +30 -0
- package/mcp-server/dist/tools/youtube/download.js.map +1 -0
- package/mcp-server/dist/tools/youtube/helpers.d.ts +66 -0
- package/mcp-server/dist/tools/youtube/helpers.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/helpers.js +143 -0
- package/mcp-server/dist/tools/youtube/helpers.js.map +1 -0
- package/mcp-server/dist/tools/youtube/media.d.ts +43 -0
- package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/media.js +97 -0
- package/mcp-server/dist/tools/youtube/media.js.map +1 -0
- package/mcp-server/dist/tools/youtube/metadata.d.ts +33 -0
- package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/metadata.js +106 -0
- package/mcp-server/dist/tools/youtube/metadata.js.map +1 -0
- package/mcp-server/dist/tools/youtube/navigate.d.ts +30 -0
- package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/navigate.js +71 -0
- package/mcp-server/dist/tools/youtube/navigate.js.map +1 -0
- package/mcp-server/dist/tools/youtube/playlist.d.ts +29 -0
- package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/playlist.js +128 -0
- package/mcp-server/dist/tools/youtube/playlist.js.map +1 -0
- package/mcp-server/dist/tools/youtube/schedule.d.ts +31 -0
- package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/schedule.js +147 -0
- package/mcp-server/dist/tools/youtube/schedule.js.map +1 -0
- package/mcp-server/dist/tools/youtube/source.d.ts +36 -0
- package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/source.js +85 -0
- package/mcp-server/dist/tools/youtube/source.js.map +1 -0
- package/mcp-server/dist/tools/youtube/upload.d.ts +53 -0
- package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube/upload.js +255 -0
- package/mcp-server/dist/tools/youtube/upload.js.map +1 -0
- package/mcp-server/dist/tools/youtube.d.ts +12 -0
- package/mcp-server/dist/tools/youtube.d.ts.map +1 -0
- package/mcp-server/dist/tools/youtube.js +38 -0
- package/mcp-server/dist/tools/youtube.js.map +1 -0
- package/mcp-server/dist/tui.d.ts +3 -0
- package/mcp-server/dist/tui.d.ts.map +1 -0
- package/mcp-server/dist/tui.js +806 -0
- package/mcp-server/dist/tui.js.map +1 -0
- package/mcp-server/dist/types.d.ts +77 -0
- package/mcp-server/dist/types.d.ts.map +1 -0
- package/mcp-server/dist/types.js +2 -0
- package/mcp-server/dist/types.js.map +1 -0
- package/mcp-server/package-lock.json +4798 -0
- package/mcp-server/package.json +55 -38
- package/mcp-server/run.mjs +12 -2
- package/mcp-server/src/config.ts +48 -3
- package/mcp-server/src/http.ts +296 -93
- package/mcp-server/src/index.ts +546 -203
- package/mcp-server/src/preload-http.ts +19 -18
- package/mcp-server/src/preload.ts +19 -18
- package/mcp-server/src/scripts/setup-embeddings.ts +9 -0
- package/mcp-server/src/services/assistant-judgments.ts +157 -0
- package/mcp-server/src/services/browser-assist-gate.ts +383 -0
- package/mcp-server/src/services/browser-assist.ts +267 -0
- package/mcp-server/src/services/browser-bench.ts +477 -0
- package/mcp-server/src/services/browser-judgments.ts +414 -0
- package/mcp-server/src/services/browser-risk.ts +484 -0
- package/mcp-server/src/services/categories.ts +36 -19
- package/mcp-server/src/services/codex-config-heal.ts +403 -0
- package/mcp-server/src/services/desktop-assist-gate.ts +251 -0
- package/mcp-server/src/services/desktop-assist.ts +308 -0
- package/mcp-server/src/services/desktop-bench.ts +372 -0
- package/mcp-server/src/services/desktop-client.ts +76 -0
- package/mcp-server/src/services/desktop-judgments.ts +218 -0
- package/mcp-server/src/services/desktop-risk.ts +743 -0
- package/mcp-server/src/services/edit-stale.ts +299 -0
- package/mcp-server/src/services/embedding-service.ts +108 -0
- package/mcp-server/src/services/embedding-worker.ts +46 -0
- package/mcp-server/src/services/fb-regions.ts +221 -0
- package/mcp-server/src/services/file-checksums.ts +26 -9
- package/mcp-server/src/services/file-excerpt.ts +59 -0
- package/mcp-server/src/services/identity.ts +291 -0
- package/mcp-server/src/services/local-embeddings.ts +2 -77
- package/mcp-server/src/services/memory-client.ts +23 -0
- package/mcp-server/src/services/memory-judge-backfill.ts +462 -0
- package/mcp-server/src/services/memory-judgments.ts +901 -0
- package/mcp-server/src/services/memory-maintenance.ts +360 -0
- package/mcp-server/src/services/memory-retrieval.ts +349 -0
- package/mcp-server/src/services/memory-schema.ts +280 -0
- package/mcp-server/src/services/memory-sessions.ts +87 -0
- package/mcp-server/src/services/memory-triage.ts +182 -0
- package/mcp-server/src/services/memory-verification.ts +68 -0
- package/mcp-server/src/services/memory-writer.ts +125 -0
- package/mcp-server/src/services/neural-interface.ts +1217 -73
- package/mcp-server/src/services/profiles.ts +428 -0
- package/mcp-server/src/services/secret-gate.ts +52 -0
- package/mcp-server/src/services/server-instructions.ts +122 -0
- package/mcp-server/src/services/sqlite.ts +1456 -823
- package/mcp-server/src/services/typesafe-bench.ts +247 -0
- package/mcp-server/src/services/typesafe-config.ts +790 -0
- package/mcp-server/src/services/typesafe.ts +390 -0
- package/mcp-server/src/services/vector-cache.ts +255 -0
- package/mcp-server/src/services/whiteboard-geometry.ts +711 -0
- package/mcp-server/src/tools/agents.ts +539 -0
- package/mcp-server/src/tools/bluesky.ts +1186 -0
- package/mcp-server/src/tools/browser-advanced.ts +15 -6
- package/mcp-server/src/tools/browser-batch.ts +178 -0
- package/mcp-server/src/tools/browser-cheatsheet.ts +155 -0
- package/mcp-server/src/tools/browser-interact.ts +362 -255
- package/mcp-server/src/tools/browser-navigate.ts +164 -77
- package/mcp-server/src/tools/browser-observe.ts +2222 -1330
- package/mcp-server/src/tools/browser.ts +181 -107
- package/mcp-server/src/tools/card.ts +7 -5
- package/mcp-server/src/tools/choice.ts +103 -0
- package/mcp-server/src/tools/computer.ts +190 -0
- package/mcp-server/src/tools/discord.ts +10 -23
- package/mcp-server/src/tools/fb-groups.ts +387 -0
- package/mcp-server/src/tools/forget.ts +1 -1
- package/mcp-server/src/tools/git.ts +3 -1
- package/mcp-server/src/tools/gsc/experience.ts +440 -0
- package/mcp-server/src/tools/gsc/helpers.ts +288 -0
- package/mcp-server/src/tools/gsc/indexing.ts +495 -0
- package/mcp-server/src/tools/gsc/inspect.ts +391 -0
- package/mcp-server/src/tools/gsc/navigate.ts +236 -0
- package/mcp-server/src/tools/gsc/performance.ts +352 -0
- package/mcp-server/src/tools/gsc/settings.ts +564 -0
- package/mcp-server/src/tools/gsc.ts +95 -0
- package/mcp-server/src/tools/image.ts +3 -1
- package/mcp-server/src/tools/leonardo-browser-tools.ts +47 -37
- package/mcp-server/src/tools/leonardo.ts +7 -5
- package/mcp-server/src/tools/loop.ts +118 -25
- package/mcp-server/src/tools/memories.ts +65 -5
- package/mcp-server/src/tools/morelogin.ts +81 -0
- package/mcp-server/src/tools/profile.ts +122 -0
- package/mcp-server/src/tools/recall.ts +105 -261
- package/mcp-server/src/tools/reflect.ts +171 -145
- package/mcp-server/src/tools/remember.ts +223 -115
- package/mcp-server/src/tools/style-guide-tools.ts +250 -0
- package/mcp-server/src/tools/style-guide.ts +16 -0
- package/mcp-server/src/tools/sync.ts +66 -9
- package/mcp-server/src/tools/tictactoe.ts +3 -1
- package/mcp-server/src/tools/utils.ts +22 -0
- package/mcp-server/src/tools/whiteboard-format.ts +394 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +191 -183
- package/mcp-server/src/tools/whiteboard.ts +7 -5
- package/mcp-server/src/tools/youtube/analytics.ts +106 -0
- package/mcp-server/src/tools/youtube/dedup.ts +57 -0
- package/mcp-server/src/tools/youtube/download.ts +41 -0
- package/mcp-server/src/tools/youtube/helpers.ts +183 -0
- package/mcp-server/src/tools/youtube/media.ts +109 -0
- package/mcp-server/src/tools/youtube/metadata.ts +112 -0
- package/mcp-server/src/tools/youtube/navigate.ts +81 -0
- package/mcp-server/src/tools/youtube/playlist.ts +134 -0
- package/mcp-server/src/tools/youtube/schedule.ts +158 -0
- package/mcp-server/src/tools/youtube/source.ts +101 -0
- package/mcp-server/src/tools/youtube/upload.ts +296 -0
- package/mcp-server/src/tools/youtube.ts +47 -0
- package/mcp-server/src/tui.ts +8 -9
- package/neural-interface/games/TicTacToe/1.svg +1 -0
- package/neural-interface/games/TicTacToe/2.svg +1 -0
- package/neural-interface/games/TicTacToe/3.svg +1 -0
- package/neural-interface/games/TicTacToe/4.svg +1 -0
- package/neural-interface/games/TicTacToe/5.svg +1 -0
- package/neural-interface/games/TicTacToe/6.svg +1 -0
- package/neural-interface/games/TicTacToe/7.svg +1 -0
- package/neural-interface/games/TicTacToe/8.svg +1 -0
- package/neural-interface/games/TicTacToe/9.svg +1 -0
- package/neural-interface/games/TicTacToe/Board.svg +1 -0
- package/neural-interface/games/TicTacToe/Circle.svg +1 -0
- package/neural-interface/games/TicTacToe/Cross.svg +1 -0
- package/neural-interface/i18n/en.json +7022 -0
- package/neural-interface/i18n/pt-BR.json +7026 -0
- package/neural-interface/lib/assistant-api.js +1005 -0
- package/neural-interface/lib/assistant-attachments.js +121 -0
- package/neural-interface/lib/assistant-brains/claude.js +207 -0
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +43 -0
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +56 -0
- package/neural-interface/lib/assistant-brains/codex.js +213 -0
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +47 -0
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +62 -0
- package/neural-interface/lib/assistant-brains/opencode.js +320 -0
- package/neural-interface/lib/assistant-budget.js +318 -0
- package/neural-interface/lib/assistant-catalog.js +731 -0
- package/neural-interface/lib/assistant-clarify.js +664 -0
- package/neural-interface/lib/assistant-config.js +232 -0
- package/neural-interface/lib/assistant-dispatch.js +2744 -0
- package/neural-interface/lib/assistant-envelope.js +582 -0
- package/neural-interface/lib/assistant-evidence.js +236 -0
- package/neural-interface/lib/assistant-hidden-models.js +272 -0
- package/neural-interface/lib/assistant-jev.js +117 -0
- package/neural-interface/lib/assistant-judgments.js +111 -0
- package/neural-interface/lib/assistant-media.js +335 -0
- package/neural-interface/lib/assistant-memory.js +398 -0
- package/neural-interface/lib/assistant-persona.js +444 -0
- package/neural-interface/lib/assistant-plan-permissions.js +448 -0
- package/neural-interface/lib/assistant-playbooks/design.md +87 -0
- package/neural-interface/lib/assistant-playbooks.js +72 -0
- package/neural-interface/lib/assistant-pricing.js +159 -0
- package/neural-interface/lib/assistant-route-gate-codex.js +163 -0
- package/neural-interface/lib/assistant-route-gate.js +212 -0
- package/neural-interface/lib/assistant-router.js +1324 -0
- package/neural-interface/lib/assistant-runtime.js +3337 -0
- package/neural-interface/lib/assistant-task-prompt.js +472 -0
- package/neural-interface/lib/assistant-usage.js +1688 -0
- package/neural-interface/lib/augmented-path.js +70 -0
- package/neural-interface/lib/automation-storage-migration.js +54 -0
- package/neural-interface/lib/backup-service.js +851 -0
- package/neural-interface/lib/backup-zip-reader.js +262 -0
- package/neural-interface/lib/browser-assist-api.js +41 -0
- package/neural-interface/lib/browser-console.js +176 -0
- package/neural-interface/lib/browser-execution.js +241 -0
- package/neural-interface/lib/browser-extraction.js +191 -0
- package/neural-interface/lib/browser-language.js +83 -0
- package/neural-interface/lib/browser-screenshot.js +399 -0
- package/neural-interface/lib/browser-semantic-context.js +639 -0
- package/neural-interface/lib/browser-snapshots.js +192 -0
- package/neural-interface/lib/browser-tab-recovery.js +119 -0
- package/neural-interface/lib/browser-targets.js +70 -0
- package/neural-interface/lib/browser-tool-policy.js +926 -0
- package/neural-interface/lib/claude-accounts.js +230 -0
- package/neural-interface/lib/claude-agent-bridge.js +3203 -0
- package/neural-interface/lib/claude-bypass-policy.js +99 -0
- package/neural-interface/lib/claude-executable.js +241 -0
- package/neural-interface/lib/claude-history.js +429 -0
- package/neural-interface/lib/claude-model-catalog.js +444 -0
- package/neural-interface/lib/claude-panel-requests.js +165 -0
- package/neural-interface/lib/claude-panel-session.js +272 -0
- package/neural-interface/lib/claude-permission-rules.js +153 -0
- package/neural-interface/lib/claude-session-cleanup.js +71 -0
- package/neural-interface/lib/claude-session-list.js +82 -0
- package/neural-interface/lib/claude-session-ops.js +140 -0
- package/neural-interface/lib/claude-settings-view.js +51 -0
- package/neural-interface/lib/claude-skin-connect.js +79 -0
- package/neural-interface/lib/claude-stall-policy.js +7 -0
- package/neural-interface/lib/claude-temporary.js +435 -0
- package/neural-interface/lib/cli-update-planner.js +312 -0
- package/neural-interface/lib/codex-capabilities.d.ts +41 -0
- package/neural-interface/lib/codex-capabilities.js +148 -0
- package/neural-interface/lib/codex-collaboration-mode.js +17 -0
- package/neural-interface/lib/codex-compat-rpc.js +241 -0
- package/neural-interface/lib/codex-mcp-approvals.js +56 -0
- package/neural-interface/lib/codex-mcp-apps.js +47 -0
- package/neural-interface/lib/codex-model-context.js +202 -0
- package/neural-interface/lib/codex-plan-store.js +71 -0
- package/neural-interface/lib/codex-request-journal.js +242 -0
- package/neural-interface/lib/codex-runtime-path.js +213 -0
- package/neural-interface/lib/codex-session-catalog.js +231 -0
- package/neural-interface/lib/codex-session-settings.js +46 -0
- package/neural-interface/lib/codex-sidepanel-permissions.js +110 -0
- package/neural-interface/lib/codex-thread-history.js +165 -0
- package/neural-interface/lib/codex-user-verification.js +34 -0
- package/neural-interface/lib/codex-writer-lifecycle.js +152 -0
- package/neural-interface/lib/confined-fs.js +256 -0
- package/neural-interface/lib/db.js +922 -0
- package/neural-interface/lib/design-md.js +9 -0
- package/neural-interface/lib/desktop/api.js +92 -0
- package/neural-interface/lib/desktop/audit.js +62 -0
- package/neural-interface/lib/desktop/build.js +423 -0
- package/neural-interface/lib/desktop/config.js +606 -0
- package/neural-interface/lib/desktop/coords.js +76 -0
- package/neural-interface/lib/desktop/fake-helper.js +1192 -0
- package/neural-interface/lib/desktop/frames.js +77 -0
- package/neural-interface/lib/desktop/grants.js +84 -0
- package/neural-interface/lib/desktop/guards.js +170 -0
- package/neural-interface/lib/desktop/helper/SynabunDesktop.swift +3056 -0
- package/neural-interface/lib/desktop/lease.js +51 -0
- package/neural-interface/lib/desktop/manager.js +545 -0
- package/neural-interface/lib/desktop/press-contexts.js +103 -0
- package/neural-interface/lib/desktop/protocol.js +495 -0
- package/neural-interface/lib/desktop/service.js +1202 -0
- package/neural-interface/lib/effort-levels.js +148 -0
- package/neural-interface/lib/event-loop-monitor.js +67 -0
- package/neural-interface/lib/exec-loop-command.js +209 -0
- package/neural-interface/lib/folder-picker.ps1 +77 -0
- package/neural-interface/lib/http-guards.js +287 -0
- package/neural-interface/lib/jsonl-lines.js +94 -0
- package/neural-interface/lib/loop-logger.js +127 -0
- package/neural-interface/lib/mcp-client-config.js +133 -0
- package/neural-interface/lib/mcp-installer.js +487 -0
- package/neural-interface/lib/mcp-profile-refresh-coordinator.js +78 -0
- package/neural-interface/lib/mcp-profile-servers.js +120 -0
- package/neural-interface/lib/memo-cache.js +84 -0
- package/neural-interface/lib/memory-api.js +758 -0
- package/neural-interface/lib/memory-map-api.js +1150 -0
- package/neural-interface/lib/memory-map-layout.js +872 -0
- package/neural-interface/lib/memory-map-worker.js +165 -0
- package/neural-interface/lib/memory-session-capture.js +81 -0
- package/neural-interface/lib/memory-timers.js +62 -0
- package/neural-interface/lib/morelogin.js +301 -0
- package/neural-interface/lib/native-binary-runtime.js +382 -0
- package/neural-interface/lib/native-loop-goal.js +33 -0
- package/neural-interface/lib/native-loop-providers.js +1002 -0
- package/neural-interface/lib/native-loop-runtime.js +747 -0
- package/neural-interface/lib/offline-page.js +54 -0
- package/neural-interface/lib/opencode-hidden-models.js +113 -0
- package/neural-interface/lib/opencode-history.js +253 -0
- package/neural-interface/lib/opencode-runtime-config.js +58 -0
- package/neural-interface/lib/opencode-v2-client.js +486 -0
- package/neural-interface/lib/opencode-v2-ws-requests.js +955 -0
- package/neural-interface/lib/path-confine.js +213 -0
- package/neural-interface/lib/plugin-installer.js +264 -0
- package/neural-interface/lib/project-storage.js +766 -0
- package/neural-interface/lib/pty-host/core.js +603 -0
- package/neural-interface/lib/pty-host/flow.js +43 -0
- package/neural-interface/lib/pty-host/host.js +47 -0
- package/neural-interface/lib/pty-host/manager.js +360 -0
- package/neural-interface/lib/pty-host/protocol.js +81 -0
- package/neural-interface/lib/pty-host/snapshot.js +133 -0
- package/neural-interface/lib/remote-policy.js +582 -0
- package/neural-interface/lib/rulesets/api.js +175 -0
- package/neural-interface/lib/rulesets/fs-safe.js +245 -0
- package/neural-interface/lib/rulesets/installer.js +1453 -0
- package/neural-interface/lib/rulesets/json-edit.js +186 -0
- package/neural-interface/lib/rulesets/lock.js +170 -0
- package/neural-interface/lib/rulesets/managed-text.js +314 -0
- package/neural-interface/lib/rulesets/normalise.js +24 -0
- package/neural-interface/lib/rulesets/render.js +84 -0
- package/neural-interface/lib/schedule-launch-intent.js +40 -0
- package/neural-interface/lib/session-cache-builder.js +524 -0
- package/neural-interface/lib/session-chunker.js +344 -0
- package/neural-interface/lib/session-indexer.js +327 -0
- package/neural-interface/lib/session-title-generator.js +232 -0
- package/neural-interface/lib/skill-prompt-bridge.js +11 -0
- package/neural-interface/lib/style-guide/api.js +375 -0
- package/neural-interface/lib/style-guide/color.js +352 -0
- package/neural-interface/lib/style-guide/export-css.js +99 -0
- package/neural-interface/lib/style-guide/export-dtcg.js +209 -0
- package/neural-interface/lib/style-guide/export-tailwind.js +102 -0
- package/neural-interface/lib/style-guide/fonts.json +227 -0
- package/neural-interface/lib/style-guide/import.js +1268 -0
- package/neural-interface/lib/style-guide/pointers.js +103 -0
- package/neural-interface/lib/style-guide/presets.js +232 -0
- package/neural-interface/lib/style-guide/render-design-md.js +319 -0
- package/neural-interface/lib/style-guide/schema.js +848 -0
- package/neural-interface/lib/style-guide/security.js +43 -0
- package/neural-interface/lib/style-guide/store.js +491 -0
- package/neural-interface/lib/style-guide/summary.js +106 -0
- package/neural-interface/lib/style-guide/tokens.js +170 -0
- package/neural-interface/lib/synabun-update-plan.js +324 -0
- package/neural-interface/lib/synabun-version.js +179 -0
- package/neural-interface/lib/system-restore.js +99 -0
- package/neural-interface/lib/terminal-env.js +86 -0
- package/neural-interface/lib/ui-blob-store.js +106 -0
- package/neural-interface/lib/vector-cache.js +2 -0
- package/neural-interface/lib/virtual-socket.js +60 -0
- package/neural-interface/lib/whatsapp/api.js +146 -0
- package/neural-interface/lib/whatsapp/auth-store.js +389 -0
- package/neural-interface/lib/whatsapp/baileys-adapter.js +457 -0
- package/neural-interface/lib/whatsapp/brain.js +128 -0
- package/neural-interface/lib/whatsapp/bridge.js +1499 -0
- package/neural-interface/lib/whatsapp/cards.js +519 -0
- package/neural-interface/lib/whatsapp/commands.js +92 -0
- package/neural-interface/lib/whatsapp/config.js +291 -0
- package/neural-interface/lib/whatsapp/connector/entry.mjs +25 -0
- package/neural-interface/lib/whatsapp/connector/manifest.json +6 -0
- package/neural-interface/lib/whatsapp/connector/npm-shrinkwrap.json +717 -0
- package/neural-interface/lib/whatsapp/connector/package.json +8 -0
- package/neural-interface/lib/whatsapp/fake-baileys.js +444 -0
- package/neural-interface/lib/whatsapp/format.js +552 -0
- package/neural-interface/lib/whatsapp/host-core.js +1225 -0
- package/neural-interface/lib/whatsapp/host.js +119 -0
- package/neural-interface/lib/whatsapp/identity.js +288 -0
- package/neural-interface/lib/whatsapp/inbound.js +149 -0
- package/neural-interface/lib/whatsapp/installer.js +562 -0
- package/neural-interface/lib/whatsapp/manager.js +638 -0
- package/neural-interface/lib/whatsapp/paths.js +81 -0
- package/neural-interface/lib/whatsapp/protocol.js +327 -0
- package/neural-interface/lib/whatsapp/qr-svg.js +57 -0
- package/neural-interface/lib/whatsapp/redact.js +130 -0
- package/neural-interface/lib/whatsapp/service.js +1305 -0
- package/neural-interface/lib/whatsapp/vendor/qrcodegen.js +848 -0
- package/neural-interface/package-lock.json +4431 -0
- package/neural-interface/package.json +58 -27
- package/neural-interface/public/apple-touch-icon.png +0 -0
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude.svg +1 -0
- package/neural-interface/public/favicon-16x16.png +0 -0
- package/neural-interface/public/favicon-32x32.png +0 -0
- package/neural-interface/public/icon-192.png +0 -0
- package/neural-interface/public/icon-512.png +0 -0
- package/neural-interface/public/index.html +32 -182
- package/neural-interface/public/index2d.html +2 -2
- package/neural-interface/public/install-app.html +31 -0
- package/neural-interface/public/logo-synabun.png +0 -0
- package/neural-interface/public/logoHD.png +0 -0
- package/neural-interface/public/manifest.json +2 -0
- package/neural-interface/public/mcp.svg +4 -0
- package/neural-interface/public/offline.html +670 -299
- package/neural-interface/public/onboarding.html +804 -161
- package/neural-interface/public/opencode-logo-dark-square.svg +1 -0
- package/neural-interface/public/shared/agent-runtime-options.js +596 -0
- package/neural-interface/public/shared/api.js +339 -28
- package/neural-interface/public/shared/assistant/asst-accounts.js +318 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +296 -0
- package/neural-interface/public/shared/assistant/asst-api.js +270 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +744 -0
- package/neural-interface/public/shared/assistant/asst-budget.js +333 -0
- package/neural-interface/public/shared/assistant/asst-composer.js +656 -0
- package/neural-interface/public/shared/assistant/asst-computer.js +803 -0
- package/neural-interface/public/shared/assistant/asst-control.js +1018 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +262 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +313 -0
- package/neural-interface/public/shared/assistant/asst-menu.js +375 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +1845 -0
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +586 -0
- package/neural-interface/public/shared/assistant/asst-render.js +3305 -0
- package/neural-interface/public/shared/assistant/asst-route.js +1653 -0
- package/neural-interface/public/shared/assistant/asst-roving.js +75 -0
- package/neural-interface/public/shared/assistant/asst-sanitize.js +117 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +488 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +675 -0
- package/neural-interface/public/shared/assistant/asst-slash.js +258 -0
- package/neural-interface/public/shared/assistant/asst-state.js +727 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +1686 -0
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +1017 -0
- package/neural-interface/public/shared/assistant/asst-usage.js +444 -0
- package/neural-interface/public/shared/assistant/asst-ws.js +175 -0
- package/neural-interface/public/shared/automation-context.js +75 -0
- package/neural-interface/public/shared/blob-store.js +99 -0
- package/neural-interface/public/shared/cdx/cdx-capabilities.js +118 -0
- package/neural-interface/public/shared/cdx/cdx-cards.js +37 -0
- package/neural-interface/public/shared/cdx/cdx-commands.js +171 -0
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +402 -0
- package/neural-interface/public/shared/cdx/cdx-context-model.js +220 -0
- package/neural-interface/public/shared/cdx/cdx-features.js +408 -0
- package/neural-interface/public/shared/cdx/cdx-icons.js +89 -0
- package/neural-interface/public/shared/cdx/cdx-mcp-app-host.js +414 -0
- package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.html +6 -0
- package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.js +56 -0
- package/neural-interface/public/shared/cdx/cdx-mcp-app.js +178 -0
- package/neural-interface/public/shared/cdx/cdx-model-catalog.js +120 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +261 -0
- package/neural-interface/public/shared/cdx/cdx-output.js +382 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +1372 -0
- package/neural-interface/public/shared/cdx/cdx-plan.js +266 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +682 -0
- package/neural-interface/public/shared/cdx/cdx-render.js +3381 -0
- package/neural-interface/public/shared/cdx/cdx-requests.js +1136 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +3251 -0
- package/neural-interface/public/shared/cdx/cdx-tabs.js +7992 -0
- package/neural-interface/public/shared/cdx/cdx-ws.js +363 -0
- package/neural-interface/public/shared/cli-status.js +169 -0
- package/neural-interface/public/shared/command-runner-model.js +30 -0
- package/neural-interface/public/shared/constants.js +22 -5
- package/neural-interface/public/shared/cp/cp-agents.js +186 -0
- package/neural-interface/public/shared/cp/cp-bash.js +252 -0
- package/neural-interface/public/shared/cp/cp-compose.js +159 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +309 -0
- package/neural-interface/public/shared/cp/cp-context-model.js +157 -0
- package/neural-interface/public/shared/cp/cp-context-window.js +80 -0
- package/neural-interface/public/shared/cp/cp-ctx.js +11 -0
- package/neural-interface/public/shared/cp/cp-diff.js +255 -0
- package/neural-interface/public/shared/cp/cp-event-rows.js +637 -0
- package/neural-interface/public/shared/cp/cp-events.js +846 -0
- package/neural-interface/public/shared/cp/cp-markdown.js +104 -0
- package/neural-interface/public/shared/cp/cp-permission-model.js +905 -0
- package/neural-interface/public/shared/cp/cp-permissions.js +626 -0
- package/neural-interface/public/shared/cp/cp-rehydrate.js +113 -0
- package/neural-interface/public/shared/cp/cp-restore.js +224 -0
- package/neural-interface/public/shared/cp/cp-session-model.js +338 -0
- package/neural-interface/public/shared/cp/cp-session-settings.js +254 -0
- package/neural-interface/public/shared/cp/cp-sessions.js +227 -0
- package/neural-interface/public/shared/cp/cp-statusline.js +153 -0
- package/neural-interface/public/shared/cp/cp-styles.js +349 -0
- package/neural-interface/public/shared/cp/cp-tasks-model.js +282 -0
- package/neural-interface/public/shared/cp/cp-tasks.js +107 -0
- package/neural-interface/public/shared/cp/cp-temporary-view.js +56 -0
- package/neural-interface/public/shared/cp/cp-temporary.js +231 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +326 -0
- package/neural-interface/public/shared/cp/cp-tool-results.js +618 -0
- package/neural-interface/public/shared/cp/cp-usage-model.js +124 -0
- package/neural-interface/public/shared/graph-activity.js +68 -0
- package/neural-interface/public/shared/html-shell.js +116 -17
- package/neural-interface/public/shared/i18n.js +132 -34
- package/neural-interface/public/shared/node-positions-store.js +44 -0
- package/neural-interface/public/shared/ocp-hidden-models.js +116 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +355 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-autoaccept-button.js +35 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-automation-ownership.js +72 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +280 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +67 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +179 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes.js +164 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +725 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +75 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +1204 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm-logic.js +117 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm.js +59 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-gauge.js +252 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +478 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +179 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +233 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-icons.js +30 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +1065 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-mention-hints.js +182 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +655 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +2636 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +601 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-profile-inheritance.js +78 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +649 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +35 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +357 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render-logic.js +189 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1869 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-select.js +42 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +117 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +1714 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +468 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-widgets.js +63 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +366 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-settings-link.js +19 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +343 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +746 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +347 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +338 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +3543 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-subagents-logic.js +482 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-tools-logic.js +351 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-tools.js +168 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-variantpicker.js +315 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +545 -0
- package/neural-interface/public/shared/provider-icons.js +30 -0
- package/neural-interface/public/shared/pwa-install-page.js +34 -0
- package/neural-interface/public/shared/pwa-install.css +12 -0
- package/neural-interface/public/shared/pwa-install.js +28 -0
- package/neural-interface/public/shared/session-title.js +100 -0
- package/neural-interface/public/shared/settings/settings-ia.js +389 -0
- package/neural-interface/public/shared/settings/settings-kit.js +234 -0
- package/neural-interface/public/shared/settings/settings-rules.js +160 -0
- package/neural-interface/public/shared/settings/settings-search.js +463 -0
- package/neural-interface/public/shared/settings/settings-shell.js +570 -0
- package/neural-interface/public/shared/start-bridge.js +583 -0
- package/neural-interface/public/shared/state.js +5 -0
- package/neural-interface/public/shared/storage.js +50 -7
- package/neural-interface/public/shared/styleguide/sg-agents.js +42 -0
- package/neural-interface/public/shared/styleguide/sg-brand.js +4 -0
- package/neural-interface/public/shared/styleguide/sg-color-client.js +388 -0
- package/neural-interface/public/shared/styleguide/sg-colors.js +151 -0
- package/neural-interface/public/shared/styleguide/sg-components.js +35 -0
- package/neural-interface/public/shared/styleguide/sg-history.js +41 -0
- package/neural-interface/public/shared/styleguide/sg-icons-logo.js +38 -0
- package/neural-interface/public/shared/styleguide/sg-imagery.js +4 -0
- package/neural-interface/public/shared/styleguide/sg-import-export.js +48 -0
- package/neural-interface/public/shared/styleguide/sg-kit.js +284 -0
- package/neural-interface/public/shared/styleguide/sg-labels.js +318 -0
- package/neural-interface/public/shared/styleguide/sg-layout.js +4 -0
- package/neural-interface/public/shared/styleguide/sg-motion.js +5 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +237 -0
- package/neural-interface/public/shared/styleguide/sg-preview.js +138 -0
- package/neural-interface/public/shared/styleguide/sg-rules.js +9 -0
- package/neural-interface/public/shared/styleguide/sg-shape.js +24 -0
- package/neural-interface/public/shared/styleguide/sg-state.js +132 -0
- package/neural-interface/public/shared/styleguide/sg-typography.js +125 -0
- package/neural-interface/public/shared/styles.css +8614 -1477
- package/neural-interface/public/shared/synabun-mascot.js +1625 -0
- package/neural-interface/public/shared/synabun-ticker.js +376 -0
- package/neural-interface/public/shared/term-ready.js +307 -0
- package/neural-interface/public/shared/term-renderer.js +343 -0
- package/neural-interface/public/shared/ui-assistant-panel.js +10 -0
- package/neural-interface/public/shared/ui-assistant.js +77 -0
- package/neural-interface/public/shared/ui-automation-studio.js +5105 -3573
- package/neural-interface/public/shared/ui-claude-panel.js +11685 -6333
- package/neural-interface/public/shared/ui-codex-panel.js +12 -0
- package/neural-interface/public/shared/ui-command-runner.js +251 -468
- package/neural-interface/public/shared/ui-cost-widget.js +55 -34
- package/neural-interface/public/shared/ui-detail.js +77 -14
- package/neural-interface/public/shared/ui-explorer.js +360 -181
- package/neural-interface/public/shared/ui-file-explorer.js +485 -107
- package/neural-interface/public/shared/ui-help.js +2 -2
- package/neural-interface/public/shared/ui-image-gallery.js +45 -21
- package/neural-interface/public/shared/ui-judgments.js +1346 -0
- package/neural-interface/public/shared/ui-keybinds.js +3 -2
- package/neural-interface/public/shared/ui-link.js +105 -51
- package/neural-interface/public/shared/ui-loading.js +222 -11
- package/neural-interface/public/shared/ui-memory-maintenance.js +111 -0
- package/neural-interface/public/shared/ui-menubar.js +100 -31
- package/neural-interface/public/shared/ui-native-loop-router-state.js +94 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +493 -0
- package/neural-interface/public/shared/ui-native-window-id.js +7 -0
- package/neural-interface/public/shared/ui-navbar.js +357 -222
- package/neural-interface/public/shared/ui-notifications.js +58 -12
- package/neural-interface/public/shared/ui-opencode-panel-v2.js +17 -0
- package/neural-interface/public/shared/ui-panels.js +15 -7
- package/neural-interface/public/shared/ui-resume.js +823 -653
- package/neural-interface/public/shared/ui-schedule-queue.js +172 -0
- package/neural-interface/public/shared/ui-schedules-studio.js +1882 -0
- package/neural-interface/public/shared/ui-scroll-follow.js +194 -0
- package/neural-interface/public/shared/ui-sessions.js +789 -34
- package/neural-interface/public/shared/ui-settings.js +5774 -2194
- package/neural-interface/public/shared/ui-sidepanel-layout.js +109 -0
- package/neural-interface/public/shared/ui-sidepanel-runtime.js +19 -0
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +40 -0
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +221 -0
- package/neural-interface/public/shared/ui-sidepanel-state.js +131 -0
- package/neural-interface/public/shared/ui-sidepanel-tray.js +547 -0
- package/neural-interface/public/shared/ui-sidepanel-windows.js +387 -0
- package/neural-interface/public/shared/ui-skills.js +18 -32
- package/neural-interface/public/shared/ui-styleguide.js +35 -0
- package/neural-interface/public/shared/ui-sync.js +87 -0
- package/neural-interface/public/shared/ui-terminal.js +7863 -6385
- package/neural-interface/public/shared/ui-tool-updates.js +292 -0
- package/neural-interface/public/shared/ui-tooltip.js +156 -15
- package/neural-interface/public/shared/ui-tutorial-draw.js +119 -683
- package/neural-interface/public/shared/ui-tutorial-layout.js +135 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +59 -2209
- package/neural-interface/public/shared/ui-tutorial.css +102 -0
- package/neural-interface/public/shared/ui-tutorial.js +557 -1076
- package/neural-interface/public/shared/ui-update.js +420 -68
- package/neural-interface/public/shared/ui-whatsapp.js +941 -0
- package/neural-interface/public/shared/ui-whiteboard.js +1076 -233
- package/neural-interface/public/shared/ui-workspaces.js +2 -1
- package/neural-interface/public/shared/utils.js +58 -0
- package/neural-interface/public/shared/vterm-buffer.js +15 -5
- package/neural-interface/public/shared/whatsapp/wa-phone.js +65 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +411 -0
- package/neural-interface/public/sidepanel-session.html +18 -0
- package/neural-interface/public/sw.js +10 -2
- package/neural-interface/public/synabun.png +0 -0
- package/neural-interface/public/variant/2d/graph.js +1707 -1635
- package/neural-interface/public/variant/2d/lasso.js +2 -1
- package/neural-interface/public/variant/2d/layout.js +595 -594
- package/neural-interface/public/variant/2d/main.js +557 -521
- package/neural-interface/public/variant/2d/settings-gfx.js +18 -24
- package/neural-interface/public/variant/3d/main.js +517 -500
- package/neural-interface/public/variant/3d/map-controls.js +144 -0
- package/neural-interface/public/variant/3d/map-data.js +251 -0
- package/neural-interface/public/variant/3d/map-labels.js +241 -0
- package/neural-interface/public/variant/3d/map-pick.js +90 -0
- package/neural-interface/public/variant/3d/map-renderer.js +521 -0
- package/neural-interface/public/variant/3d/map-shaders.js +158 -0
- package/neural-interface/public/variant/3d/map-view.js +618 -0
- package/neural-interface/public/vendor/three/manifest.json +3 -0
- package/neural-interface/public/vendor/three/three-0.169.0.min.js +3844 -0
- package/neural-interface/public/vendor/xterm/manifest.json +3 -0
- package/neural-interface/public/vendor/xterm/xterm-6.0.0.css +285 -0
- package/neural-interface/public/vendor/xterm/xterm-6.0.0.min.js +192 -0
- package/neural-interface/scripts/build-vendor.mjs +64 -0
- package/neural-interface/scripts/codex-protocol-fixture.mjs +83 -0
- package/neural-interface/scripts/rebuild-pty.js +39 -22
- package/neural-interface/scripts/vendor-three-entry.js +24 -0
- package/neural-interface/scripts/vendor-xterm-entry.js +8 -0
- package/neural-interface/server.js +33983 -15000
- package/neural-interface/skins/default/skin.css +90 -0
- package/neural-interface/skins/default/skin.json +8 -0
- package/neural-interface/templates/rulesets/README.md +9 -0
- package/neural-interface/templates/rulesets/claude.md +3 -0
- package/neural-interface/templates/rulesets/codex.md +2 -0
- package/neural-interface/templates/rulesets/coexistence.md +6 -0
- package/neural-interface/templates/rulesets/core.md +26 -0
- package/neural-interface/templates/rulesets/cursor.md +2 -0
- package/neural-interface/templates/rulesets/gemini.md +2 -0
- package/neural-interface/templates/rulesets/legacy-hashes.json +227 -0
- package/neural-interface/templates/rulesets/manifest.json +12 -0
- package/neural-interface/templates/rulesets/opencode.md +2 -0
- package/package.json +131 -53
- package/postinstall.js +55 -26
- package/preuninstall.js +187 -0
- package/scripts/restore-data-home-from-backup.mjs +239 -0
- package/setup.js +872 -260
- package/skills/leonardo/SKILL.md +19 -10
- package/skills/leonardo/modules/image-prompter.md +18 -12
- package/skills/leonardo/modules/video-prompter.md +19 -13
- package/skills/synabun/SKILL.md +209 -185
- package/skills/synabun/icon.png +0 -0
- package/skills/synabun/modules/audit.md +380 -374
- package/skills/synabun/modules/changelog.md +15 -6
- package/skills/synabun/modules/idea.md +148 -144
- package/skills/synabun/modules/memorize.md +13 -4
- package/skills/synabun/modules/schedule.md +198 -0
- package/skills/synabun/synabunicon.png +0 -0
- package/updater.cmd +31 -0
- package/updater.mjs +382 -0
- package/updater.sh +29 -0
- package/hooks/claude-code/test-user-learning.mjs +0 -531
- package/neural-interface/public/claude-chat.css +0 -878
- package/neural-interface/public/claude-chat.html +0 -92
- package/neural-interface/public/claude-chat.js +0 -1168
- package/neural-interface/public/index-legacy.html +0 -14677
- package/neural-interface/public/index2d-legacy.html +0 -8193
- package/neural-interface/public/shared/html-term-renderer.js +0 -1022
- package/neural-interface/public/variant/3d/background.js +0 -884
- package/neural-interface/public/variant/3d/camera.js +0 -661
- package/neural-interface/public/variant/3d/gfx.js +0 -116
- package/neural-interface/public/variant/3d/graph.js +0 -2399
- package/neural-interface/public/variant/3d/settings-gfx.js +0 -228
- package/neural-interface/templates/CLAUDE-template.md +0 -211
|
@@ -11,12 +11,26 @@ import { state, emit, on } from './state.js';
|
|
|
11
11
|
import { KEYS } from './constants.js';
|
|
12
12
|
import { storage } from './storage.js';
|
|
13
13
|
import { isGuest, hasPermission } from './ui-sync.js';
|
|
14
|
+
import { openSettingsModal } from './ui-settings.js';
|
|
15
|
+
import { fetchRulesStatus } from './api.js';
|
|
14
16
|
|
|
15
17
|
// ── Helpers ──
|
|
16
18
|
|
|
17
19
|
const $ = (id) => document.getElementById(id);
|
|
18
20
|
const _genId = () => 'wb-' + Date.now() + '-' + Math.random().toString(36).slice(2, 5);
|
|
19
21
|
|
|
22
|
+
// Stable per-tab id (survives reloads via sessionStorage). Sent with every
|
|
23
|
+
// viewport/geometry/screenshot message so the server can tell windows apart
|
|
24
|
+
// and pick the focus-active one for MCP coordinate math + screenshots.
|
|
25
|
+
const _clientId = (() => {
|
|
26
|
+
const fresh = () => 'wbc-' + Math.random().toString(36).slice(2, 10);
|
|
27
|
+
try {
|
|
28
|
+
let id = sessionStorage.getItem('wb-client-id');
|
|
29
|
+
if (!id) { id = fresh(); sessionStorage.setItem('wb-client-id', id); }
|
|
30
|
+
return id;
|
|
31
|
+
} catch { return fresh(); }
|
|
32
|
+
})();
|
|
33
|
+
|
|
20
34
|
// ── Internal state ──
|
|
21
35
|
|
|
22
36
|
let _elements = []; // Array<{id, type, x, y, width, height, ...}>
|
|
@@ -79,19 +93,31 @@ const SECTION_TYPES = {
|
|
|
79
93
|
|
|
80
94
|
// Persist debounce + cross-client sync
|
|
81
95
|
let _persistTimer = null;
|
|
82
|
-
let _isRemoteUpdate = false; // Suppress WS echo when receiving remote state
|
|
83
96
|
const PERSIST_DELAY = 600;
|
|
84
97
|
const MAX_UNDO = 50;
|
|
85
98
|
const SNAP_DIST = 30; // px for arrow anchor snapping
|
|
86
99
|
const MAX_IMAGE_DIM = 1920;
|
|
87
100
|
|
|
88
|
-
|
|
101
|
+
// Rendered-size measurement for auto-sized text/list cards (see _runMeasurePass)
|
|
102
|
+
let _measureScheduled = false;
|
|
103
|
+
let _measurePending = false; // a pass was skipped mid-gesture; run it at mouseup
|
|
104
|
+
const _geometryDirty = new Map(); // id → { id, width, height } awaiting send to the server
|
|
105
|
+
let _geometryTimer = null;
|
|
106
|
+
const GEOMETRY_SEND_DELAY = 250;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Cross-browser clipboard write with fallback for non-secure contexts.
|
|
110
|
+
* Returns a Promise that resolves true on success, false on failure — callers
|
|
111
|
+
* need this to show accurate feedback (a silent failure is how the "Copy image
|
|
112
|
+
* path" button used to claim success while the OS clipboard was empty).
|
|
113
|
+
*/
|
|
89
114
|
function _clipboardWrite(text) {
|
|
90
115
|
if (navigator.clipboard?.writeText) {
|
|
91
|
-
navigator.clipboard.writeText(text)
|
|
92
|
-
|
|
93
|
-
|
|
116
|
+
return navigator.clipboard.writeText(text)
|
|
117
|
+
.then(() => true)
|
|
118
|
+
.catch(() => _clipboardFallback(text));
|
|
94
119
|
}
|
|
120
|
+
return Promise.resolve(_clipboardFallback(text));
|
|
95
121
|
}
|
|
96
122
|
function _clipboardFallback(text) {
|
|
97
123
|
const ta = document.createElement('textarea');
|
|
@@ -99,8 +125,10 @@ function _clipboardFallback(text) {
|
|
|
99
125
|
ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0';
|
|
100
126
|
document.body.appendChild(ta);
|
|
101
127
|
ta.select();
|
|
102
|
-
|
|
128
|
+
let ok = false;
|
|
129
|
+
try { ok = document.execCommand('copy'); } catch {}
|
|
103
130
|
ta.remove();
|
|
131
|
+
return ok;
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
/** Snap a value to grid if grid snapping is enabled. */
|
|
@@ -114,13 +142,93 @@ const IMAGE_QUALITY = 0.8;
|
|
|
114
142
|
// DOM refs (set in init)
|
|
115
143
|
let _root, _toolbar, _canvas, _arrowsSvg, _elementsDiv, _arrowPreview, _arrowHint;
|
|
116
144
|
|
|
145
|
+
// The rules reminder beside the logo: shown only while a connected tool has no
|
|
146
|
+
// SynaBun rules, an edited copy or damaged markers (GET /api/setup/rules).
|
|
147
|
+
const RULES_REMINDER_STATES = ['not-installed', 'modified', 'conflict'];
|
|
148
|
+
let _rulesReminderSections = []; // Settings > Setup sections of the tools that need attention
|
|
149
|
+
|
|
117
150
|
|
|
118
151
|
// ═══════════════════════════════════════════
|
|
119
152
|
// COORDINATE TRANSFORMS
|
|
120
153
|
// ═══════════════════════════════════════════
|
|
121
154
|
|
|
155
|
+
// Cached _root rect. Reading getBoundingClientRect() inside mousemove (60Hz)
|
|
156
|
+
// forces a layout flush. We capture it once at drag/pen/marquee start and
|
|
157
|
+
// reuse it for the rest of the gesture, then invalidate on mouseup/resize/scroll.
|
|
158
|
+
let _rootRectCached = null;
|
|
159
|
+
|
|
160
|
+
function _getRootRect() {
|
|
161
|
+
if (_rootRectCached) return _rootRectCached;
|
|
162
|
+
if (!_root) return { left: 0, top: 0, width: 0, height: 0, right: 0, bottom: 0 };
|
|
163
|
+
_rootRectCached = _root.getBoundingClientRect();
|
|
164
|
+
return _rootRectCached;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function _invalidateRootRect() {
|
|
168
|
+
_rootRectCached = null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Abort any in-flight pointer gesture. Called whenever the board state is
|
|
173
|
+
* replaced under the user (state:full from another client, workspace restore,
|
|
174
|
+
* clear) and when a mouseup can no longer reach us (window blur / hidden tab),
|
|
175
|
+
* so a stale _drag/_marquee/_penPoints/_shapeCreating flag can never swallow
|
|
176
|
+
* every subsequent mousemove. `pointerOnly` keeps a multi-click arrow in
|
|
177
|
+
* progress (its waypoints survive an alt-tab; Escape still cancels it).
|
|
178
|
+
*/
|
|
179
|
+
function _cancelGesture({ pointerOnly = false } = {}) {
|
|
180
|
+
let needsRender = false;
|
|
181
|
+
if (_drag) {
|
|
182
|
+
// Positions already mutated by mousemove stay where they are — make sure
|
|
183
|
+
// they reach the server even though no mouseup will finalize them.
|
|
184
|
+
_drag = null;
|
|
185
|
+
persistDebounced();
|
|
186
|
+
}
|
|
187
|
+
if (_marquee) {
|
|
188
|
+
_marquee.rect?.remove();
|
|
189
|
+
_marquee = null;
|
|
190
|
+
}
|
|
191
|
+
if (_shapeCreating) {
|
|
192
|
+
_elements = _elements.filter(el => el.id !== _shapeCreating.id);
|
|
193
|
+
_shapeCreating = null;
|
|
194
|
+
needsRender = true;
|
|
195
|
+
}
|
|
196
|
+
if (_sectionCreating) {
|
|
197
|
+
_elements = _elements.filter(el => el.id !== _sectionCreating.id);
|
|
198
|
+
_sectionCreating = null;
|
|
199
|
+
needsRender = true;
|
|
200
|
+
}
|
|
201
|
+
if (_penLiveEl) _penLiveEl.remove();
|
|
202
|
+
_penLiveEl = null;
|
|
203
|
+
_penPoints = null;
|
|
204
|
+
if (!pointerOnly) {
|
|
205
|
+
_arrowCreating = null;
|
|
206
|
+
clearArrowPreview();
|
|
207
|
+
hideArrowHint();
|
|
208
|
+
}
|
|
209
|
+
document.body.classList.remove('ui-interacting');
|
|
210
|
+
_invalidateRootRect();
|
|
211
|
+
_invalidateElementMap();
|
|
212
|
+
if (needsRender) renderAll();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Prepare for a whole-board replacement (restore / clear / remote state:full):
|
|
217
|
+
* optionally commit + exit text/list editing, abort gestures, drop selection UI.
|
|
218
|
+
*/
|
|
219
|
+
function _beginStateReplace({ exitEdits = false } = {}) {
|
|
220
|
+
if (exitEdits) {
|
|
221
|
+
exitEditMode();
|
|
222
|
+
exitListEditMode();
|
|
223
|
+
}
|
|
224
|
+
_cancelGesture();
|
|
225
|
+
hideContextMenu();
|
|
226
|
+
_selectedId = null;
|
|
227
|
+
_selectedIds.clear();
|
|
228
|
+
}
|
|
229
|
+
|
|
122
230
|
function clientToCanvas(clientX, clientY) {
|
|
123
|
-
const rect =
|
|
231
|
+
const rect = _getRootRect();
|
|
124
232
|
return {
|
|
125
233
|
x: clientX - rect.left,
|
|
126
234
|
y: clientY - rect.top,
|
|
@@ -160,10 +268,22 @@ function undo() {
|
|
|
160
268
|
} else if (entry.action === 'move' || entry.action === 'resize' || entry.action === 'edit') {
|
|
161
269
|
const idx = _elements.findIndex(el => el.id === entry.before.id);
|
|
162
270
|
if (idx >= 0) _elements[idx] = { ...entry.before };
|
|
271
|
+
} else if (entry.action === 'batch') {
|
|
272
|
+
// Reverse an external batch: restore removed, restore updated befores, drop added
|
|
273
|
+
for (const el of entry.before.removed) _elements.push({ ...el });
|
|
274
|
+
for (const snap of entry.before.updated) {
|
|
275
|
+
const idx = _elements.findIndex(el => el.id === snap.id);
|
|
276
|
+
if (idx >= 0) _elements[idx] = { ...snap, points: snap.points ? snap.points.map(p => [...p]) : undefined };
|
|
277
|
+
}
|
|
278
|
+
if (entry.before.added.length) {
|
|
279
|
+
const ids = new Set(entry.before.added);
|
|
280
|
+
_elements = _elements.filter(el => !ids.has(el.id));
|
|
281
|
+
}
|
|
163
282
|
}
|
|
164
283
|
|
|
165
284
|
_selectedId = null;
|
|
166
285
|
_selectedIds.clear();
|
|
286
|
+
_invalidateElementMap();
|
|
167
287
|
renderAll();
|
|
168
288
|
persistDebounced();
|
|
169
289
|
updateUndoButtons();
|
|
@@ -193,10 +313,22 @@ function redo() {
|
|
|
193
313
|
const idx = _elements.findIndex(el => el.id === entry.after.id);
|
|
194
314
|
if (idx >= 0) _elements[idx] = { ...entry.after };
|
|
195
315
|
else _elements.push({ ...entry.after });
|
|
316
|
+
} else if (entry.action === 'batch') {
|
|
317
|
+
const removedIds = new Set(entry.before.removed.map(el => el.id));
|
|
318
|
+
if (removedIds.size) _elements = _elements.filter(el => !removedIds.has(el.id));
|
|
319
|
+
for (const snap of entry.after.updated) {
|
|
320
|
+
const idx = _elements.findIndex(el => el.id === snap.id);
|
|
321
|
+
const copy = { ...snap, points: snap.points ? snap.points.map(p => [...p]) : undefined };
|
|
322
|
+
if (idx >= 0) _elements[idx] = copy; else _elements.push(copy);
|
|
323
|
+
}
|
|
324
|
+
for (const el of entry.after.added) {
|
|
325
|
+
if (!_elements.some(e => e.id === el.id)) _elements.push({ ...el });
|
|
326
|
+
}
|
|
196
327
|
}
|
|
197
328
|
|
|
198
329
|
_selectedId = null;
|
|
199
330
|
_selectedIds.clear();
|
|
331
|
+
_invalidateElementMap();
|
|
200
332
|
renderAll();
|
|
201
333
|
persistDebounced();
|
|
202
334
|
updateUndoButtons();
|
|
@@ -209,6 +341,64 @@ function updateUndoButtons() {
|
|
|
209
341
|
if (redoBtn) redoBtn.classList.toggle('disabled', !_redoStack.length);
|
|
210
342
|
}
|
|
211
343
|
|
|
344
|
+
function isRulesetAlertDismissed() {
|
|
345
|
+
return storage.getItem(KEYS.WHITEBOARD_RULESET_ALERT_DISMISSED) === '1';
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function syncRulesetAlertVisibility() {
|
|
349
|
+
const alert = $('wb-ruleset-alert');
|
|
350
|
+
if (!alert) return;
|
|
351
|
+
const logo = document.querySelector('#static-bg .static-bg-logo');
|
|
352
|
+
const logoHidden = logo?.style.display === 'none';
|
|
353
|
+
alert.classList.toggle('hidden', isRulesetAlertDismissed() || logoHidden || _rulesReminderSections.length === 0);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Which connected tools need their rules looked at. A tool the user opted out
|
|
357
|
+
// of (managed === false) is not nagged about; an unknown status (no route, a
|
|
358
|
+
// failed request, a guest) shows nothing.
|
|
359
|
+
function applyRulesStatusToAlert(status) {
|
|
360
|
+
const hosts = status?.ok && status.hosts ? status.hosts : {};
|
|
361
|
+
_rulesReminderSections = Object.entries(hosts)
|
|
362
|
+
.filter(([, info]) => info?.mcp && RULES_REMINDER_STATES.includes(info.state) && !(info.state === 'not-installed' && info.managed === false))
|
|
363
|
+
.map(([host]) => `setup-${host}`);
|
|
364
|
+
syncRulesetAlertVisibility();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function refreshRulesetAlert() {
|
|
368
|
+
if (isRulesetAlertDismissed()) return;
|
|
369
|
+
applyRulesStatusToAlert(await fetchRulesStatus());
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function initRulesetAlert() {
|
|
373
|
+
const alert = $('wb-ruleset-alert');
|
|
374
|
+
if (!alert) return;
|
|
375
|
+
|
|
376
|
+
const setupBtn = $('wb-ruleset-settings');
|
|
377
|
+
const dismissBtn = $('wb-ruleset-dismiss');
|
|
378
|
+
|
|
379
|
+
setupBtn?.addEventListener('click', (e) => {
|
|
380
|
+
e.stopPropagation();
|
|
381
|
+
openSettingsModal({
|
|
382
|
+
tab: 'ai-connections',
|
|
383
|
+
expand: ['setup-rules', ..._rulesReminderSections],
|
|
384
|
+
highlight: _rulesReminderSections.length ? _rulesReminderSections : ['setup-rules'],
|
|
385
|
+
scrollTo: 'setup-rules',
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
dismissBtn?.addEventListener('click', (e) => {
|
|
390
|
+
e.stopPropagation();
|
|
391
|
+
storage.setItem(KEYS.WHITEBOARD_RULESET_ALERT_DISMISSED, '1');
|
|
392
|
+
syncRulesetAlertVisibility();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Settings > Setup passes the fresh status on after every rules action.
|
|
396
|
+
on('rules:changed', applyRulesStatusToAlert);
|
|
397
|
+
|
|
398
|
+
syncRulesetAlertVisibility();
|
|
399
|
+
refreshRulesetAlert();
|
|
400
|
+
}
|
|
401
|
+
|
|
212
402
|
|
|
213
403
|
// ═══════════════════════════════════════════
|
|
214
404
|
// PERSISTENCE
|
|
@@ -218,7 +408,7 @@ function updateUndoButtons() {
|
|
|
218
408
|
function flushListEdits() {
|
|
219
409
|
const dom = _root?.querySelector('.wb-list.editing');
|
|
220
410
|
if (!dom) return;
|
|
221
|
-
const ul = dom.querySelector('ul');
|
|
411
|
+
const ul = dom.querySelector('ul, ol');
|
|
222
412
|
const elId = dom.dataset.wbId;
|
|
223
413
|
const el = _elements.find(e => e.id === elId);
|
|
224
414
|
if (el && ul) {
|
|
@@ -244,22 +434,35 @@ function persistDebounced() {
|
|
|
244
434
|
flushTextEdits();
|
|
245
435
|
flushListEdits();
|
|
246
436
|
const snapshot = { elements: _elements, nextZIndex: _nextZIndex };
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
if (
|
|
437
|
+
// Guest edits without whiteboard permission never persist. (Remote-originated
|
|
438
|
+
// changes never reach here: the external command handlers don't persist.)
|
|
439
|
+
if (isGuest() && !hasPermission('whiteboard')) return;
|
|
440
|
+
if (_ws && _ws.readyState === 1) {
|
|
441
|
+
// Live cross-client relay + server persistence (saveWhiteboard) in one.
|
|
442
|
+
// The whiteboard no longer rides the ui-state PATCH cycle.
|
|
250
443
|
_ws.send(JSON.stringify({ type: 'state:sync', snapshot }));
|
|
444
|
+
} else {
|
|
445
|
+
// Socket down — persist via REST so a reload doesn't lose the edit.
|
|
446
|
+
fetch('/api/whiteboard', {
|
|
447
|
+
method: 'PUT',
|
|
448
|
+
headers: { 'Content-Type': 'application/json' },
|
|
449
|
+
body: JSON.stringify(snapshot),
|
|
450
|
+
}).catch(() => {});
|
|
251
451
|
}
|
|
252
452
|
}, PERSIST_DELAY);
|
|
253
453
|
}
|
|
254
454
|
|
|
255
|
-
function loadPersisted() {
|
|
455
|
+
async function loadPersisted() {
|
|
456
|
+
// Board now lives in its own server store (data/whiteboard.json), not in the
|
|
457
|
+
// client storage / ui-state PATCH cycle. Fetch it on init.
|
|
256
458
|
try {
|
|
257
|
-
const
|
|
258
|
-
if (!
|
|
259
|
-
const data =
|
|
459
|
+
const res = await fetch('/api/whiteboard');
|
|
460
|
+
if (!res.ok) return;
|
|
461
|
+
const data = await res.json();
|
|
260
462
|
if (Array.isArray(data.elements)) _elements = data.elements;
|
|
261
463
|
if (data.nextZIndex) _nextZIndex = data.nextZIndex;
|
|
262
|
-
|
|
464
|
+
_invalidateElementMap();
|
|
465
|
+
} catch { /* server unreachable — start empty */ }
|
|
263
466
|
}
|
|
264
467
|
|
|
265
468
|
|
|
@@ -290,14 +493,44 @@ function shortenPoints(pts, amount) {
|
|
|
290
493
|
return result;
|
|
291
494
|
}
|
|
292
495
|
|
|
496
|
+
/** Box size for anchor/centre math. Prefers the stored size — for text/list
|
|
497
|
+
* cards that is the browser-measured (or server-estimated) footprint — and
|
|
498
|
+
* only falls back to the legacy 100x60 phantom when nothing is stored. */
|
|
499
|
+
function _elBox(el) {
|
|
500
|
+
return {
|
|
501
|
+
w: el.width > 0 ? el.width : 100,
|
|
502
|
+
h: el.height > 0 ? el.height : 60,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** Anchor-adjusted copy of an arrow's points — the exact geometry the DOM
|
|
507
|
+
* renders. Shared by renderArrows, the drag fast path and the screenshot so
|
|
508
|
+
* the three can never disagree. Resolution is progressive on the same copy
|
|
509
|
+
* (a 2-point arrow's end is aimed at the resolved start); the server-side
|
|
510
|
+
* port in mcp-server/src/services/whiteboard-geometry.ts mirrors this. */
|
|
511
|
+
function resolveArrowPoints(el) {
|
|
512
|
+
const pts = (el.points || []).map(p => [...p]);
|
|
513
|
+
if (pts.length < 2) return pts;
|
|
514
|
+
if (el.startAnchor) {
|
|
515
|
+
const pt = getAnchorPoint(el.startAnchor, pts[1][0], pts[1][1]);
|
|
516
|
+
if (pt) pts[0] = [pt.x, pt.y];
|
|
517
|
+
}
|
|
518
|
+
if (el.endAnchor) {
|
|
519
|
+
const pt = getAnchorPoint(el.endAnchor, pts[pts.length - 2][0], pts[pts.length - 2][1]);
|
|
520
|
+
if (pt) pts[pts.length - 1] = [pt.x, pt.y];
|
|
521
|
+
}
|
|
522
|
+
return pts;
|
|
523
|
+
}
|
|
524
|
+
|
|
293
525
|
/** Get arrow endpoint from an anchored element. Returns edge intersection point. */
|
|
294
526
|
function getAnchorPoint(anchorId, fromX, fromY) {
|
|
295
|
-
const el =
|
|
527
|
+
const el = _findEl(anchorId);
|
|
296
528
|
if (!el || el.type === 'arrow') return null;
|
|
297
|
-
const
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
const
|
|
529
|
+
const { w, h } = _elBox(el);
|
|
530
|
+
const cx = el.x + w / 2;
|
|
531
|
+
const cy = el.y + h / 2;
|
|
532
|
+
const hw = w / 2;
|
|
533
|
+
const hh = h / 2;
|
|
301
534
|
|
|
302
535
|
const dx = fromX - cx;
|
|
303
536
|
const dy = fromY - cy;
|
|
@@ -320,8 +553,9 @@ function findNearestAnchor(cx, cy, excludeId) {
|
|
|
320
553
|
let bestDist = SNAP_DIST;
|
|
321
554
|
for (const el of _elements) {
|
|
322
555
|
if (el.type === 'arrow' || el.id === excludeId) continue;
|
|
323
|
-
const
|
|
324
|
-
const
|
|
556
|
+
const box = _elBox(el);
|
|
557
|
+
const ecx = el.x + box.w / 2;
|
|
558
|
+
const ecy = el.y + box.h / 2;
|
|
325
559
|
const d = Math.sqrt((cx - ecx) ** 2 + (cy - ecy) ** 2);
|
|
326
560
|
if (d < bestDist) {
|
|
327
561
|
bestDist = d;
|
|
@@ -331,6 +565,19 @@ function findNearestAnchor(cx, cy, excludeId) {
|
|
|
331
565
|
return best;
|
|
332
566
|
}
|
|
333
567
|
|
|
568
|
+
/** Ids of arrows anchored (start or end) to any element id in `movedIds`.
|
|
569
|
+
* Precomputed at drag start so the per-frame fast path only re-routes the
|
|
570
|
+
* handful of arrows actually pinned to the element(s) being moved. */
|
|
571
|
+
function _anchoredArrowIds(movedIds) {
|
|
572
|
+
const set = movedIds instanceof Set ? movedIds : new Set(movedIds);
|
|
573
|
+
const out = [];
|
|
574
|
+
for (const a of _elements) {
|
|
575
|
+
if (a.type !== 'arrow') continue;
|
|
576
|
+
if (set.has(a.startAnchor) || set.has(a.endAnchor)) out.push(a.id);
|
|
577
|
+
}
|
|
578
|
+
return out;
|
|
579
|
+
}
|
|
580
|
+
|
|
334
581
|
|
|
335
582
|
// ═══════════════════════════════════════════
|
|
336
583
|
// IMAGE COMPRESSION
|
|
@@ -366,13 +613,60 @@ function compressImage(blob) {
|
|
|
366
613
|
// RENDERING
|
|
367
614
|
// ═══════════════════════════════════════════
|
|
368
615
|
|
|
616
|
+
// Element ID → element lookup cache. Rebuilt lazily; many mousemove paths
|
|
617
|
+
// previously did O(n) Array.prototype.find on every event.
|
|
618
|
+
//
|
|
619
|
+
// The cache is SELF-VALIDATING. An earlier version keyed it on array length
|
|
620
|
+
// only, so a same-count delete→add (or a whole-array replacement from a
|
|
621
|
+
// workspace restore / another client's state:full) left it pointing at ghost
|
|
622
|
+
// objects or missing new ids — drags silently no-op'd until the count changed
|
|
623
|
+
// again (the "board is locked until I draw a pen stroke" bug). Every hit is now
|
|
624
|
+
// checked against the live array by index and the map is rebuilt on any
|
|
625
|
+
// mismatch, miss, identity change or length change.
|
|
626
|
+
let _elementMap = null; // Map<id, { el, idx }>
|
|
627
|
+
let _elementMapSrc = null; // the _elements array the map was built from
|
|
628
|
+
let _elementMapLen = -1;
|
|
629
|
+
|
|
630
|
+
function _rebuildElementMap() {
|
|
631
|
+
_elementMap = new Map();
|
|
632
|
+
for (let i = 0; i < _elements.length; i++) {
|
|
633
|
+
_elementMap.set(_elements[i].id, { el: _elements[i], idx: i });
|
|
634
|
+
}
|
|
635
|
+
_elementMapSrc = _elements;
|
|
636
|
+
_elementMapLen = _elements.length;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function _findEl(id) {
|
|
640
|
+
if (!id) return null;
|
|
641
|
+
if (!_elementMap || _elementMapSrc !== _elements || _elementMapLen !== _elements.length) {
|
|
642
|
+
_rebuildElementMap();
|
|
643
|
+
}
|
|
644
|
+
const hit = _elementMap.get(id);
|
|
645
|
+
if (hit && _elements[hit.idx] === hit.el) return hit.el;
|
|
646
|
+
// Stale entry (object replaced in place / same-length splice+push) or a
|
|
647
|
+
// brand-new id: rebuild once and retry.
|
|
648
|
+
_rebuildElementMap();
|
|
649
|
+
const again = _elementMap.get(id);
|
|
650
|
+
return again ? again.el : null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function _invalidateElementMap() {
|
|
654
|
+
_elementMap = null;
|
|
655
|
+
_elementMapSrc = null;
|
|
656
|
+
_elementMapLen = -1;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Synchronous render. Earlier perf pass tried rAF-batching this — that caused
|
|
660
|
+
// visible drag lag because the move/resize/rotate mousemove paths each call
|
|
661
|
+
// renderAll() once and rely on the DOM update completing before the next
|
|
662
|
+
// pointer event. Keep the call synchronous; the real per-frame cost lives in
|
|
663
|
+
// _findEl/_getRootRect (already optimized), not in scheduling.
|
|
369
664
|
function renderAll() {
|
|
370
665
|
renderElements();
|
|
371
666
|
renderArrows();
|
|
372
|
-
// Reposition context menu if element(s) selected
|
|
373
667
|
if (_ctxMenu && _selectedIds.size > 0) {
|
|
374
668
|
if (_selectedIds.size === 1) {
|
|
375
|
-
const el =
|
|
669
|
+
const el = _findEl(_selectedId);
|
|
376
670
|
if (el) positionContextMenu(el);
|
|
377
671
|
} else {
|
|
378
672
|
positionContextMenuMulti();
|
|
@@ -380,6 +674,8 @@ function renderAll() {
|
|
|
380
674
|
}
|
|
381
675
|
}
|
|
382
676
|
|
|
677
|
+
function renderAllImmediate() { renderAll(); }
|
|
678
|
+
|
|
383
679
|
function renderElements() {
|
|
384
680
|
if (!_elementsDiv) return;
|
|
385
681
|
const existing = new Map();
|
|
@@ -403,6 +699,79 @@ function renderElements() {
|
|
|
403
699
|
for (const [id, dom] of existing) {
|
|
404
700
|
if (!activeIds.has(id)) dom.remove();
|
|
405
701
|
}
|
|
702
|
+
|
|
703
|
+
_scheduleMeasure();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ── Rendered-size measurement (text/list auto-size) ──────────────────
|
|
707
|
+
// Text and list cards size themselves from content (CSS width:max-content), so
|
|
708
|
+
// their stored width/height used to be 0/undefined forever. That left the MCP
|
|
709
|
+
// tools, the server's viewport clamp and arrow anchoring blind to their real
|
|
710
|
+
// footprint. After every render we read the border-box size once (coalesced
|
|
711
|
+
// into a single rAF pass, never during a gesture) and write it back into the
|
|
712
|
+
// element, then ship it to the server as a `geometry` message. This pass never
|
|
713
|
+
// pushes undo entries, never persists the board and never re-renders — no loops.
|
|
714
|
+
|
|
715
|
+
function _scheduleMeasure() {
|
|
716
|
+
if (_measureScheduled) return;
|
|
717
|
+
_measureScheduled = true;
|
|
718
|
+
requestAnimationFrame(_runMeasurePass);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function _gestureActive() {
|
|
722
|
+
return !!(_drag || _marquee || _shapeCreating || _sectionCreating || _penPoints);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function _runMeasurePass() {
|
|
726
|
+
_measureScheduled = false;
|
|
727
|
+
if (!_elementsDiv) return;
|
|
728
|
+
if (_gestureActive()) { _measurePending = true; return; }
|
|
729
|
+
|
|
730
|
+
const domById = new Map();
|
|
731
|
+
for (const child of _elementsDiv.children) domById.set(child.dataset.wbId, child);
|
|
732
|
+
|
|
733
|
+
const changedIds = [];
|
|
734
|
+
for (const el of _elements) {
|
|
735
|
+
if (el.type !== 'text' && el.type !== 'list') continue;
|
|
736
|
+
const dom = domById.get(el.id);
|
|
737
|
+
if (!dom) continue;
|
|
738
|
+
if (dom.classList.contains('editing') || dom.contains(document.activeElement)) continue;
|
|
739
|
+
const w = dom.offsetWidth, h = dom.offsetHeight; // border-box; immune to transform:rotate
|
|
740
|
+
if (!w || !h) continue;
|
|
741
|
+
if (el.measured && Math.abs((el.width || 0) - w) < 1 && Math.abs((el.height || 0) - h) < 1) continue;
|
|
742
|
+
el.width = w;
|
|
743
|
+
el.height = h;
|
|
744
|
+
el.measured = true;
|
|
745
|
+
delete el.estimated;
|
|
746
|
+
_geometryDirty.set(el.id, { id: el.id, width: w, height: h });
|
|
747
|
+
changedIds.push(el.id);
|
|
748
|
+
}
|
|
749
|
+
if (!changedIds.length) return;
|
|
750
|
+
|
|
751
|
+
// Arrows pinned to a re-measured card need re-routing (the edge intersection
|
|
752
|
+
// depends on the box) — touch only those paths, never the whole SVG.
|
|
753
|
+
for (const aid of _anchoredArrowIds(changedIds)) {
|
|
754
|
+
const a = _findEl(aid);
|
|
755
|
+
if (a) _renderOneArrowPaths(a);
|
|
756
|
+
}
|
|
757
|
+
if (_ctxMenu && _selectedIds.size === 1) {
|
|
758
|
+
const sel = _findEl(_selectedId);
|
|
759
|
+
if (sel) positionContextMenu(sel);
|
|
760
|
+
}
|
|
761
|
+
_queueGeometrySend();
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function _queueGeometrySend() {
|
|
765
|
+
clearTimeout(_geometryTimer);
|
|
766
|
+
_geometryTimer = setTimeout(() => {
|
|
767
|
+
_geometryTimer = null;
|
|
768
|
+
if (!_geometryDirty.size) return;
|
|
769
|
+
if (isGuest() && !hasPermission('whiteboard')) { _geometryDirty.clear(); return; }
|
|
770
|
+
if (!_ws || _ws.readyState !== 1) return; // stays dirty — flushed on the next open/pass
|
|
771
|
+
const items = [..._geometryDirty.values()];
|
|
772
|
+
_geometryDirty.clear();
|
|
773
|
+
_ws.send(JSON.stringify({ type: 'geometry', clientId: _clientId, items }));
|
|
774
|
+
}, GEOMETRY_SEND_DELAY);
|
|
406
775
|
}
|
|
407
776
|
|
|
408
777
|
function createElementDOM(el) {
|
|
@@ -410,11 +779,12 @@ function createElementDOM(el) {
|
|
|
410
779
|
div.dataset.wbId = el.id;
|
|
411
780
|
|
|
412
781
|
if (el.type === 'text') {
|
|
782
|
+
// No resize handle: text cards auto-size from content (width:max-content).
|
|
413
783
|
div.className = 'wb-text';
|
|
414
|
-
div.innerHTML = `<div class="wb-text-content" contenteditable="false"></div
|
|
784
|
+
div.innerHTML = `<div class="wb-text-content" contenteditable="false"></div>`;
|
|
415
785
|
} else if (el.type === 'list') {
|
|
416
786
|
div.className = 'wb-list';
|
|
417
|
-
div.innerHTML =
|
|
787
|
+
div.innerHTML = el.ordered ? '<ol></ol>' : '<ul></ul>';
|
|
418
788
|
} else if (el.type === 'shape') {
|
|
419
789
|
div.className = 'wb-shape';
|
|
420
790
|
const ns = 'http://www.w3.org/2000/svg';
|
|
@@ -426,7 +796,8 @@ function createElementDOM(el) {
|
|
|
426
796
|
div.appendChild(handle);
|
|
427
797
|
} else if (el.type === 'image') {
|
|
428
798
|
div.className = 'wb-image';
|
|
429
|
-
div.
|
|
799
|
+
div.setAttribute('draggable', 'false');
|
|
800
|
+
div.innerHTML = `<img src="" alt="" draggable="false"><span class="wb-copy-path" data-tooltip="Copy image path"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></span><div class="wb-resize-handle"></div>`;
|
|
430
801
|
// Wire click-to-copy-path
|
|
431
802
|
const copyBtn = div.querySelector('.wb-copy-path');
|
|
432
803
|
copyBtn.addEventListener('click', (e) => {
|
|
@@ -444,13 +815,21 @@ function createElementDOM(el) {
|
|
|
444
815
|
const msg = JSON.parse(evt.data);
|
|
445
816
|
if (msg.type === 'image_saved' && msg.path) {
|
|
446
817
|
_ws.removeEventListener('message', handler);
|
|
447
|
-
_clipboardWrite(msg.path)
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
818
|
+
Promise.resolve(_clipboardWrite(msg.path)).then((ok) => {
|
|
819
|
+
if (ok) {
|
|
820
|
+
copyBtn.dataset.tooltip = 'Copied!';
|
|
821
|
+
copyBtn.classList.add('wb-copied');
|
|
822
|
+
} else {
|
|
823
|
+
// Clipboard write silently failed (usually: page not focused,
|
|
824
|
+
// non-secure context, or browser permission). Surface it so
|
|
825
|
+
// the user doesn't paste an empty clipboard into the CLI.
|
|
826
|
+
copyBtn.dataset.tooltip = 'Copy blocked — focus page & retry';
|
|
827
|
+
}
|
|
828
|
+
setTimeout(() => {
|
|
829
|
+
copyBtn.dataset.tooltip = 'Copy image path';
|
|
830
|
+
copyBtn.classList.remove('wb-copied');
|
|
831
|
+
}, 1800);
|
|
832
|
+
});
|
|
454
833
|
}
|
|
455
834
|
} catch {}
|
|
456
835
|
};
|
|
@@ -483,19 +862,34 @@ function updateElementDOM(dom, el) {
|
|
|
483
862
|
if (el.type === 'text') {
|
|
484
863
|
const content = dom.querySelector('.wb-text-content');
|
|
485
864
|
if (content && !content.matches(':focus')) {
|
|
486
|
-
|
|
865
|
+
// Skip the write when unchanged — this runs on every drag frame and
|
|
866
|
+
// setting innerText forces a layout.
|
|
867
|
+
const key = el.content || '';
|
|
868
|
+
if (dom._wbTextKey !== key) { content.innerText = key; dom._wbTextKey = key; }
|
|
487
869
|
}
|
|
488
870
|
if (el.fontSize) dom.style.fontSize = el.fontSize + 'px';
|
|
489
871
|
dom.style.fontWeight = el.bold ? '700' : '400';
|
|
490
872
|
dom.style.fontStyle = el.italic ? 'italic' : 'normal';
|
|
491
873
|
if (el.color) dom.style.color = el.color;
|
|
492
874
|
} else if (el.type === 'list') {
|
|
493
|
-
|
|
494
|
-
if (
|
|
875
|
+
let list = dom.querySelector('ul, ol');
|
|
876
|
+
if (list && !dom.classList.contains('editing')) {
|
|
495
877
|
const items = el.items && el.items.length ? el.items : [''];
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
878
|
+
const wantTag = el.ordered ? 'OL' : 'UL';
|
|
879
|
+
if (list.tagName !== wantTag) {
|
|
880
|
+
// ordered flag changed — swap the container (never while editing)
|
|
881
|
+
const swapped = document.createElement(wantTag.toLowerCase());
|
|
882
|
+
list.replaceWith(swapped);
|
|
883
|
+
list = swapped;
|
|
884
|
+
dom._wbListKey = null;
|
|
885
|
+
}
|
|
886
|
+
const key = wantTag + '|' + JSON.stringify(items);
|
|
887
|
+
if (dom._wbListKey !== key) {
|
|
888
|
+
list.innerHTML = items.map(item =>
|
|
889
|
+
`<li>${item.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</li>`
|
|
890
|
+
).join('');
|
|
891
|
+
dom._wbListKey = key;
|
|
892
|
+
}
|
|
499
893
|
}
|
|
500
894
|
if (el.fontSize) dom.style.fontSize = el.fontSize + 'px';
|
|
501
895
|
if (el.color) dom.style.color = el.color;
|
|
@@ -537,6 +931,47 @@ function updateElementDOM(dom, el) {
|
|
|
537
931
|
}
|
|
538
932
|
}
|
|
539
933
|
|
|
934
|
+
// ── Arrowhead markers ──
|
|
935
|
+
// The static <defs> in html-shell.js only carry a white and a selected (blue)
|
|
936
|
+
// head. Arrows honour el.color on the shaft, so the head must follow: one
|
|
937
|
+
// <marker> per distinct colour, created on demand inside the persistent <defs>
|
|
938
|
+
// (renderArrows re-appends that node, so generated markers survive rebuilds).
|
|
939
|
+
const _markerIds = new Map(); // color → marker id
|
|
940
|
+
|
|
941
|
+
function _arrowMarkerId(color) {
|
|
942
|
+
if (!color) return 'wb-arrowhead';
|
|
943
|
+
let id = _markerIds.get(color);
|
|
944
|
+
if (!id) {
|
|
945
|
+
id = 'wb-ah-' + color.replace(/[^a-zA-Z0-9]/g, '_');
|
|
946
|
+
_markerIds.set(color, id);
|
|
947
|
+
}
|
|
948
|
+
return id;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function _ensureArrowMarker(color) {
|
|
952
|
+
const id = _arrowMarkerId(color);
|
|
953
|
+
if (!color || !_arrowsSvg) return id;
|
|
954
|
+
const defs = _arrowsSvg.querySelector('defs');
|
|
955
|
+
if (!defs || defs.querySelector('#' + id)) return id;
|
|
956
|
+
const ns = 'http://www.w3.org/2000/svg';
|
|
957
|
+
const marker = document.createElementNS(ns, 'marker');
|
|
958
|
+
marker.setAttribute('id', id);
|
|
959
|
+
marker.setAttribute('markerWidth', '20');
|
|
960
|
+
marker.setAttribute('markerHeight', '20');
|
|
961
|
+
marker.setAttribute('refX', '2');
|
|
962
|
+
marker.setAttribute('refY', '10');
|
|
963
|
+
marker.setAttribute('orient', 'auto');
|
|
964
|
+
marker.setAttribute('markerUnits', 'userSpaceOnUse');
|
|
965
|
+
marker.setAttribute('overflow', 'visible');
|
|
966
|
+
const head = document.createElementNS(ns, 'path');
|
|
967
|
+
head.setAttribute('d', 'M 2 2 L 18 10 L 2 18 Z');
|
|
968
|
+
head.setAttribute('fill', color);
|
|
969
|
+
head.setAttribute('stroke', 'none');
|
|
970
|
+
marker.appendChild(head);
|
|
971
|
+
defs.appendChild(marker);
|
|
972
|
+
return id;
|
|
973
|
+
}
|
|
974
|
+
|
|
540
975
|
function renderArrows() {
|
|
541
976
|
if (!_arrowsSvg) return;
|
|
542
977
|
const defs = _arrowsSvg.querySelector('defs');
|
|
@@ -546,19 +981,9 @@ function renderArrows() {
|
|
|
546
981
|
for (const el of _elements) {
|
|
547
982
|
// ── Arrows ──
|
|
548
983
|
if (el.type === 'arrow') {
|
|
549
|
-
const pts = (el
|
|
984
|
+
const pts = resolveArrowPoints(el);
|
|
550
985
|
if (pts.length < 2) continue;
|
|
551
986
|
|
|
552
|
-
// Apply anchor offsets to first/last points
|
|
553
|
-
if (el.startAnchor) {
|
|
554
|
-
const pt = getAnchorPoint(el.startAnchor, pts[1][0], pts[1][1]);
|
|
555
|
-
if (pt) { pts[0] = [pt.x, pt.y]; }
|
|
556
|
-
}
|
|
557
|
-
if (el.endAnchor) {
|
|
558
|
-
const pt = getAnchorPoint(el.endAnchor, pts[pts.length - 2][0], pts[pts.length - 2][1]);
|
|
559
|
-
if (pt) { pts[pts.length - 1] = [pt.x, pt.y]; }
|
|
560
|
-
}
|
|
561
|
-
|
|
562
987
|
const isSel = _selectedIds.has(el.id);
|
|
563
988
|
const d = computeArrowPath(pts);
|
|
564
989
|
|
|
@@ -575,11 +1000,11 @@ function renderArrows() {
|
|
|
575
1000
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
576
1001
|
path.setAttribute('d', d);
|
|
577
1002
|
path.setAttribute('fill', 'none');
|
|
578
|
-
path.setAttribute('stroke', isSel ? 'var(--accent-blue, #60a5fa)' : 'rgba(255,255,255,0.8)');
|
|
1003
|
+
path.setAttribute('stroke', isSel ? 'var(--accent-blue, #60a5fa)' : (el.color || 'rgba(255,255,255,0.8)'));
|
|
579
1004
|
path.setAttribute('stroke-width', isSel ? '3' : '2');
|
|
580
1005
|
path.setAttribute('stroke-linecap', 'butt');
|
|
581
1006
|
path.setAttribute('stroke-linejoin', 'round');
|
|
582
|
-
path.setAttribute('marker-end', isSel ? 'url(#wb-arrowhead-sel)' :
|
|
1007
|
+
path.setAttribute('marker-end', isSel ? 'url(#wb-arrowhead-sel)' : `url(#${_ensureArrowMarker(el.color)})`);
|
|
583
1008
|
path.dataset.wbId = el.id;
|
|
584
1009
|
path.style.pointerEvents = 'none';
|
|
585
1010
|
if (isSel) path.classList.add('selected');
|
|
@@ -616,13 +1041,92 @@ function renderArrows() {
|
|
|
616
1041
|
}
|
|
617
1042
|
}
|
|
618
1043
|
|
|
1044
|
+
// ── Drag fast path ──────────────────────────────────────────
|
|
1045
|
+
// During a single/multi-element drag we update ONLY the affected DOM node(s)
|
|
1046
|
+
// and ONLY the arrows anchored to them, instead of the full renderAll()
|
|
1047
|
+
// (which restyles every element AND tears down/rebuilds the entire arrows SVG
|
|
1048
|
+
// every mousemove). All synchronous — the 2026-05-11 rAF-batching attempt was
|
|
1049
|
+
// reverted because deferring breaks pointer responsiveness; the win here is
|
|
1050
|
+
// eliminating per-frame work, not scheduling it. Each helper returns false if
|
|
1051
|
+
// a target node is missing so the caller can fall back to renderAll() once.
|
|
1052
|
+
|
|
1053
|
+
/** Re-route one arrow/pen's existing <path> nodes in place (hit + visible),
|
|
1054
|
+
* recomputing `d` exactly as renderArrows() does. No innerHTML, no createElementNS. */
|
|
1055
|
+
function _renderOneArrowPaths(el) {
|
|
1056
|
+
if (!_arrowsSvg) return false;
|
|
1057
|
+
const nodes = _arrowsSvg.querySelectorAll(`path[data-wb-id="${el.id}"]`);
|
|
1058
|
+
if (!nodes.length) return false;
|
|
1059
|
+
let d;
|
|
1060
|
+
if (el.type === 'arrow') {
|
|
1061
|
+
const pts = resolveArrowPoints(el);
|
|
1062
|
+
if (pts.length < 2) return true;
|
|
1063
|
+
d = computeArrowPath(pts);
|
|
1064
|
+
} else { // pen
|
|
1065
|
+
d = el.pathD || pointsToSmoothPath(el.points || []);
|
|
1066
|
+
}
|
|
1067
|
+
for (const n of nodes) n.setAttribute('d', d);
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/** Update a single element's DOM node via the existing updateElementDOM (which
|
|
1072
|
+
* handles position, size, rotation, shape inner-SVG redraw, section recolor). */
|
|
1073
|
+
function updateElementPosition(id) {
|
|
1074
|
+
if (!_elementsDiv) return false;
|
|
1075
|
+
const el = _findEl(id);
|
|
1076
|
+
if (!el) return false;
|
|
1077
|
+
const dom = _elementsDiv.querySelector(`[data-wb-id="${id}"]`);
|
|
1078
|
+
if (!dom) return false;
|
|
1079
|
+
updateElementDOM(dom, el);
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/** Per-frame drag update. `ids` = moved element ids (Set|array);
|
|
1084
|
+
* `anchoredArrowIds` = arrows pinned to moved DOM elements (precomputed at
|
|
1085
|
+
* drag start). Returns false → caller does a one-shot renderAll() fallback. */
|
|
1086
|
+
function _fastDragUpdate(ids, anchoredArrowIds) {
|
|
1087
|
+
const idSet = ids instanceof Set ? ids : new Set(ids);
|
|
1088
|
+
let ok = true;
|
|
1089
|
+
for (const id of idSet) {
|
|
1090
|
+
const el = _findEl(id);
|
|
1091
|
+
if (!el) { ok = false; continue; }
|
|
1092
|
+
if (el.type === 'arrow' || el.type === 'pen') {
|
|
1093
|
+
if (!_renderOneArrowPaths(el)) ok = false;
|
|
1094
|
+
} else {
|
|
1095
|
+
if (!updateElementPosition(id)) ok = false;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
if (anchoredArrowIds) {
|
|
1099
|
+
for (const aid of anchoredArrowIds) {
|
|
1100
|
+
if (idSet.has(aid)) continue; // already updated above
|
|
1101
|
+
const a = _findEl(aid);
|
|
1102
|
+
if (a && !_renderOneArrowPaths(a)) ok = false;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
// Keep selection chrome tracking the element. positionContextMenu* now read
|
|
1106
|
+
// the cached gesture rect (_getRootRect), so this adds no per-frame layout.
|
|
1107
|
+
if (_ctxMenu && _selectedIds.size > 0) {
|
|
1108
|
+
if (_selectedIds.size === 1) {
|
|
1109
|
+
const sel = _findEl(_selectedId);
|
|
1110
|
+
if (sel) positionContextMenu(sel);
|
|
1111
|
+
} else {
|
|
1112
|
+
positionContextMenuMulti();
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return ok;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
619
1118
|
|
|
620
1119
|
// ═══════════════════════════════════════════
|
|
621
1120
|
// ELEMENT CRUD
|
|
622
1121
|
// ═══════════════════════════════════════════
|
|
623
1122
|
|
|
624
|
-
function addElement(el, { skipPersist = false } = {}) {
|
|
625
|
-
el.zIndex
|
|
1123
|
+
function addElement(el, { skipPersist = false, keepZIndex = false } = {}) {
|
|
1124
|
+
if (keepZIndex && Number.isFinite(el.zIndex)) {
|
|
1125
|
+
// Server-assigned z-order (MCP adds): honour it and keep our counter ahead.
|
|
1126
|
+
_nextZIndex = Math.max(_nextZIndex, el.zIndex + 1);
|
|
1127
|
+
} else {
|
|
1128
|
+
el.zIndex = _nextZIndex++;
|
|
1129
|
+
}
|
|
626
1130
|
_elements.push(el);
|
|
627
1131
|
pushUndo('add', null, { ...el });
|
|
628
1132
|
renderAll();
|
|
@@ -637,8 +1141,9 @@ function deleteElement(id) {
|
|
|
637
1141
|
_elements.splice(idx, 1);
|
|
638
1142
|
|
|
639
1143
|
// Detach arrows anchored to deleted element
|
|
640
|
-
const
|
|
641
|
-
const
|
|
1144
|
+
const box = _elBox(el);
|
|
1145
|
+
const cx = el.x + box.w / 2;
|
|
1146
|
+
const cy = el.y + box.h / 2;
|
|
642
1147
|
for (const a of _elements) {
|
|
643
1148
|
if (a.type !== 'arrow' || !a.points) continue;
|
|
644
1149
|
if (a.startAnchor === id) {
|
|
@@ -666,8 +1171,9 @@ function deleteMultipleElements(ids) {
|
|
|
666
1171
|
const el = { ..._elements[idx] };
|
|
667
1172
|
_elements.splice(idx, 1);
|
|
668
1173
|
// Detach arrows anchored to this element
|
|
669
|
-
const
|
|
670
|
-
const
|
|
1174
|
+
const box = _elBox(el);
|
|
1175
|
+
const cx = el.x + box.w / 2;
|
|
1176
|
+
const cy = el.y + box.h / 2;
|
|
671
1177
|
for (const a of _elements) {
|
|
672
1178
|
if (a.type !== 'arrow' || !a.points) continue;
|
|
673
1179
|
if (a.startAnchor === id) { a.points[0] = [cx, cy]; a.startAnchor = null; }
|
|
@@ -917,6 +1423,7 @@ function showContextMenu(id) {
|
|
|
917
1423
|
startX: ev.clientX, startY: ev.clientY,
|
|
918
1424
|
origX: el2.x, origY: el2.y,
|
|
919
1425
|
before: { ...el2 },
|
|
1426
|
+
anchoredArrows: _anchoredArrowIds([id]),
|
|
920
1427
|
};
|
|
921
1428
|
});
|
|
922
1429
|
|
|
@@ -942,7 +1449,7 @@ function getElementCenter(el) {
|
|
|
942
1449
|
}
|
|
943
1450
|
|
|
944
1451
|
function positionContextMenu(el) {
|
|
945
|
-
const rootRect =
|
|
1452
|
+
const rootRect = _getRootRect(); // cached for the gesture — no per-frame layout flush
|
|
946
1453
|
if (!rootRect) return;
|
|
947
1454
|
|
|
948
1455
|
let cx, topY, rightX, bottomY, leftX;
|
|
@@ -1060,7 +1567,7 @@ function showMultiContextMenu() {
|
|
|
1060
1567
|
|
|
1061
1568
|
function positionContextMenuMulti() {
|
|
1062
1569
|
if (!_ctxMenu || _selectedIds.size < 2) return;
|
|
1063
|
-
const rootRect =
|
|
1570
|
+
const rootRect = _getRootRect(); // cached for the gesture — no per-frame layout flush
|
|
1064
1571
|
if (!rootRect) return;
|
|
1065
1572
|
|
|
1066
1573
|
const bounds = getMultiSelectBounds();
|
|
@@ -1541,6 +2048,7 @@ function enterEditMode(dom, elId) {
|
|
|
1541
2048
|
el.content = content.innerText.trimEnd() || '';
|
|
1542
2049
|
pushUndo('edit', before, { ...el });
|
|
1543
2050
|
persistDebounced();
|
|
2051
|
+
_scheduleMeasure(); // content changed without a render — re-measure the card
|
|
1544
2052
|
}
|
|
1545
2053
|
};
|
|
1546
2054
|
content.addEventListener('blur', onBlur);
|
|
@@ -1552,7 +2060,7 @@ function exitEditMode() {
|
|
|
1552
2060
|
}
|
|
1553
2061
|
|
|
1554
2062
|
function enterListEditMode(dom, elId) {
|
|
1555
|
-
const ul = dom.querySelector('ul');
|
|
2063
|
+
const ul = dom.querySelector('ul, ol');
|
|
1556
2064
|
if (!ul) return;
|
|
1557
2065
|
dom.classList.add('editing');
|
|
1558
2066
|
|
|
@@ -1575,7 +2083,7 @@ function enterListEditMode(dom, elId) {
|
|
|
1575
2083
|
function exitListEditMode() {
|
|
1576
2084
|
const dom = _root?.querySelector('.wb-list.editing');
|
|
1577
2085
|
if (!dom) return;
|
|
1578
|
-
const ul = dom.querySelector('ul');
|
|
2086
|
+
const ul = dom.querySelector('ul, ol');
|
|
1579
2087
|
const elId = dom.dataset.wbId;
|
|
1580
2088
|
|
|
1581
2089
|
dom.classList.remove('editing');
|
|
@@ -1590,6 +2098,7 @@ function exitListEditMode() {
|
|
|
1590
2098
|
el.items = rawItems.filter((t, i, arr) => t !== '' || arr.length === 1);
|
|
1591
2099
|
if (!el.items.length) el.items = [''];
|
|
1592
2100
|
pushUndo('edit', before, { ...el, items: [...el.items] });
|
|
2101
|
+
dom._wbListKey = null; // editing mutated the <li>s directly — force a clean rewrite
|
|
1593
2102
|
renderElements();
|
|
1594
2103
|
persistDebounced();
|
|
1595
2104
|
}
|
|
@@ -1617,6 +2126,7 @@ function _startMultiDrag(e) {
|
|
|
1617
2126
|
type: 'move-multi',
|
|
1618
2127
|
startX: e.clientX, startY: e.clientY,
|
|
1619
2128
|
snapshots,
|
|
2129
|
+
anchoredArrows: _anchoredArrowIds(_selectedIds),
|
|
1620
2130
|
};
|
|
1621
2131
|
}
|
|
1622
2132
|
|
|
@@ -1680,13 +2190,16 @@ function onMouseDown(e) {
|
|
|
1680
2190
|
// Resize handle? (only when single-selected)
|
|
1681
2191
|
if (e.target.closest('.wb-resize-handle') && targetId) {
|
|
1682
2192
|
const el = _elements.find(e2 => e2.id === targetId);
|
|
1683
|
-
|
|
2193
|
+
// Text/list cards auto-size — they have no handle; guard anyway so a
|
|
2194
|
+
// stray hit falls through to a plain move.
|
|
2195
|
+
if (el && el.type !== 'text' && el.type !== 'list') {
|
|
1684
2196
|
selectElement(targetId);
|
|
1685
2197
|
_drag = {
|
|
1686
2198
|
type: 'resize', id: targetId,
|
|
1687
2199
|
startX: e.clientX, startY: e.clientY,
|
|
1688
2200
|
origW: el.width || 200, origH: el.height || 80,
|
|
1689
2201
|
before: { ...el },
|
|
2202
|
+
anchoredArrows: _anchoredArrowIds([targetId]),
|
|
1690
2203
|
};
|
|
1691
2204
|
e.preventDefault();
|
|
1692
2205
|
return;
|
|
@@ -1713,6 +2226,7 @@ function onMouseDown(e) {
|
|
|
1713
2226
|
startX: e.clientX, startY: e.clientY,
|
|
1714
2227
|
origX: el.x, origY: el.y,
|
|
1715
2228
|
before: { ...el },
|
|
2229
|
+
anchoredArrows: _anchoredArrowIds([targetId]),
|
|
1716
2230
|
};
|
|
1717
2231
|
}
|
|
1718
2232
|
e.preventDefault();
|
|
@@ -1908,6 +2422,13 @@ function onMouseDown(e) {
|
|
|
1908
2422
|
function onMouseMove(e) {
|
|
1909
2423
|
if (!_root) return;
|
|
1910
2424
|
|
|
2425
|
+
// While actively dragging/drawing, suppress ambient transitions + glow/pulse
|
|
2426
|
+
// animations (consumed by the body.ui-interacting CSS kill-switch at
|
|
2427
|
+
// styles.css:7231). classList.add is idempotent — cheap to call each move.
|
|
2428
|
+
if (_drag || _marquee || _shapeCreating || _sectionCreating || _penPoints) {
|
|
2429
|
+
document.body.classList.add('ui-interacting');
|
|
2430
|
+
}
|
|
2431
|
+
|
|
1911
2432
|
// Marquee selection drag
|
|
1912
2433
|
if (_marquee) {
|
|
1913
2434
|
const pt = clientToCanvas(e.clientX, e.clientY);
|
|
@@ -1919,6 +2440,8 @@ function onMouseMove(e) {
|
|
|
1919
2440
|
if (!_marquee.rect && (w > 4 || h > 4)) {
|
|
1920
2441
|
_marquee.rect = document.createElement('div');
|
|
1921
2442
|
_marquee.rect.className = 'wb-marquee';
|
|
2443
|
+
// Keep the marquee overlay above promoted images/elements with runtime zIndex values.
|
|
2444
|
+
_marquee.rect.style.zIndex = String(_nextZIndex + 100);
|
|
1922
2445
|
_root.appendChild(_marquee.rect);
|
|
1923
2446
|
}
|
|
1924
2447
|
if (_marquee.rect) {
|
|
@@ -1937,7 +2460,7 @@ function onMouseMove(e) {
|
|
|
1937
2460
|
const dx = state.gridSnap ? (_snap(_drag.snapshots[0]?.origX + rawDx) - _drag.snapshots[0]?.origX) : rawDx;
|
|
1938
2461
|
const dy = state.gridSnap ? (_snap(_drag.snapshots[0]?.origY + rawDy) - _drag.snapshots[0]?.origY) : rawDy;
|
|
1939
2462
|
for (const snap of _drag.snapshots) {
|
|
1940
|
-
const el =
|
|
2463
|
+
const el = _findEl(snap.id);
|
|
1941
2464
|
if (!el) continue;
|
|
1942
2465
|
if (el.type === 'arrow') {
|
|
1943
2466
|
el.points = snap.origPoints.map(([px, py]) => [px + dx, py + dy]);
|
|
@@ -1953,14 +2476,14 @@ function onMouseMove(e) {
|
|
|
1953
2476
|
el.y = snap.origY + dy;
|
|
1954
2477
|
}
|
|
1955
2478
|
}
|
|
1956
|
-
renderAll();
|
|
2479
|
+
if (!_fastDragUpdate(_drag.snapshots.map(s => s.id), _drag.anchoredArrows)) renderAll();
|
|
1957
2480
|
return;
|
|
1958
2481
|
}
|
|
1959
2482
|
|
|
1960
2483
|
// Shape drag-to-create
|
|
1961
2484
|
if (_shapeCreating) {
|
|
1962
2485
|
const pt = clientToCanvas(e.clientX, e.clientY);
|
|
1963
|
-
const el =
|
|
2486
|
+
const el = _findEl(_shapeCreating.id);
|
|
1964
2487
|
if (el) {
|
|
1965
2488
|
const sx = _snap(Math.min(_shapeCreating.originX, pt.x));
|
|
1966
2489
|
const sy = _snap(Math.min(_shapeCreating.originY, pt.y));
|
|
@@ -1976,7 +2499,7 @@ function onMouseMove(e) {
|
|
|
1976
2499
|
// Section drag-to-create
|
|
1977
2500
|
if (_sectionCreating) {
|
|
1978
2501
|
const pt = clientToCanvas(e.clientX, e.clientY);
|
|
1979
|
-
const el =
|
|
2502
|
+
const el = _findEl(_sectionCreating.id);
|
|
1980
2503
|
if (el) {
|
|
1981
2504
|
const sx = _snap(Math.min(_sectionCreating.originX, pt.x));
|
|
1982
2505
|
const sy = _snap(Math.min(_sectionCreating.originY, pt.y));
|
|
@@ -1991,7 +2514,7 @@ function onMouseMove(e) {
|
|
|
1991
2514
|
|
|
1992
2515
|
// Pen drawing
|
|
1993
2516
|
if (_penPoints) {
|
|
1994
|
-
const rect =
|
|
2517
|
+
const rect = _getRootRect();
|
|
1995
2518
|
penMove(e.clientX - rect.left, e.clientY - rect.top);
|
|
1996
2519
|
return;
|
|
1997
2520
|
}
|
|
@@ -2000,11 +2523,11 @@ function onMouseMove(e) {
|
|
|
2000
2523
|
if (_drag?.type === 'move') {
|
|
2001
2524
|
const dx = e.clientX - _drag.startX;
|
|
2002
2525
|
const dy = e.clientY - _drag.startY;
|
|
2003
|
-
const el =
|
|
2526
|
+
const el = _findEl(_drag.id);
|
|
2004
2527
|
if (el) {
|
|
2005
2528
|
el.x = _snap(_drag.origX + dx);
|
|
2006
2529
|
el.y = _snap(_drag.origY + dy);
|
|
2007
|
-
renderAll();
|
|
2530
|
+
if (!_fastDragUpdate([_drag.id], _drag.anchoredArrows)) renderAll();
|
|
2008
2531
|
}
|
|
2009
2532
|
return;
|
|
2010
2533
|
}
|
|
@@ -2013,12 +2536,12 @@ function onMouseMove(e) {
|
|
|
2013
2536
|
if (_drag?.type === 'move-arrow') {
|
|
2014
2537
|
const dx = e.clientX - _drag.startX;
|
|
2015
2538
|
const dy = e.clientY - _drag.startY;
|
|
2016
|
-
const el =
|
|
2539
|
+
const el = _findEl(_drag.id);
|
|
2017
2540
|
if (el && el.points) {
|
|
2018
2541
|
el.points = _drag.origPoints.map(([px, py]) => [px + dx, py + dy]);
|
|
2019
2542
|
el.startAnchor = null;
|
|
2020
2543
|
el.endAnchor = null;
|
|
2021
|
-
renderAll();
|
|
2544
|
+
if (!_fastDragUpdate([_drag.id], null)) renderAll();
|
|
2022
2545
|
}
|
|
2023
2546
|
return;
|
|
2024
2547
|
}
|
|
@@ -2027,13 +2550,13 @@ function onMouseMove(e) {
|
|
|
2027
2550
|
if (_drag?.type === 'move-pen') {
|
|
2028
2551
|
const dx = e.clientX - _drag.startX;
|
|
2029
2552
|
const dy = e.clientY - _drag.startY;
|
|
2030
|
-
const el =
|
|
2553
|
+
const el = _findEl(_drag.id);
|
|
2031
2554
|
if (el) {
|
|
2032
2555
|
el.x = _drag.origX + dx;
|
|
2033
2556
|
el.y = _drag.origY + dy;
|
|
2034
2557
|
el.points = _drag.origPoints.map(([px, py]) => [px + dx, py + dy]);
|
|
2035
2558
|
el.pathD = pointsToSmoothPath(el.points);
|
|
2036
|
-
renderAll();
|
|
2559
|
+
if (!_fastDragUpdate([_drag.id], null)) renderAll();
|
|
2037
2560
|
}
|
|
2038
2561
|
return;
|
|
2039
2562
|
}
|
|
@@ -2044,7 +2567,7 @@ function onMouseMove(e) {
|
|
|
2044
2567
|
const angle = Math.atan2(pt2.y - _drag.centerY, pt2.x - _drag.centerX);
|
|
2045
2568
|
const deltaRad = angle - _drag.startAngle;
|
|
2046
2569
|
const deltaDeg = deltaRad * (180 / Math.PI);
|
|
2047
|
-
const el =
|
|
2570
|
+
const el = _findEl(_drag.id);
|
|
2048
2571
|
if (el) {
|
|
2049
2572
|
// For arrows: rotate all points around centroid
|
|
2050
2573
|
if (el.type === 'arrow' && _drag.origPoints) {
|
|
@@ -2087,7 +2610,7 @@ function onMouseMove(e) {
|
|
|
2087
2610
|
}
|
|
2088
2611
|
el.rotation = Math.round(newRot * 10) / 10;
|
|
2089
2612
|
}
|
|
2090
|
-
renderAll();
|
|
2613
|
+
if (!_fastDragUpdate([_drag.id], _drag.anchoredArrows)) renderAll();
|
|
2091
2614
|
}
|
|
2092
2615
|
return;
|
|
2093
2616
|
}
|
|
@@ -2096,11 +2619,11 @@ function onMouseMove(e) {
|
|
|
2096
2619
|
if (_drag?.type === 'resize') {
|
|
2097
2620
|
const dx = e.clientX - _drag.startX;
|
|
2098
2621
|
const dy = e.clientY - _drag.startY;
|
|
2099
|
-
const el =
|
|
2622
|
+
const el = _findEl(_drag.id);
|
|
2100
2623
|
if (el) {
|
|
2101
2624
|
el.width = _snap(Math.max(60, _drag.origW + dx));
|
|
2102
2625
|
el.height = _snap(Math.max(30, _drag.origH + dy));
|
|
2103
|
-
renderAll();
|
|
2626
|
+
if (!_fastDragUpdate([_drag.id], _drag.anchoredArrows)) renderAll();
|
|
2104
2627
|
}
|
|
2105
2628
|
return;
|
|
2106
2629
|
}
|
|
@@ -2114,6 +2637,12 @@ function onMouseMove(e) {
|
|
|
2114
2637
|
}
|
|
2115
2638
|
|
|
2116
2639
|
function onMouseUp(e) {
|
|
2640
|
+
// Any interaction is ending — lift the transition/animation kill-switch and
|
|
2641
|
+
// drop the gesture-cached root rect (both run first so they fire on every
|
|
2642
|
+
// early-return finalize path below).
|
|
2643
|
+
document.body.classList.remove('ui-interacting');
|
|
2644
|
+
_invalidateRootRect();
|
|
2645
|
+
|
|
2117
2646
|
// Finalize marquee selection
|
|
2118
2647
|
if (_marquee) {
|
|
2119
2648
|
if (_marquee.rect) {
|
|
@@ -2251,6 +2780,8 @@ function onMouseUp(e) {
|
|
|
2251
2780
|
_drag = null;
|
|
2252
2781
|
persistDebounced();
|
|
2253
2782
|
}
|
|
2783
|
+
// A measurement pass skipped during the gesture runs now.
|
|
2784
|
+
if (_measurePending) { _measurePending = false; _scheduleMeasure(); }
|
|
2254
2785
|
}
|
|
2255
2786
|
|
|
2256
2787
|
function onDblClick(e) {
|
|
@@ -2345,12 +2876,27 @@ function isWhiteboardActive() {
|
|
|
2345
2876
|
return bg && bg.classList.contains('visible');
|
|
2346
2877
|
}
|
|
2347
2878
|
|
|
2879
|
+
// A key pressed inside a side panel is the panel's: registerSidepanel marks every panel root
|
|
2880
|
+
// (Claude, Codex, OpenCode, Assistant) with data-sidepanel-provider.
|
|
2881
|
+
export function isSidepanelKeyTarget(el) {
|
|
2882
|
+
return !!el?.closest?.('[data-sidepanel-provider]');
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2348
2885
|
function onKeyDown(e) {
|
|
2349
2886
|
if (!isWhiteboardActive()) return;
|
|
2350
2887
|
|
|
2351
|
-
// Don't intercept keys when focus is in inputs outside the whiteboard
|
|
2352
|
-
|
|
2353
|
-
|
|
2888
|
+
// Don't intercept keys when focus is in inputs/contenteditables outside the whiteboard
|
|
2889
|
+
// (e.g. code editor, floating CLI rename) — typing must produce characters, not trigger keybinds.
|
|
2890
|
+
const ae = document.activeElement;
|
|
2891
|
+
const tag = ae?.tagName;
|
|
2892
|
+
const isTextField = tag === 'INPUT' || tag === 'TEXTAREA' || ae?.isContentEditable;
|
|
2893
|
+
if (isTextField && !ae.closest('#static-bg')) return;
|
|
2894
|
+
// Nor while the keyboard is inside the Settings panel: its rows, buttons and dialogs take their own keys
|
|
2895
|
+
// (Escape empties its search, then closes it), and a board shortcut must not fire from a setting.
|
|
2896
|
+
if (ae?.closest?.('#settings-panel')) return;
|
|
2897
|
+
// Nor while it is inside a side panel: Escape there stops a turn, and Delete, Backspace or
|
|
2898
|
+
// Ctrl+A/Z/C on one of its controls is not a board edit.
|
|
2899
|
+
if (isSidepanelKeyTarget(e.target) || isSidepanelKeyTarget(ae)) return;
|
|
2354
2900
|
|
|
2355
2901
|
const isEditingText = !!document.activeElement?.closest('.wb-text.editing');
|
|
2356
2902
|
const editingListLi = document.activeElement?.closest('.wb-list.editing li') || null;
|
|
@@ -2417,11 +2963,13 @@ function onKeyDown(e) {
|
|
|
2417
2963
|
if (_shapeCreating) {
|
|
2418
2964
|
_elements = _elements.filter(el => el.id !== _shapeCreating.id);
|
|
2419
2965
|
_shapeCreating = null;
|
|
2966
|
+
_invalidateElementMap();
|
|
2420
2967
|
renderAll();
|
|
2421
2968
|
}
|
|
2422
2969
|
if (_sectionCreating) {
|
|
2423
2970
|
_elements = _elements.filter(el => el.id !== _sectionCreating.id);
|
|
2424
2971
|
_sectionCreating = null;
|
|
2972
|
+
_invalidateElementMap();
|
|
2425
2973
|
renderAll();
|
|
2426
2974
|
}
|
|
2427
2975
|
if (_arrowCreating) { _arrowCreating = null; clearArrowPreview(); }
|
|
@@ -2737,6 +3285,7 @@ function onToolbarClick(e) {
|
|
|
2737
3285
|
if (breathe) breathe.style.display = hidden ? 'none' : '';
|
|
2738
3286
|
btn.classList.toggle('active', hidden);
|
|
2739
3287
|
sessionStorage.setItem('wb-logo-hidden', hidden ? '1' : '');
|
|
3288
|
+
syncRulesetAlertVisibility();
|
|
2740
3289
|
return;
|
|
2741
3290
|
}
|
|
2742
3291
|
}
|
|
@@ -2771,6 +3320,7 @@ export function restoreWhiteboardSnapshot(snap) {
|
|
|
2771
3320
|
clearWhiteboard();
|
|
2772
3321
|
return;
|
|
2773
3322
|
}
|
|
3323
|
+
_beginStateReplace({ exitEdits: true });
|
|
2774
3324
|
_elements = (snap.elements || []).map(el => {
|
|
2775
3325
|
// Migrate legacy arrow format (startX/endX → points)
|
|
2776
3326
|
if (el.type === 'arrow' && !el.points && el.startX != null) {
|
|
@@ -2788,11 +3338,13 @@ export function restoreWhiteboardSnapshot(snap) {
|
|
|
2788
3338
|
_arrowCreating = null;
|
|
2789
3339
|
_sectionCreating = null;
|
|
2790
3340
|
_clipboard = null;
|
|
3341
|
+
_invalidateElementMap();
|
|
2791
3342
|
renderAll();
|
|
2792
3343
|
persistDebounced();
|
|
2793
3344
|
}
|
|
2794
3345
|
|
|
2795
3346
|
export function clearWhiteboard() {
|
|
3347
|
+
_beginStateReplace({ exitEdits: true });
|
|
2796
3348
|
_elements = [];
|
|
2797
3349
|
_nextZIndex = 1;
|
|
2798
3350
|
_undoStack = [];
|
|
@@ -2802,6 +3354,7 @@ export function clearWhiteboard() {
|
|
|
2802
3354
|
_arrowCreating = null;
|
|
2803
3355
|
_sectionCreating = null;
|
|
2804
3356
|
_clipboard = null;
|
|
3357
|
+
_invalidateElementMap();
|
|
2805
3358
|
renderAll();
|
|
2806
3359
|
persistDebounced();
|
|
2807
3360
|
}
|
|
@@ -2862,10 +3415,11 @@ export function initWhiteboard() {
|
|
|
2862
3415
|
|
|
2863
3416
|
_root.appendChild(_arrowHint);
|
|
2864
3417
|
|
|
2865
|
-
//
|
|
2866
|
-
loadPersisted
|
|
3418
|
+
// Render immediately (likely empty) for an instant paint, then again once the
|
|
3419
|
+
// server returns the saved board (loadPersisted is async — fetches /api/whiteboard).
|
|
2867
3420
|
renderAll();
|
|
2868
3421
|
updateUndoButtons();
|
|
3422
|
+
loadPersisted().then(() => { renderAll(); updateUndoButtons(); });
|
|
2869
3423
|
|
|
2870
3424
|
// Mouse events
|
|
2871
3425
|
_root.addEventListener('mousedown', onMouseDown);
|
|
@@ -2873,13 +3427,32 @@ export function initWhiteboard() {
|
|
|
2873
3427
|
window.addEventListener('mouseup', onMouseUp);
|
|
2874
3428
|
_root.addEventListener('dblclick', onDblClick);
|
|
2875
3429
|
|
|
3430
|
+
// Never let the browser start a native HTML5 drag inside the board (an <img>
|
|
3431
|
+
// drag would swallow our mouseup and strand _drag). Nodes that opt in with
|
|
3432
|
+
// draggable="true" (e.g. a future drag-to-terminal handle) are exempt.
|
|
3433
|
+
_root.addEventListener('dragstart', (e) => {
|
|
3434
|
+
if (e.target?.closest?.('[draggable="true"]')) return;
|
|
3435
|
+
e.preventDefault();
|
|
3436
|
+
});
|
|
3437
|
+
|
|
2876
3438
|
// Keyboard — capture phase so we fire before global keybinds
|
|
2877
3439
|
document.addEventListener('keydown', onKeyDown, true);
|
|
2878
3440
|
|
|
2879
3441
|
// Track Ctrl key for tool lock
|
|
2880
3442
|
document.addEventListener('keydown', onCtrlTrack);
|
|
2881
3443
|
document.addEventListener('keyup', onCtrlTrack);
|
|
2882
|
-
window
|
|
3444
|
+
// A window blur or hidden tab means the matching mouseup may never arrive —
|
|
3445
|
+
// drop the tool lock AND any in-flight pointer gesture so the board never
|
|
3446
|
+
// ends up with a stale _drag/_marquee/_penPoints swallowing every mousemove.
|
|
3447
|
+
window.addEventListener('blur', () => {
|
|
3448
|
+
_toolLocked = false;
|
|
3449
|
+
updateLockIndicator();
|
|
3450
|
+
_cancelGesture({ pointerOnly: true });
|
|
3451
|
+
});
|
|
3452
|
+
document.addEventListener('visibilitychange', () => {
|
|
3453
|
+
if (document.hidden) _cancelGesture({ pointerOnly: true });
|
|
3454
|
+
_sendViewport(); // `visible` flag feeds the server's primary-client pick
|
|
3455
|
+
});
|
|
2883
3456
|
|
|
2884
3457
|
// Paste — listen on document (not _root) because _root can't receive focus
|
|
2885
3458
|
// and paste events only fire on the focused element's ancestor chain.
|
|
@@ -2898,6 +3471,9 @@ export function initWhiteboard() {
|
|
|
2898
3471
|
// Section picker
|
|
2899
3472
|
initSectionPicker();
|
|
2900
3473
|
|
|
3474
|
+
// Logo-adjacent ruleset reminder
|
|
3475
|
+
initRulesetAlert();
|
|
3476
|
+
|
|
2901
3477
|
// Prevent context menu on whiteboard
|
|
2902
3478
|
_root.addEventListener('contextmenu', e => e.preventDefault());
|
|
2903
3479
|
|
|
@@ -2907,6 +3483,7 @@ export function initWhiteboard() {
|
|
|
2907
3483
|
|
|
2908
3484
|
// Auto-focus whiteboard when entering focus mode so Ctrl+V paste works immediately
|
|
2909
3485
|
on('focus:enter', () => {
|
|
3486
|
+
refreshRulesetAlert();
|
|
2910
3487
|
setTimeout(() => _root.focus({ preventScroll: true }), 150);
|
|
2911
3488
|
// Restore logo hidden state from session
|
|
2912
3489
|
if (sessionStorage.getItem('wb-logo-hidden') === '1') {
|
|
@@ -2914,14 +3491,40 @@ export function initWhiteboard() {
|
|
|
2914
3491
|
const breathe = document.querySelector('#static-bg .focus-breathe');
|
|
2915
3492
|
if (logo) logo.style.display = 'none';
|
|
2916
3493
|
if (breathe) breathe.style.display = 'none';
|
|
3494
|
+
syncRulesetAlertVisibility();
|
|
2917
3495
|
const btn = document.getElementById('wb-toggle-logo');
|
|
2918
3496
|
if (btn) btn.classList.add('active');
|
|
3497
|
+
} else {
|
|
3498
|
+
syncRulesetAlertVisibility();
|
|
2919
3499
|
}
|
|
2920
3500
|
});
|
|
2921
3501
|
|
|
2922
3502
|
// Connect to server for external commands (Claude MCP)
|
|
2923
3503
|
_connectWhiteboardWS();
|
|
2924
3504
|
|
|
3505
|
+
// Viewport truth: re-report the usable area whenever anything that bounds it
|
|
3506
|
+
// changes — root size, the CSS variables the navbar/terminal/right panel
|
|
3507
|
+
// publish on <html>, focus mode, panel visibility, tab visibility.
|
|
3508
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
3509
|
+
new ResizeObserver(() => _sendViewport()).observe(_root);
|
|
3510
|
+
}
|
|
3511
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
3512
|
+
new MutationObserver(() => _sendViewport())
|
|
3513
|
+
.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] });
|
|
3514
|
+
}
|
|
3515
|
+
// focus:enter/exit fire BEFORE ui-settings toggles #static-bg.visible (100 ms
|
|
3516
|
+
// stagger), so report after the class has landed.
|
|
3517
|
+
on('focus:enter', () => setTimeout(() => _sendViewport({ immediate: true }), 250));
|
|
3518
|
+
on('focus:exit', () => setTimeout(() => _sendViewport({ immediate: true }), 250));
|
|
3519
|
+
on('claude-panel:visibility', () => _sendViewport());
|
|
3520
|
+
|
|
3521
|
+
// First measurement of text/list cards once the real font is in (metrics
|
|
3522
|
+
// taken with a fallback font would be wrong by a few px per glyph).
|
|
3523
|
+
if (document.fonts?.ready) {
|
|
3524
|
+
document.fonts.ready.then(_scheduleMeasure).catch(() => {});
|
|
3525
|
+
document.fonts.addEventListener?.('loadingdone', _scheduleMeasure);
|
|
3526
|
+
}
|
|
3527
|
+
|
|
2925
3528
|
// ── Guest permission: disable drawing tools when whiteboard perm is off ──
|
|
2926
3529
|
function updateWbPermVisual() {
|
|
2927
3530
|
if (!_toolbar || !_canvas) return;
|
|
@@ -2968,15 +3571,12 @@ function _connectWhiteboardWS() {
|
|
|
2968
3571
|
_ws.onopen = () => {
|
|
2969
3572
|
console.log('[whiteboard-ws] Connected');
|
|
2970
3573
|
if (_wsReconnectTimer) { clearInterval(_wsReconnectTimer); _wsReconnectTimer = null; }
|
|
2971
|
-
// Report usable viewport
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
const tbW = toolbar ? toolbar.getBoundingClientRect().right - rect.left + 12 : 60;
|
|
2978
|
-
_ws.send(JSON.stringify({ type: 'viewport', width: Math.round(rect.width - tbW), height: Math.round(rect.height - navH), yOffset: Math.round(navH), xOffset: Math.round(tbW) }));
|
|
2979
|
-
}
|
|
3574
|
+
// Report the usable viewport (excluding navbar, tool rail, right panel,
|
|
3575
|
+
// terminal) for MCP coordinate mapping, then flush any measured sizes that
|
|
3576
|
+
// were collected while the socket was down.
|
|
3577
|
+
_lastViewportJson = '';
|
|
3578
|
+
_sendViewport({ immediate: true });
|
|
3579
|
+
_queueGeometrySend();
|
|
2980
3580
|
};
|
|
2981
3581
|
|
|
2982
3582
|
_ws.onmessage = (event) => {
|
|
@@ -2997,21 +3597,63 @@ function _connectWhiteboardWS() {
|
|
|
2997
3597
|
_ws.onerror = () => {}; // onclose fires after
|
|
2998
3598
|
}
|
|
2999
3599
|
|
|
3000
|
-
//
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3600
|
+
// ── Viewport reporting ──
|
|
3601
|
+
|
|
3602
|
+
function _cssPx(name) {
|
|
3603
|
+
const v = parseFloat(getComputedStyle(document.documentElement).getPropertyValue(name));
|
|
3604
|
+
return Number.isFinite(v) ? v : 0;
|
|
3605
|
+
}
|
|
3606
|
+
|
|
3607
|
+
/** Usable whiteboard viewport in CSS px relative to #wb-root's top-left: the
|
|
3608
|
+
* root minus the left tool rail, the navbar and the docked right-side agent
|
|
3609
|
+
* panel (its reserved width is published as --right-panel-width by
|
|
3610
|
+
* ui-sidepanel-layout.js; 0 when hidden or floating). #wb-root already ends
|
|
3611
|
+
* above a docked terminal (--terminal-height). Sent to the server so MCP
|
|
3612
|
+
* coordinate math and screenshots describe what the user can actually see. */
|
|
3613
|
+
function _computeViewport() {
|
|
3614
|
+
const rect = _root.getBoundingClientRect();
|
|
3615
|
+
const titleBar = document.getElementById('title-bar');
|
|
3616
|
+
const toolbar = document.getElementById('wb-toolbar');
|
|
3617
|
+
const navH = titleBar ? titleBar.getBoundingClientRect().height : _cssPx('--navbar-height');
|
|
3618
|
+
const tbW = toolbar ? Math.max(0, toolbar.getBoundingClientRect().right - rect.left + 12) : 60;
|
|
3619
|
+
const rightW = Math.max(0, _cssPx('--right-panel-width') || _cssPx('--claude-panel-width'));
|
|
3620
|
+
return {
|
|
3621
|
+
type: 'viewport',
|
|
3622
|
+
clientId: _clientId,
|
|
3623
|
+
focusActive: isWhiteboardActive(),
|
|
3624
|
+
visible: document.visibilityState === 'visible',
|
|
3625
|
+
width: Math.max(0, Math.round(rect.width - tbW - rightW)),
|
|
3626
|
+
height: Math.max(0, Math.round(rect.height - navH)),
|
|
3627
|
+
xOffset: Math.round(tbW),
|
|
3628
|
+
yOffset: Math.round(navH),
|
|
3629
|
+
rootWidth: Math.round(rect.width),
|
|
3630
|
+
rootHeight: Math.round(rect.height),
|
|
3631
|
+
rightPanelWidth: Math.round(rightW),
|
|
3632
|
+
dpr: window.devicePixelRatio || 1,
|
|
3633
|
+
};
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
let _viewportTimer = null;
|
|
3637
|
+
let _lastViewportJson = '';
|
|
3638
|
+
|
|
3639
|
+
/** Debounced (150 ms), deduplicated viewport report. Every trigger also drops
|
|
3640
|
+
* the gesture-cached root rect since the layout just changed. */
|
|
3641
|
+
function _sendViewport({ immediate = false } = {}) {
|
|
3642
|
+
_invalidateRootRect();
|
|
3643
|
+
clearTimeout(_viewportTimer);
|
|
3644
|
+
const send = () => {
|
|
3645
|
+
_viewportTimer = null;
|
|
3646
|
+
if (!_ws || _ws.readyState !== 1 || !_root) return;
|
|
3647
|
+
const json = JSON.stringify(_computeViewport());
|
|
3648
|
+
if (json === _lastViewportJson) return;
|
|
3649
|
+
_lastViewportJson = json;
|
|
3650
|
+
_ws.send(json);
|
|
3651
|
+
};
|
|
3652
|
+
if (immediate) send();
|
|
3653
|
+
else _viewportTimer = setTimeout(send, 150);
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
window.addEventListener('resize', () => _sendViewport());
|
|
3015
3657
|
|
|
3016
3658
|
function _handleExternalCommand(msg) {
|
|
3017
3659
|
switch (msg.type) {
|
|
@@ -3020,7 +3662,17 @@ function _handleExternalCommand(msg) {
|
|
|
3020
3662
|
|
|
3021
3663
|
case 'add':
|
|
3022
3664
|
for (const el of (msg.elements || [])) {
|
|
3023
|
-
|
|
3665
|
+
const idx = _elements.findIndex(e => e.id === el.id);
|
|
3666
|
+
if (idx >= 0) {
|
|
3667
|
+
// The server re-sent an id we already hold — replace in place
|
|
3668
|
+
// (no duplicate DOM node, no extra undo entry).
|
|
3669
|
+
_elements[idx] = el;
|
|
3670
|
+
if (Number.isFinite(el.zIndex)) _nextZIndex = Math.max(_nextZIndex, el.zIndex + 1);
|
|
3671
|
+
_invalidateElementMap();
|
|
3672
|
+
renderAll();
|
|
3673
|
+
continue;
|
|
3674
|
+
}
|
|
3675
|
+
addElement(el, { skipPersist: true, keepZIndex: true });
|
|
3024
3676
|
}
|
|
3025
3677
|
break;
|
|
3026
3678
|
|
|
@@ -3049,30 +3701,79 @@ function _handleExternalCommand(msg) {
|
|
|
3049
3701
|
break;
|
|
3050
3702
|
}
|
|
3051
3703
|
|
|
3052
|
-
case 'clear':
|
|
3704
|
+
case 'clear': {
|
|
3705
|
+
// External (MCP) clear is UNDOABLE: push a delete-multi entry (undo()
|
|
3706
|
+
// pushes the copies back) instead of wiping the user's history.
|
|
3707
|
+
_beginStateReplace({ exitEdits: true });
|
|
3708
|
+
if (_elements.length) {
|
|
3709
|
+
pushUndo('delete-multi', _elements.map(el => ({ ...el, points: el.points ? el.points.map(p => [...p]) : undefined })), null);
|
|
3710
|
+
} else {
|
|
3711
|
+
_nextZIndex = 1;
|
|
3712
|
+
}
|
|
3053
3713
|
_elements = [];
|
|
3054
|
-
|
|
3055
|
-
_undoStack = [];
|
|
3056
|
-
_redoStack = [];
|
|
3057
|
-
_selectedId = null;
|
|
3058
|
-
_selectedIds.clear();
|
|
3059
|
-
_arrowCreating = null;
|
|
3060
|
-
_sectionCreating = null;
|
|
3714
|
+
_invalidateElementMap();
|
|
3061
3715
|
renderAll();
|
|
3716
|
+
updateUndoButtons();
|
|
3062
3717
|
break;
|
|
3718
|
+
}
|
|
3063
3719
|
|
|
3064
3720
|
case 'state:full':
|
|
3065
|
-
// Full state sync from another client
|
|
3721
|
+
// Full state sync relayed from another client. The server already
|
|
3722
|
+
// persisted it (saveWhiteboard); just apply locally — no re-persist.
|
|
3723
|
+
// Do NOT exit an in-progress text edit here (the user may be typing);
|
|
3724
|
+
// gestures are cancelled so a drag can't continue on ghost objects.
|
|
3725
|
+
_beginStateReplace({ exitEdits: false });
|
|
3066
3726
|
_elements = msg.snapshot?.elements || [];
|
|
3067
3727
|
_nextZIndex = msg.snapshot?.nextZIndex || 1;
|
|
3068
|
-
|
|
3069
|
-
|
|
3728
|
+
_invalidateElementMap();
|
|
3729
|
+
renderAll();
|
|
3730
|
+
break;
|
|
3731
|
+
|
|
3732
|
+
case 'batch': {
|
|
3733
|
+
// Several server-side ops (batch update/remove, delete cascades) applied
|
|
3734
|
+
// atomically: one undo entry, one render, no persist (server already has it).
|
|
3735
|
+
const ops = Array.isArray(msg.ops) ? msg.ops : [];
|
|
3736
|
+
if (!ops.length) break;
|
|
3737
|
+
const copyEl = (el) => ({ ...el, points: el.points ? el.points.map(p => [...p]) : undefined });
|
|
3738
|
+
const befores = [], afters = [], removed = [], addedIds = [], addedCopies = [];
|
|
3739
|
+
for (const op of ops) {
|
|
3740
|
+
if (op.op === 'update') {
|
|
3741
|
+
const el = _elements.find(e => e.id === op.id);
|
|
3742
|
+
if (!el) continue;
|
|
3743
|
+
befores.push(copyEl(el));
|
|
3744
|
+
for (const [key, value] of Object.entries(op.updates || {})) {
|
|
3745
|
+
if (key === 'id' || key === 'type') continue;
|
|
3746
|
+
el[key] = value;
|
|
3747
|
+
}
|
|
3748
|
+
afters.push(copyEl(el));
|
|
3749
|
+
} else if (op.op === 'remove') {
|
|
3750
|
+
const idx = _elements.findIndex(e => e.id === op.id);
|
|
3751
|
+
if (idx < 0) continue;
|
|
3752
|
+
removed.push(copyEl(_elements[idx]));
|
|
3753
|
+
_elements.splice(idx, 1);
|
|
3754
|
+
_selectedIds.delete(op.id);
|
|
3755
|
+
if (_selectedId === op.id) _selectedId = null;
|
|
3756
|
+
} else if (op.op === 'add' && op.element?.id) {
|
|
3757
|
+
if (_elements.some(e => e.id === op.element.id)) continue;
|
|
3758
|
+
const el = op.element;
|
|
3759
|
+
if (Number.isFinite(el.zIndex)) _nextZIndex = Math.max(_nextZIndex, el.zIndex + 1);
|
|
3760
|
+
else el.zIndex = _nextZIndex++;
|
|
3761
|
+
_elements.push(el);
|
|
3762
|
+
addedIds.push(el.id);
|
|
3763
|
+
addedCopies.push(copyEl(el));
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
if (befores.length || removed.length || addedIds.length) {
|
|
3767
|
+
pushUndo('batch', { updated: befores, removed, added: addedIds }, { updated: afters, added: addedCopies });
|
|
3768
|
+
}
|
|
3769
|
+
if (_selectedIds.size === 0) hideContextMenu();
|
|
3770
|
+
_invalidateElementMap();
|
|
3070
3771
|
renderAll();
|
|
3071
|
-
storage.setItem(KEYS.WHITEBOARD, JSON.stringify({ elements: _elements, nextZIndex: _nextZIndex }));
|
|
3072
3772
|
break;
|
|
3773
|
+
}
|
|
3073
3774
|
|
|
3074
3775
|
case 'screenshot:request':
|
|
3075
|
-
_captureScreenshot(msg.requestId);
|
|
3776
|
+
_captureScreenshot(msg.requestId, { crop: msg.crop });
|
|
3076
3777
|
break;
|
|
3077
3778
|
|
|
3078
3779
|
case 'screenshot:auto': {
|
|
@@ -3112,138 +3813,267 @@ function _loadImage(src) {
|
|
|
3112
3813
|
});
|
|
3113
3814
|
}
|
|
3114
3815
|
|
|
3115
|
-
function
|
|
3816
|
+
function _roundRectPath(ctx, x, y, w, h, r) {
|
|
3817
|
+
const rr = Math.max(0, Math.min(r, w / 2, h / 2));
|
|
3818
|
+
ctx.moveTo(x + rr, y);
|
|
3819
|
+
ctx.lineTo(x + w - rr, y);
|
|
3820
|
+
ctx.arcTo(x + w, y, x + w, y + rr, rr);
|
|
3821
|
+
ctx.lineTo(x + w, y + h - rr);
|
|
3822
|
+
ctx.arcTo(x + w, y + h, x + w - rr, y + h, rr);
|
|
3823
|
+
ctx.lineTo(x + rr, y + h);
|
|
3824
|
+
ctx.arcTo(x, y + h, x, y + h - rr, rr);
|
|
3825
|
+
ctx.lineTo(x, y + rr);
|
|
3826
|
+
ctx.arcTo(x, y, x + rr, y, rr);
|
|
3827
|
+
ctx.closePath();
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
/** `color` with an alpha byte applied (hex2 like '0d'). Handles #rgb/#rrggbb
|
|
3831
|
+
* and rgb()/rgba(); anything else is returned unchanged. */
|
|
3832
|
+
function _withAlpha(color, hex2) {
|
|
3833
|
+
const a = parseInt(hex2, 16) / 255;
|
|
3834
|
+
let m = /^#([0-9a-f]{6})$/i.exec(color);
|
|
3835
|
+
if (m) return color + hex2;
|
|
3836
|
+
m = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color);
|
|
3837
|
+
if (m) return `#${m[1]}${m[1]}${m[2]}${m[2]}${m[3]}${m[3]}${hex2}`;
|
|
3838
|
+
m = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i.exec(color);
|
|
3839
|
+
if (m) return `rgba(${m[1]},${m[2]},${m[3]},${a.toFixed(3)})`;
|
|
3840
|
+
return color;
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
/** Arrowhead identical to the SVG marker (path M 2 2 L 18 10 L 2 18, refX 2,
|
|
3844
|
+
* refY 10): a 16px triangle extending past the last point along the end tangent. */
|
|
3845
|
+
function _drawArrowhead(ctx, points, fill) {
|
|
3116
3846
|
if (points.length < 2) return;
|
|
3117
3847
|
const last = points[points.length - 1];
|
|
3118
3848
|
const prev = points[points.length - 2];
|
|
3119
3849
|
const angle = Math.atan2(last[1] - prev[1], last[0] - prev[0]);
|
|
3120
|
-
|
|
3121
|
-
ctx.
|
|
3850
|
+
ctx.save();
|
|
3851
|
+
ctx.translate(last[0], last[1]);
|
|
3852
|
+
ctx.rotate(angle);
|
|
3853
|
+
ctx.fillStyle = fill;
|
|
3122
3854
|
ctx.beginPath();
|
|
3123
|
-
ctx.moveTo(
|
|
3124
|
-
ctx.lineTo(
|
|
3125
|
-
ctx.lineTo(
|
|
3855
|
+
ctx.moveTo(0, -8);
|
|
3856
|
+
ctx.lineTo(16, 0);
|
|
3857
|
+
ctx.lineTo(0, 8);
|
|
3126
3858
|
ctx.closePath();
|
|
3127
3859
|
ctx.fill();
|
|
3860
|
+
ctx.restore();
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
/** Baseline offset from the top of a CSS line box of `lineH` px for the
|
|
3864
|
+
* current ctx.font (half-leading + ascent), with metric fallbacks. */
|
|
3865
|
+
function _lineBaseline(ctx, fs, lineH) {
|
|
3866
|
+
const m = ctx.measureText('Mg');
|
|
3867
|
+
const ascent = m.fontBoundingBoxAscent > 0 ? m.fontBoundingBoxAscent : fs * 1.02;
|
|
3868
|
+
const descent = m.fontBoundingBoxDescent > 0 ? m.fontBoundingBoxDescent : fs * 0.3;
|
|
3869
|
+
return (lineH - (ascent + descent)) / 2 + ascent;
|
|
3128
3870
|
}
|
|
3129
3871
|
|
|
3130
|
-
|
|
3872
|
+
/**
|
|
3873
|
+
* Re-render the board to an offscreen canvas for the MCP whiteboard_screenshot
|
|
3874
|
+
* tool. This is a RECONSTRUCTION, so every stroke reuses the generators and
|
|
3875
|
+
* constants the DOM uses: JetBrains Mono with the CSS paddings, the seeded
|
|
3876
|
+
* hand-drawn shape paths, anchor-resolved Catmull-Rom arrows with the
|
|
3877
|
+
* marker-shaped head, section/image chrome. Output is scaled up to 2x (capped
|
|
3878
|
+
* so the long edge stays ≤ 2000 px) and the response carries the coordinate
|
|
3879
|
+
* mapping (image px ÷ scale = canvas px) plus the usable viewport.
|
|
3880
|
+
*/
|
|
3881
|
+
async function _captureScreenshot(requestId, { crop } = {}) {
|
|
3131
3882
|
try {
|
|
3132
|
-
|
|
3883
|
+
flushTextEdits();
|
|
3884
|
+
flushListEdits();
|
|
3885
|
+
try {
|
|
3886
|
+
if (document.fonts?.ready) await document.fonts.ready;
|
|
3887
|
+
if (document.fonts?.load) {
|
|
3888
|
+
await Promise.allSettled([
|
|
3889
|
+
document.fonts.load('400 22px "JetBrains Mono"'),
|
|
3890
|
+
document.fonts.load('700 22px "JetBrains Mono"'),
|
|
3891
|
+
document.fonts.load('italic 400 22px "JetBrains Mono"'),
|
|
3892
|
+
]);
|
|
3893
|
+
}
|
|
3894
|
+
} catch { /* draw with whatever is loaded */ }
|
|
3895
|
+
|
|
3133
3896
|
const rootRect = _root.getBoundingClientRect();
|
|
3134
|
-
|
|
3135
|
-
|
|
3897
|
+
const vp = _computeViewport();
|
|
3898
|
+
const usable = { x: vp.xOffset, y: vp.yOffset, width: vp.width, height: vp.height };
|
|
3899
|
+
const box = crop === 'usable'
|
|
3900
|
+
? usable
|
|
3901
|
+
: { x: 0, y: 0, width: Math.round(rootRect.width), height: Math.round(rootRect.height) };
|
|
3902
|
+
const outW = Math.max(1, Math.round(box.width));
|
|
3903
|
+
const outH = Math.max(1, Math.round(box.height));
|
|
3904
|
+
let scale = Math.min(2, window.devicePixelRatio || 1);
|
|
3905
|
+
const longEdge = Math.max(outW, outH);
|
|
3906
|
+
if (longEdge * scale > 2000) scale = 2000 / longEdge;
|
|
3907
|
+
|
|
3908
|
+
const canvas = document.createElement('canvas');
|
|
3909
|
+
canvas.width = Math.max(1, Math.round(outW * scale));
|
|
3910
|
+
canvas.height = Math.max(1, Math.round(outH * scale));
|
|
3136
3911
|
const ctx = canvas.getContext('2d');
|
|
3912
|
+
ctx.scale(scale, scale);
|
|
3913
|
+
ctx.translate(-box.x, -box.y);
|
|
3914
|
+
ctx.imageSmoothingQuality = 'high';
|
|
3915
|
+
|
|
3916
|
+
// Background — same as #static-bg
|
|
3917
|
+
ctx.fillStyle = '#070709';
|
|
3918
|
+
ctx.fillRect(box.x, box.y, outW, outH);
|
|
3137
3919
|
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3920
|
+
const mono = '"JetBrains Mono", monospace';
|
|
3921
|
+
const bodyFont = getComputedStyle(document.body).fontFamily || 'sans-serif';
|
|
3922
|
+
const tttActive = _root.classList.contains('ttt-active');
|
|
3923
|
+
const domById = new Map();
|
|
3924
|
+
if (_elementsDiv) for (const child of _elementsDiv.children) domById.set(child.dataset.wbId, child);
|
|
3141
3925
|
|
|
3142
3926
|
// Sort by zIndex for correct layering
|
|
3143
3927
|
const sorted = [..._elements].sort((a, b) => (a.zIndex || 0) - (b.zIndex || 0));
|
|
3144
3928
|
|
|
3145
3929
|
for (const el of sorted) {
|
|
3146
3930
|
ctx.save();
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
ctx.font = `${style}${weight} ${el.fontSize || 22}px Caveat, cursive`;
|
|
3160
|
-
ctx.fillStyle = el.color || 'rgba(255,255,255,0.85)';
|
|
3161
|
-
ctx.textBaseline = 'top';
|
|
3162
|
-
const lines = (el.content || '').split('\n');
|
|
3163
|
-
let lineY = el.y + 10;
|
|
3164
|
-
for (const line of lines) {
|
|
3165
|
-
ctx.fillText(line, el.x + 14, lineY);
|
|
3166
|
-
lineY += (el.fontSize || 22) * 1.35;
|
|
3931
|
+
try {
|
|
3932
|
+
// Border-box size: measured for text/list (DOM fallback if unmeasured),
|
|
3933
|
+
// explicit or default for the rest.
|
|
3934
|
+
let bw = el.width || 0, bh = el.height || 0;
|
|
3935
|
+
if ((el.type === 'text' || el.type === 'list') && (!bw || !bh)) {
|
|
3936
|
+
const dom = domById.get(el.id);
|
|
3937
|
+
if (dom) { bw = dom.offsetWidth; bh = dom.offsetHeight; }
|
|
3938
|
+
} else if (el.type === 'shape') {
|
|
3939
|
+
bw = el.width || 160; bh = el.height || 100;
|
|
3940
|
+
} else if (el.type === 'section') {
|
|
3941
|
+
const def = SECTION_TYPES[el.sectionType] || SECTION_TYPES.content;
|
|
3942
|
+
bw = el.width || def.w; bh = el.height || def.h;
|
|
3167
3943
|
}
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
ctx.
|
|
3177
|
-
lineY += fs * 1.5;
|
|
3944
|
+
|
|
3945
|
+
// CSS transform: rotate() about the rendered box centre (DOM elements
|
|
3946
|
+
// only — arrows/pen bake rotation into their points)
|
|
3947
|
+
if (el.rotation && el.type !== 'arrow' && el.type !== 'pen') {
|
|
3948
|
+
const cx = el.x + bw / 2;
|
|
3949
|
+
const cy = el.y + bh / 2;
|
|
3950
|
+
ctx.translate(cx, cy);
|
|
3951
|
+
ctx.rotate(el.rotation * Math.PI / 180);
|
|
3952
|
+
ctx.translate(-cx, -cy);
|
|
3178
3953
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
ctx.
|
|
3187
|
-
|
|
3188
|
-
ctx
|
|
3189
|
-
|
|
3190
|
-
const
|
|
3954
|
+
|
|
3955
|
+
if (el.type === 'text') {
|
|
3956
|
+
// .wb-text: 1px border + 10px/14px padding, line-height 1.35
|
|
3957
|
+
const fs = el.fontSize || 22;
|
|
3958
|
+
ctx.font = `${el.italic ? 'italic ' : ''}${el.bold ? '700' : '400'} ${fs}px ${mono}`;
|
|
3959
|
+
ctx.fillStyle = el.color || 'rgba(255,255,255,0.85)';
|
|
3960
|
+
ctx.textBaseline = 'alphabetic';
|
|
3961
|
+
ctx.textAlign = 'left';
|
|
3962
|
+
const lineH = fs * 1.35;
|
|
3963
|
+
const baseOff = _lineBaseline(ctx, fs, lineH);
|
|
3964
|
+
let lineTop = el.y + 11;
|
|
3965
|
+
for (const line of (el.content || '').split('\n')) {
|
|
3966
|
+
if (line) ctx.fillText(line, el.x + 15, lineTop + baseOff);
|
|
3967
|
+
lineTop += lineH;
|
|
3968
|
+
}
|
|
3969
|
+
} else if (el.type === 'list') {
|
|
3970
|
+
// .wb-list: same box, line-height 1.5, <ul>/<ol> padding-left 1.4em
|
|
3971
|
+
const fs = el.fontSize || 18;
|
|
3972
|
+
ctx.font = `400 ${fs}px ${mono}`;
|
|
3973
|
+
ctx.fillStyle = el.color || 'rgba(255,255,255,0.85)';
|
|
3974
|
+
ctx.textBaseline = 'alphabetic';
|
|
3975
|
+
const lineH = fs * 1.5;
|
|
3976
|
+
const baseOff = _lineBaseline(ctx, fs, lineH);
|
|
3977
|
+
const items = el.items && el.items.length ? el.items : [''];
|
|
3978
|
+
const contentLeft = el.x + 15 + 1.4 * fs;
|
|
3979
|
+
let lineTop = el.y + 11;
|
|
3980
|
+
items.forEach((item, i) => {
|
|
3981
|
+
const baseline = lineTop + baseOff;
|
|
3982
|
+
ctx.textAlign = 'right';
|
|
3983
|
+
ctx.fillText(el.ordered ? `${i + 1}.` : '•', contentLeft - 0.6 * fs, baseline);
|
|
3984
|
+
ctx.textAlign = 'left';
|
|
3985
|
+
if (item) ctx.fillText(item, contentLeft, baseline);
|
|
3986
|
+
lineTop += lineH;
|
|
3987
|
+
});
|
|
3988
|
+
} else if (el.type === 'shape') {
|
|
3989
|
+
// Same seeded hand-drawn path the DOM's inner SVG uses (stroke 4, 5px inset)
|
|
3990
|
+
const shape = el.shape || 'rect';
|
|
3991
|
+
const d = shape === 'drawn-circle'
|
|
3992
|
+
? generateDrawnCirclePath(bw, bh, el.id)
|
|
3993
|
+
: generateHandDrawnShape(shape, bw, bh, el.id);
|
|
3994
|
+
ctx.translate(el.x, el.y);
|
|
3995
|
+
ctx.strokeStyle = el.color || 'rgba(255,255,255,0.8)';
|
|
3996
|
+
ctx.lineWidth = 4;
|
|
3997
|
+
ctx.lineCap = 'round';
|
|
3998
|
+
ctx.lineJoin = 'round';
|
|
3999
|
+
ctx.stroke(new Path2D(d));
|
|
4000
|
+
} else if (el.type === 'image' && el.dataUrl) {
|
|
4001
|
+
const img = await _loadImage(el.dataUrl).catch(() => null);
|
|
4002
|
+
if (img && bw > 0 && bh > 0) {
|
|
4003
|
+
// .wb-image: 2px border, radius 16 (img radius 14, object-fit: cover);
|
|
4004
|
+
// during tic-tac-toe the chrome is stripped and cell markers are 20% opaque
|
|
4005
|
+
const inset = tttActive ? 0 : 2;
|
|
4006
|
+
const radius = tttActive ? 0 : 14;
|
|
4007
|
+
const dx = el.x + inset, dy = el.y + inset;
|
|
4008
|
+
const dw = Math.max(0, bw - inset * 2), dh = Math.max(0, bh - inset * 2);
|
|
4009
|
+
const nw = img.naturalWidth || img.width || dw, nh = img.naturalHeight || img.height || dh;
|
|
4010
|
+
const s = Math.max(dw / nw, dh / nh);
|
|
4011
|
+
const sw = dw / s, sh = dh / s;
|
|
4012
|
+
const sx = (nw - sw) / 2, sy = (nh - sh) / 2;
|
|
4013
|
+
ctx.save();
|
|
4014
|
+
if (tttActive && String(el.id).startsWith('ttt-cell-')) ctx.globalAlpha = 0.2;
|
|
4015
|
+
ctx.beginPath();
|
|
4016
|
+
_roundRectPath(ctx, dx, dy, dw, dh, radius);
|
|
4017
|
+
ctx.clip();
|
|
4018
|
+
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
4019
|
+
ctx.restore();
|
|
4020
|
+
if (!tttActive) {
|
|
4021
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
|
|
4022
|
+
ctx.lineWidth = 2;
|
|
4023
|
+
ctx.beginPath();
|
|
4024
|
+
_roundRectPath(ctx, el.x + 1, el.y + 1, bw - 2, bh - 2, 16);
|
|
4025
|
+
ctx.stroke();
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
} else if (el.type === 'arrow' && el.points && el.points.length >= 2) {
|
|
4029
|
+
// Anchor-resolved, Catmull-Rom smoothed — identical to renderArrows()
|
|
4030
|
+
const pts = resolveArrowPoints(el);
|
|
4031
|
+
ctx.strokeStyle = el.color || 'rgba(255,255,255,0.8)';
|
|
4032
|
+
ctx.lineWidth = 2;
|
|
4033
|
+
ctx.lineCap = 'butt';
|
|
4034
|
+
ctx.lineJoin = 'round';
|
|
4035
|
+
ctx.stroke(new Path2D(computeArrowPath(pts)));
|
|
4036
|
+
_drawArrowhead(ctx, pts, el.color || 'rgba(255,255,255,0.9)');
|
|
4037
|
+
} else if (el.type === 'pen' && el.points && el.points.length >= 2) {
|
|
4038
|
+
ctx.strokeStyle = el.color || 'rgba(255,255,255,0.8)';
|
|
4039
|
+
ctx.lineWidth = el.strokeWidth || 3;
|
|
4040
|
+
ctx.lineCap = 'round';
|
|
4041
|
+
ctx.lineJoin = 'round';
|
|
4042
|
+
ctx.stroke(new Path2D(el.pathD || pointsToSmoothPath(el.points)));
|
|
4043
|
+
} else if (el.type === 'section') {
|
|
4044
|
+
// .wb-section: 1px dashed border in the section colour, radius 4,
|
|
4045
|
+
// background colour@0d, centred 28px icon @15%, 11px mono label @70%
|
|
4046
|
+
const def = SECTION_TYPES[el.sectionType] || SECTION_TYPES.content;
|
|
4047
|
+
const color = el.color || def.color;
|
|
3191
4048
|
ctx.beginPath();
|
|
3192
|
-
ctx
|
|
4049
|
+
_roundRectPath(ctx, el.x, el.y, bw, bh, 4);
|
|
4050
|
+
ctx.fillStyle = _withAlpha(color, '0d');
|
|
4051
|
+
ctx.fill();
|
|
4052
|
+
ctx.setLineDash([3, 3]);
|
|
4053
|
+
ctx.lineWidth = 1;
|
|
4054
|
+
ctx.strokeStyle = color;
|
|
3193
4055
|
ctx.stroke();
|
|
3194
|
-
|
|
3195
|
-
ctx.
|
|
4056
|
+
ctx.setLineDash([]);
|
|
4057
|
+
ctx.save();
|
|
4058
|
+
ctx.globalAlpha = 0.15;
|
|
4059
|
+
ctx.fillStyle = color;
|
|
4060
|
+
ctx.font = `28px ${bodyFont}`;
|
|
4061
|
+
ctx.textAlign = 'center';
|
|
4062
|
+
ctx.textBaseline = 'middle';
|
|
4063
|
+
ctx.fillText(def.icon, el.x + bw / 2, el.y + bh / 2);
|
|
4064
|
+
ctx.restore();
|
|
4065
|
+
ctx.save();
|
|
4066
|
+
ctx.globalAlpha = 0.7;
|
|
4067
|
+
ctx.fillStyle = color;
|
|
4068
|
+
ctx.font = `11px ${mono}`;
|
|
4069
|
+
ctx.textAlign = 'left';
|
|
4070
|
+
ctx.textBaseline = 'top';
|
|
4071
|
+
ctx.fillText(el.label || def.label, el.x + 8, el.y + 4);
|
|
4072
|
+
ctx.restore();
|
|
3196
4073
|
}
|
|
3197
|
-
}
|
|
3198
|
-
|
|
3199
|
-
const img = await _loadImage(el.dataUrl);
|
|
3200
|
-
ctx.drawImage(img, el.x, el.y, el.width, el.height);
|
|
3201
|
-
} catch { /* skip broken images */ }
|
|
3202
|
-
} else if (el.type === 'arrow' && el.points && el.points.length >= 2) {
|
|
3203
|
-
ctx.strokeStyle = el.color || 'rgba(255,255,255,0.7)';
|
|
3204
|
-
ctx.lineWidth = 3;
|
|
3205
|
-
ctx.lineCap = 'round';
|
|
3206
|
-
ctx.lineJoin = 'round';
|
|
3207
|
-
ctx.beginPath();
|
|
3208
|
-
ctx.moveTo(el.points[0][0], el.points[0][1]);
|
|
3209
|
-
for (let i = 1; i < el.points.length; i++) {
|
|
3210
|
-
ctx.lineTo(el.points[i][0], el.points[i][1]);
|
|
3211
|
-
}
|
|
3212
|
-
ctx.stroke();
|
|
3213
|
-
_drawArrowhead(ctx, el.points);
|
|
3214
|
-
} else if (el.type === 'pen' && el.points && el.points.length >= 2) {
|
|
3215
|
-
ctx.strokeStyle = el.color || 'rgba(255,255,255,0.7)';
|
|
3216
|
-
ctx.lineWidth = el.strokeWidth || 3;
|
|
3217
|
-
ctx.lineCap = 'round';
|
|
3218
|
-
ctx.lineJoin = 'round';
|
|
3219
|
-
const pathD = el.pathD || pointsToSmoothPath(el.points);
|
|
3220
|
-
const p2d = new Path2D(pathD);
|
|
3221
|
-
ctx.stroke(p2d);
|
|
3222
|
-
} else if (el.type === 'section') {
|
|
3223
|
-
const def = SECTION_TYPES[el.sectionType] || SECTION_TYPES.content;
|
|
3224
|
-
const color = el.color || def.color;
|
|
3225
|
-
const w = el.width || def.w;
|
|
3226
|
-
const h = el.height || def.h;
|
|
3227
|
-
ctx.strokeStyle = color;
|
|
3228
|
-
ctx.lineWidth = 1;
|
|
3229
|
-
ctx.setLineDash([6, 4]);
|
|
3230
|
-
ctx.strokeRect(el.x, el.y, w, h);
|
|
3231
|
-
ctx.setLineDash([]);
|
|
3232
|
-
ctx.fillStyle = color + '1a';
|
|
3233
|
-
ctx.fillRect(el.x, el.y, w, h);
|
|
3234
|
-
ctx.font = '28px sans-serif';
|
|
3235
|
-
ctx.fillStyle = color + '33';
|
|
3236
|
-
ctx.textAlign = 'center';
|
|
3237
|
-
ctx.textBaseline = 'middle';
|
|
3238
|
-
ctx.fillText(def.icon, el.x + w / 2, el.y + h / 2);
|
|
3239
|
-
ctx.font = '12px JetBrains Mono, monospace';
|
|
3240
|
-
ctx.fillStyle = color;
|
|
3241
|
-
ctx.textAlign = 'left';
|
|
3242
|
-
ctx.textBaseline = 'top';
|
|
3243
|
-
ctx.fillText(el.label || def.label, el.x + 8, el.y + 6);
|
|
3244
|
-
ctx.textAlign = 'start';
|
|
4074
|
+
} catch (elErr) {
|
|
4075
|
+
console.warn('[whiteboard] screenshot: skipped element', el.id, elErr);
|
|
3245
4076
|
}
|
|
3246
|
-
|
|
3247
4077
|
ctx.restore();
|
|
3248
4078
|
}
|
|
3249
4079
|
|
|
@@ -3251,12 +4081,25 @@ async function _captureScreenshot(requestId) {
|
|
|
3251
4081
|
const base64 = dataUrl.split(',')[1];
|
|
3252
4082
|
|
|
3253
4083
|
if (_ws && _ws.readyState === 1) {
|
|
3254
|
-
_ws.send(JSON.stringify({
|
|
4084
|
+
_ws.send(JSON.stringify({
|
|
4085
|
+
type: 'screenshot:response',
|
|
4086
|
+
requestId,
|
|
4087
|
+
data: base64,
|
|
4088
|
+
clientId: _clientId,
|
|
4089
|
+
focusActive: isWhiteboardActive(),
|
|
4090
|
+
width: outW,
|
|
4091
|
+
height: outH,
|
|
4092
|
+
scale,
|
|
4093
|
+
xOffset: vp.xOffset,
|
|
4094
|
+
yOffset: vp.yOffset,
|
|
4095
|
+
crop: crop === 'usable' ? 'usable' : 'full',
|
|
4096
|
+
usable,
|
|
4097
|
+
}));
|
|
3255
4098
|
}
|
|
3256
4099
|
} catch (err) {
|
|
3257
4100
|
console.error('[whiteboard] Screenshot capture failed:', err);
|
|
3258
4101
|
if (_ws && _ws.readyState === 1) {
|
|
3259
|
-
_ws.send(JSON.stringify({ type: 'screenshot:error', requestId, error: err.message }));
|
|
4102
|
+
_ws.send(JSON.stringify({ type: 'screenshot:error', requestId, error: err.message, clientId: _clientId }));
|
|
3260
4103
|
}
|
|
3261
4104
|
}
|
|
3262
4105
|
}
|