@bastani/atomic 0.9.13 → 0.9.14-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (762) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +3 -3
  3. package/dist/builtin/intercom/package.json +2 -2
  4. package/dist/builtin/mcp/package.json +3 -3
  5. package/dist/builtin/subagents/CHANGELOG.md +14 -0
  6. package/dist/builtin/subagents/README.md +6 -4
  7. package/dist/builtin/subagents/agents/code-simplifier.md +1 -1
  8. package/dist/builtin/subagents/agents/codebase-analyzer.md +1 -1
  9. package/dist/builtin/subagents/agents/codebase-locator.md +1 -1
  10. package/dist/builtin/subagents/agents/codebase-online-researcher.md +1 -1
  11. package/dist/builtin/subagents/agents/codebase-pattern-finder.md +1 -1
  12. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +1 -1
  13. package/dist/builtin/subagents/agents/codebase-research-locator.md +1 -1
  14. package/dist/builtin/subagents/agents/debugger.md +1 -1
  15. package/dist/builtin/subagents/agents/worker.md +1 -1
  16. package/dist/builtin/subagents/package.json +6 -5
  17. package/dist/builtin/subagents/src/agents/agent-discovery.ts +17 -11
  18. package/dist/builtin/subagents/src/agents/agent-loaders.ts +115 -43
  19. package/dist/builtin/subagents/src/agents/agent-management-helpers.ts +10 -8
  20. package/dist/builtin/subagents/src/agents/agent-management.ts +8 -7
  21. package/dist/builtin/subagents/src/agents/agent-overrides.ts +2 -2
  22. package/dist/builtin/subagents/src/agents/agent-scope.ts +1 -1
  23. package/dist/builtin/subagents/src/agents/agent-selection.ts +1 -1
  24. package/dist/builtin/subagents/src/agents/agent-serializer.ts +24 -3
  25. package/dist/builtin/subagents/src/agents/agent-types.ts +3 -1
  26. package/dist/builtin/subagents/src/agents/agents.ts +7 -5
  27. package/dist/builtin/subagents/src/agents/frontmatter.ts +67 -22
  28. package/dist/builtin/subagents/src/agents/identity.ts +1 -1
  29. package/dist/builtin/subagents/src/agents/skills-paths.ts +1 -1
  30. package/dist/builtin/subagents/src/agents/skills.ts +64 -6
  31. package/dist/builtin/subagents/src/extension/config.ts +1 -1
  32. package/dist/builtin/subagents/src/extension/control-notices.ts +2 -2
  33. package/dist/builtin/subagents/src/extension/doctor.ts +15 -5
  34. package/dist/builtin/subagents/src/extension/fanout-child.ts +12 -12
  35. package/dist/builtin/subagents/src/extension/index.ts +23 -23
  36. package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
  37. package/dist/builtin/subagents/src/extension/schemas.ts +1 -1
  38. package/dist/builtin/subagents/src/extension/startup-maintenance.ts +3 -3
  39. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +3 -3
  40. package/dist/builtin/subagents/src/intercom/result-intercom.ts +1 -1
  41. package/dist/builtin/subagents/src/intercom/supervisor-authorization.ts +1 -1
  42. package/dist/builtin/subagents/src/runs/foreground/completion-notification.ts +2 -2
  43. package/dist/builtin/subagents/src/runs/foreground/execution-detach-reservations.ts +1 -1
  44. package/dist/builtin/subagents/src/runs/foreground/execution-intercom-detach.ts +3 -3
  45. package/dist/builtin/subagents/src/runs/foreground/execution-run-sync.ts +1 -1
  46. package/dist/builtin/subagents/src/runs/foreground/execution-updates.ts +2 -2
  47. package/dist/builtin/subagents/src/runs/foreground/execution-utils.ts +1 -1
  48. package/dist/builtin/subagents/src/runs/foreground/execution.ts +2 -2
  49. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +27 -11
  50. package/dist/builtin/subagents/src/runs/foreground/notify.ts +3 -3
  51. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +11 -11
  52. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +3 -3
  53. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +15 -14
  54. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +19 -19
  55. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +2 -2
  56. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +14 -13
  57. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +9 -9
  58. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +7 -7
  59. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-worktree.ts +6 -6
  60. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +24 -23
  61. package/dist/builtin/subagents/src/runs/inprocess/control-registry.ts +1 -1
  62. package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +3 -3
  63. package/dist/builtin/subagents/src/runs/inprocess/index.ts +3 -3
  64. package/dist/builtin/subagents/src/runs/inprocess/prompt-behavior.ts +17 -8
  65. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +60 -12
  66. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-api.ts +8 -8
  67. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-control.ts +3 -3
  68. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-core.ts +4 -4
  69. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-projection.ts +4 -4
  70. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-registry.ts +5 -5
  71. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-rendering.ts +3 -3
  72. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-sanitize.ts +3 -3
  73. package/dist/builtin/subagents/src/runs/inprocess.ts +1 -1
  74. package/dist/builtin/subagents/src/runs/shared/long-running-guard.ts +1 -1
  75. package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +1 -1
  76. package/dist/builtin/subagents/src/runs/shared/model-candidate-filter.ts +3 -3
  77. package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +2 -2
  78. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -1
  79. package/dist/builtin/subagents/src/runs/shared/single-output.ts +1 -1
  80. package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +1 -1
  81. package/dist/builtin/subagents/src/shared/artifacts.ts +2 -2
  82. package/dist/builtin/subagents/src/shared/event-jsonl-writer.ts +1 -1
  83. package/dist/builtin/subagents/src/shared/fast-mode.ts +1 -1
  84. package/dist/builtin/subagents/src/shared/formatters.ts +2 -2
  85. package/dist/builtin/subagents/src/shared/jsonl-writer.ts +1 -1
  86. package/dist/builtin/subagents/src/shared/model-resolution.ts +1 -1
  87. package/dist/builtin/subagents/src/shared/session-tokens.ts +1 -1
  88. package/dist/builtin/subagents/src/shared/settings.ts +4 -4
  89. package/dist/builtin/subagents/src/shared/status-format.ts +1 -1
  90. package/dist/builtin/subagents/src/shared/types-config.ts +16 -4
  91. package/dist/builtin/subagents/src/shared/types-depth.ts +1 -1
  92. package/dist/builtin/subagents/src/shared/types-nested.ts +1 -1
  93. package/dist/builtin/subagents/src/shared/types-output.ts +1 -1
  94. package/dist/builtin/subagents/src/shared/types-results.ts +1 -1
  95. package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -1
  96. package/dist/builtin/subagents/src/shared/types.ts +6 -6
  97. package/dist/builtin/subagents/src/shared/utils.ts +3 -3
  98. package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +1 -1
  99. package/dist/builtin/subagents/src/slash/slash-bridge.ts +3 -3
  100. package/dist/builtin/subagents/src/slash/slash-commands.ts +6 -6
  101. package/dist/builtin/subagents/src/slash/slash-live-state.ts +3 -3
  102. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +2 -2
  103. package/dist/builtin/subagents/src/tui/render-layout.ts +1 -1
  104. package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
  105. package/dist/builtin/subagents/src/tui/render-result-compact.ts +7 -7
  106. package/dist/builtin/subagents/src/tui/render-result.ts +10 -10
  107. package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -1
  108. package/dist/builtin/subagents/src/tui/render-status-progress.ts +5 -5
  109. package/dist/builtin/subagents/src/tui/render.ts +4 -4
  110. package/dist/builtin/web-access/package.json +2 -2
  111. package/dist/builtin/workflows/CHANGELOG.md +57 -0
  112. package/dist/builtin/workflows/README.md +39 -8
  113. package/dist/builtin/workflows/builtin/adversarial-verification-runner.ts +16 -5
  114. package/dist/builtin/workflows/builtin/adversarial-verification.ts +4 -0
  115. package/dist/builtin/workflows/builtin/classify-and-act.ts +4 -0
  116. package/dist/builtin/workflows/builtin/fan-out-and-synthesize.ts +4 -0
  117. package/dist/builtin/workflows/builtin/generate-and-filter-runner.ts +12 -4
  118. package/dist/builtin/workflows/builtin/generate-and-filter.ts +4 -0
  119. package/dist/builtin/workflows/builtin/goal-models.ts +8 -10
  120. package/dist/builtin/workflows/builtin/goal.ts +4 -0
  121. package/dist/builtin/workflows/builtin/index.d.ts +0 -4
  122. package/dist/builtin/workflows/builtin/loop-until-done-runner.ts +4 -2
  123. package/dist/builtin/workflows/builtin/loop-until-done.ts +4 -0
  124. package/dist/builtin/workflows/builtin/open-claude-design-live-protocol.ts +305 -0
  125. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +115 -171
  126. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +9 -25
  127. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +19 -86
  128. package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +0 -6
  129. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +0 -5
  130. package/dist/builtin/workflows/builtin/open-claude-design.ts +11 -15
  131. package/dist/builtin/workflows/builtin/ralph-models.ts +24 -29
  132. package/dist/builtin/workflows/builtin/ralph.ts +4 -0
  133. package/dist/builtin/workflows/builtin/tournament-runner.ts +6 -3
  134. package/dist/builtin/workflows/builtin/tournament.ts +4 -0
  135. package/dist/builtin/workflows/package.json +2 -2
  136. package/dist/builtin/workflows/skills/impeccable/SKILL.md +3 -3
  137. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_asset_producer.toml +25 -31
  138. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_documenter.toml +1 -0
  139. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_finish_reviewer.toml +16 -10
  140. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_manual_edit_applier.toml +2 -2
  141. package/dist/builtin/workflows/skills/impeccable/reference/android.md +6 -0
  142. package/dist/builtin/workflows/skills/impeccable/reference/animate.md +3 -0
  143. package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +3 -1
  144. package/dist/builtin/workflows/skills/impeccable/reference/craft-floor.md +6 -1
  145. package/dist/builtin/workflows/skills/impeccable/reference/critique.md +18 -2
  146. package/dist/builtin/workflows/skills/impeccable/reference/degraded/asset-producer.md +24 -30
  147. package/dist/builtin/workflows/skills/impeccable/reference/degraded/documenter.md +1 -0
  148. package/dist/builtin/workflows/skills/impeccable/reference/degraded/finish-reviewer.md +16 -10
  149. package/dist/builtin/workflows/skills/impeccable/reference/degraded/manual-edit-applier.md +2 -2
  150. package/dist/builtin/workflows/skills/impeccable/reference/distill.md +1 -1
  151. package/dist/builtin/workflows/skills/impeccable/reference/doctor.md +1 -0
  152. package/dist/builtin/workflows/skills/impeccable/reference/document.md +1 -1
  153. package/dist/builtin/workflows/skills/impeccable/reference/extract.md +1 -1
  154. package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +12 -6
  155. package/dist/builtin/workflows/skills/impeccable/reference/init.md +12 -3
  156. package/dist/builtin/workflows/skills/impeccable/reference/ios.md +6 -0
  157. package/dist/builtin/workflows/skills/impeccable/reference/live-setup.md +102 -0
  158. package/dist/builtin/workflows/skills/impeccable/reference/live.md +113 -521
  159. package/dist/builtin/workflows/skills/impeccable/reference/new-work.md +39 -26
  160. package/dist/builtin/workflows/skills/impeccable/reference/overdrive.md +1 -1
  161. package/dist/builtin/workflows/skills/impeccable/reference/polish.md +3 -3
  162. package/dist/builtin/workflows/skills/impeccable/reference/quieter.md +1 -1
  163. package/dist/builtin/workflows/skills/impeccable/reference/visualize.md +25 -11
  164. package/dist/builtin/workflows/skills/impeccable/scripts/concept-seed.mjs +332 -221
  165. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +10 -19
  166. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +91 -38
  167. package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +19 -10
  168. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +56 -6
  169. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +10 -16
  170. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +140 -2
  171. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +1174 -694
  172. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +1 -0
  173. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +5 -1
  174. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +431 -45
  175. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +10 -1
  176. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +47 -6
  177. package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +23 -22
  178. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +0 -10
  179. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +384 -384
  180. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/color.mjs +466 -2
  181. package/dist/builtin/workflows/skills/impeccable/scripts/doctor.mjs +2 -0
  182. package/dist/builtin/workflows/skills/impeccable/scripts/embed-prompt.mjs +270 -0
  183. package/dist/builtin/workflows/skills/impeccable/scripts/generate-image.mjs +51 -6
  184. package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +70 -10
  185. package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +336 -90
  186. package/dist/builtin/workflows/skills/impeccable/scripts/lib/composition-catalog.mjs +38 -3
  187. package/dist/builtin/workflows/skills/impeccable/scripts/lib/concept-catalog.mjs +77 -11
  188. package/dist/builtin/workflows/skills/impeccable/scripts/lib/design-parser.mjs +102 -19
  189. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +44 -59
  190. package/dist/builtin/workflows/skills/impeccable/scripts/lib/is-generated.mjs +3 -0
  191. package/dist/builtin/workflows/skills/impeccable/scripts/lib/open-system-browser.mjs +57 -0
  192. package/dist/builtin/workflows/skills/impeccable/scripts/lib/roll-selection.mjs +369 -0
  193. package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-deep.mjs +31 -3
  194. package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness.mjs +73 -2
  195. package/dist/builtin/workflows/skills/impeccable/scripts/live/accept-css.mjs +617 -0
  196. package/dist/builtin/workflows/skills/impeccable/scripts/live/accept-verify.mjs +71 -0
  197. package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +29 -2
  198. package/dist/builtin/workflows/skills/impeccable/scripts/live/event-validation.mjs +54 -7
  199. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/astro.mjs +47 -0
  200. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/detect-utils.mjs +157 -0
  201. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/index.mjs +143 -0
  202. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/journal.mjs +205 -0
  203. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/nextjs.mjs +49 -0
  204. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/nuxt.mjs +170 -0
  205. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/script-src.mjs +26 -0
  206. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/static-html.mjs +26 -0
  207. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/sveltekit.mjs +71 -0
  208. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/tag-strategy.mjs +247 -0
  209. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/tanstack-start.mjs +70 -0
  210. package/dist/builtin/workflows/skills/impeccable/scripts/live/frameworks/vite-generic.mjs +42 -0
  211. package/dist/builtin/workflows/skills/impeccable/scripts/live/instructions.mjs +169 -0
  212. package/dist/builtin/workflows/skills/impeccable/scripts/live/roots.mjs +670 -0
  213. package/dist/builtin/workflows/skills/impeccable/scripts/live/session-store.mjs +232 -35
  214. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-ast.mjs +969 -0
  215. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +621 -74
  216. package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +66 -28
  217. package/dist/builtin/workflows/skills/impeccable/scripts/live/tanstack-adapter.mjs +12 -12
  218. package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-surfaces.mjs +75 -0
  219. package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +135 -0
  220. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +23 -37
  221. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +945 -115
  222. package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +33 -1
  223. package/dist/builtin/workflows/skills/impeccable/scripts/live-copy-edit-agent.mjs +132 -15
  224. package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +173 -425
  225. package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +2 -0
  226. package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +16 -3
  227. package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +39 -10
  228. package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +194 -27
  229. package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +10 -4
  230. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +50 -31
  231. package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +91 -28
  232. package/dist/builtin/workflows/skills/impeccable/scripts/pin.mjs +8 -5
  233. package/dist/builtin/workflows/skills/impeccable/scripts/serve-question.mjs +759 -109
  234. package/dist/builtin/workflows/src/authoring/workflow.ts +9 -0
  235. package/dist/builtin/workflows/src/authoring.d.ts +3 -0
  236. package/dist/builtin/workflows/src/durable/completed-catalog.ts +23 -2
  237. package/dist/builtin/workflows/src/durable/dbos-backend.ts +30 -17
  238. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +14 -0
  239. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +60 -57
  240. package/dist/builtin/workflows/src/durable/dbos-process-owner.ts +87 -0
  241. package/dist/builtin/workflows/src/durable/dbos-registration-diagnostics.ts +113 -0
  242. package/dist/builtin/workflows/src/durable/factory.ts +11 -8
  243. package/dist/builtin/workflows/src/durable/tool-failure-checkpoint.ts +5 -0
  244. package/dist/builtin/workflows/src/durable/tool-primitive.ts +186 -20
  245. package/dist/builtin/workflows/src/durable/types.ts +4 -0
  246. package/dist/builtin/workflows/src/durable/workflow-heartbeat-anchor.ts +153 -0
  247. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +28 -0
  248. package/dist/builtin/workflows/src/engine/run-tool-control-registry.ts +9 -1
  249. package/dist/builtin/workflows/src/engine/run.ts +1 -0
  250. package/dist/builtin/workflows/src/extension/adopt-session-run-state.ts +20 -0
  251. package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +9 -30
  252. package/dist/builtin/workflows/src/extension/extension-factory.ts +3 -0
  253. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +28 -10
  254. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +138 -0
  255. package/dist/builtin/workflows/src/extension/index.bundle.mjs +3365 -1341
  256. package/dist/builtin/workflows/src/extension/workflow-heartbeat-delivery.ts +197 -0
  257. package/dist/builtin/workflows/src/extension/workflow-heartbeat-notice.ts +115 -0
  258. package/dist/builtin/workflows/src/extension/workflow-heartbeat-scheduler.ts +962 -0
  259. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +4 -1
  260. package/dist/builtin/workflows/src/extension/workflow-schema.ts +29 -5
  261. package/dist/builtin/workflows/src/extension/workflow-tool-send.ts +32 -1
  262. package/dist/builtin/workflows/src/runs/background/cancellation-registry.ts +10 -1
  263. package/dist/builtin/workflows/src/runs/background/job-tracker.ts +10 -1
  264. package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +16 -11
  265. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +31 -1
  266. package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +41 -10
  267. package/dist/builtin/workflows/src/runs/foreground/stage-runner-messages.ts +48 -0
  268. package/dist/builtin/workflows/src/runs/foreground/stage-runner-output.ts +4 -1
  269. package/dist/builtin/workflows/src/runs/foreground/stage-runner-structured-output.ts +45 -12
  270. package/dist/builtin/workflows/src/sdk-surface.ts +9 -0
  271. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +10 -0
  272. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +10 -0
  273. package/dist/builtin/workflows/src/shared/graph-store-snapshot.ts +14 -2
  274. package/dist/builtin/workflows/src/shared/persistence-restore.ts +12 -0
  275. package/dist/builtin/workflows/src/shared/prompt-answer.ts +130 -0
  276. package/dist/builtin/workflows/src/shared/session-scoped-singleton.ts +98 -0
  277. package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +9 -1
  278. package/dist/builtin/workflows/src/shared/store-factory.ts +9 -1
  279. package/dist/builtin/workflows/src/shared/store-public-types.ts +2 -1
  280. package/dist/builtin/workflows/src/shared/store-tool-node-methods.ts +7 -0
  281. package/dist/builtin/workflows/src/shared/store-types.ts +18 -1
  282. package/dist/builtin/workflows/src/shared/tool-payload-bounds.ts +576 -0
  283. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +1 -0
  284. package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +1 -0
  285. package/dist/builtin/workflows/src/shared/workflow-heartbeat-contract.d.ts +21 -0
  286. package/dist/builtin/workflows/src/shared/workflow-heartbeat-contract.ts +25 -0
  287. package/dist/builtin/workflows/src/tui/graph-theme.ts +8 -1
  288. package/dist/builtin/workflows/src/tui/graph-view-constants.ts +11 -0
  289. package/dist/builtin/workflows/src/tui/graph-view-input.ts +100 -5
  290. package/dist/builtin/workflows/src/tui/graph-view-layout.ts +42 -11
  291. package/dist/builtin/workflows/src/tui/graph-view-render-helpers.ts +55 -6
  292. package/dist/builtin/workflows/src/tui/graph-view-render.ts +71 -6
  293. package/dist/builtin/workflows/src/tui/graph-view-state.ts +43 -0
  294. package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +2 -0
  295. package/dist/builtin/workflows/src/tui/node-card.ts +5 -1
  296. package/dist/builtin/workflows/src/tui/stage-chat-view-archive-history.ts +11 -2
  297. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +36 -10
  298. package/dist/builtin/workflows/src/tui/switcher.ts +11 -4
  299. package/dist/builtin/workflows/src/tui/tool-detail.ts +348 -0
  300. package/dist/builtin/workflows/src/tui/workflow-status.ts +5 -2
  301. package/dist/cli/args.d.ts +1 -0
  302. package/dist/cli/args.d.ts.map +1 -1
  303. package/dist/cli/args.js +11 -0
  304. package/dist/cli/args.js.map +1 -1
  305. package/dist/cli/auth-check.d.ts +1 -1
  306. package/dist/cli/auth-check.js.map +1 -1
  307. package/dist/cli/credential-print.d.ts +1 -1
  308. package/dist/cli/credential-print.js.map +1 -1
  309. package/dist/cli/list-models.d.ts +1 -1
  310. package/dist/cli/list-models.js.map +1 -1
  311. package/dist/cli/startup-ui.d.ts +9 -0
  312. package/dist/cli/startup-ui.d.ts.map +1 -1
  313. package/dist/cli/startup-ui.js +7 -1
  314. package/dist/cli/startup-ui.js.map +1 -1
  315. package/dist/core/agent-session-custom-message-commit.js +2 -2
  316. package/dist/core/agent-session-custom-message-commit.js.map +1 -1
  317. package/dist/core/agent-session-export.d.ts +6 -1
  318. package/dist/core/agent-session-export.d.ts.map +1 -1
  319. package/dist/core/agent-session-export.js +6 -3
  320. package/dist/core/agent-session-export.js.map +1 -1
  321. package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
  322. package/dist/core/agent-session-extension-bindings.js +6 -4
  323. package/dist/core/agent-session-extension-bindings.js.map +1 -1
  324. package/dist/core/agent-session-methods.d.ts +5 -2
  325. package/dist/core/agent-session-methods.d.ts.map +1 -1
  326. package/dist/core/agent-session-methods.js.map +1 -1
  327. package/dist/core/agent-session-prompt.d.ts +12 -1
  328. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  329. package/dist/core/agent-session-prompt.js +31 -10
  330. package/dist/core/agent-session-prompt.js.map +1 -1
  331. package/dist/core/agent-session-runtime-auth.js.map +1 -1
  332. package/dist/core/agent-session-services.d.ts +1 -1
  333. package/dist/core/agent-session-services.js.map +1 -1
  334. package/dist/core/agent-session-skill-block.d.ts +1 -0
  335. package/dist/core/agent-session-skill-block.d.ts.map +1 -1
  336. package/dist/core/agent-session-skill-block.js +18 -2
  337. package/dist/core/agent-session-skill-block.js.map +1 -1
  338. package/dist/core/agent-session-state.d.ts.map +1 -1
  339. package/dist/core/agent-session-state.js +2 -1
  340. package/dist/core/agent-session-state.js.map +1 -1
  341. package/dist/core/agent-session-types.d.ts +2 -1
  342. package/dist/core/agent-session-types.d.ts.map +1 -1
  343. package/dist/core/agent-session-types.js.map +1 -1
  344. package/dist/core/agent-session.d.ts +1 -1
  345. package/dist/core/agent-session.js.map +1 -1
  346. package/dist/core/atomic-guide-command.js +2 -2
  347. package/dist/core/atomic-guide-command.js.map +1 -1
  348. package/dist/core/builtin-packages.d.ts.map +1 -1
  349. package/dist/core/builtin-packages.js +0 -6
  350. package/dist/core/builtin-packages.js.map +1 -1
  351. package/dist/core/cloudflare-gateway-binding.d.ts +11 -0
  352. package/dist/core/cloudflare-gateway-binding.d.ts.map +1 -0
  353. package/dist/core/cloudflare-gateway-binding.js +11 -0
  354. package/dist/core/cloudflare-gateway-binding.js.map +1 -0
  355. package/dist/core/codex-fast-mode-transport.d.ts +12 -0
  356. package/dist/core/codex-fast-mode-transport.d.ts.map +1 -0
  357. package/dist/core/codex-fast-mode-transport.js +130 -0
  358. package/dist/core/codex-fast-mode-transport.js.map +1 -0
  359. package/dist/core/codex-fast-mode.d.ts +20 -6
  360. package/dist/core/codex-fast-mode.d.ts.map +1 -1
  361. package/dist/core/codex-fast-mode.js +111 -3
  362. package/dist/core/codex-fast-mode.js.map +1 -1
  363. package/dist/core/diagnostics.d.ts +4 -0
  364. package/dist/core/diagnostics.d.ts.map +1 -1
  365. package/dist/core/diagnostics.js.map +1 -1
  366. package/dist/core/event-bus.d.ts +12 -0
  367. package/dist/core/event-bus.d.ts.map +1 -1
  368. package/dist/core/event-bus.js +22 -0
  369. package/dist/core/event-bus.js.map +1 -1
  370. package/dist/core/experimental.d.ts +25 -0
  371. package/dist/core/experimental.d.ts.map +1 -1
  372. package/dist/core/experimental.js +24 -0
  373. package/dist/core/experimental.js.map +1 -1
  374. package/dist/core/export-html/template-js/tree-filter-render.js +4 -3
  375. package/dist/core/export-html/template.js +4 -3
  376. package/dist/core/extension-session-state.d.ts +28 -0
  377. package/dist/core/extension-session-state.d.ts.map +1 -0
  378. package/dist/core/extension-session-state.js +55 -0
  379. package/dist/core/extension-session-state.js.map +1 -0
  380. package/dist/core/extensions/api-types.d.ts +6 -0
  381. package/dist/core/extensions/api-types.d.ts.map +1 -1
  382. package/dist/core/extensions/api-types.js.map +1 -1
  383. package/dist/core/extensions/context-types.d.ts +4 -0
  384. package/dist/core/extensions/context-types.d.ts.map +1 -1
  385. package/dist/core/extensions/context-types.js.map +1 -1
  386. package/dist/core/extensions/index.d.ts +1 -0
  387. package/dist/core/extensions/index.d.ts.map +1 -1
  388. package/dist/core/extensions/index.js +1 -0
  389. package/dist/core/extensions/index.js.map +1 -1
  390. package/dist/core/extensions/loader-api.d.ts +1 -1
  391. package/dist/core/extensions/loader-api.d.ts.map +1 -1
  392. package/dist/core/extensions/loader-api.js +16 -10
  393. package/dist/core/extensions/loader-api.js.map +1 -1
  394. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  395. package/dist/core/extensions/loader-virtual-modules.js +12 -1
  396. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  397. package/dist/core/extensions/runner-context.d.ts +2 -0
  398. package/dist/core/extensions/runner-context.d.ts.map +1 -1
  399. package/dist/core/extensions/runner-context.js +8 -0
  400. package/dist/core/extensions/runner-context.js.map +1 -1
  401. package/dist/core/extensions/runner.d.ts +1 -0
  402. package/dist/core/extensions/runner.d.ts.map +1 -1
  403. package/dist/core/extensions/runner.js +3 -0
  404. package/dist/core/extensions/runner.js.map +1 -1
  405. package/dist/core/extensions/runtime-types.d.ts +3 -0
  406. package/dist/core/extensions/runtime-types.d.ts.map +1 -1
  407. package/dist/core/extensions/runtime-types.js.map +1 -1
  408. package/dist/core/extensions/ui-types.d.ts +26 -0
  409. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  410. package/dist/core/extensions/ui-types.js +17 -1
  411. package/dist/core/extensions/ui-types.js.map +1 -1
  412. package/dist/core/http-dispatcher.d.ts.map +1 -1
  413. package/dist/core/http-dispatcher.js +5 -0
  414. package/dist/core/http-dispatcher.js.map +1 -1
  415. package/dist/core/keybindings.d.ts +24 -0
  416. package/dist/core/keybindings.d.ts.map +1 -1
  417. package/dist/core/keybindings.js +6 -0
  418. package/dist/core/keybindings.js.map +1 -1
  419. package/dist/core/model-config.d.ts +5 -0
  420. package/dist/core/model-config.d.ts.map +1 -1
  421. package/dist/core/model-config.js +1 -0
  422. package/dist/core/model-config.js.map +1 -1
  423. package/dist/core/model-registry.d.ts +1 -1
  424. package/dist/core/model-registry.js.map +1 -1
  425. package/dist/core/model-resolver-cli.d.ts +1 -1
  426. package/dist/core/model-resolver-cli.js.map +1 -1
  427. package/dist/core/model-resolver-defaults.js +3 -3
  428. package/dist/core/model-resolver-defaults.js.map +1 -1
  429. package/dist/core/model-resolver-initial.d.ts +1 -1
  430. package/dist/core/model-resolver-initial.js.map +1 -1
  431. package/dist/core/model-resolver-scope.d.ts +1 -1
  432. package/dist/core/model-resolver-scope.js.map +1 -1
  433. package/dist/core/model-runtime.d.ts +54 -0
  434. package/dist/core/model-runtime.d.ts.map +1 -1
  435. package/dist/core/model-runtime.js +96 -13
  436. package/dist/core/model-runtime.js.map +1 -1
  437. package/dist/core/oauth-login.js.map +1 -1
  438. package/dist/core/project-trust.d.ts +10 -0
  439. package/dist/core/project-trust.d.ts.map +1 -1
  440. package/dist/core/project-trust.js +12 -0
  441. package/dist/core/project-trust.js.map +1 -1
  442. package/dist/core/resource-loader-assets.d.ts.map +1 -1
  443. package/dist/core/resource-loader-assets.js +14 -5
  444. package/dist/core/resource-loader-assets.js.map +1 -1
  445. package/dist/core/resource-loader-core.d.ts +3 -0
  446. package/dist/core/resource-loader-core.d.ts.map +1 -1
  447. package/dist/core/resource-loader-core.js +5 -0
  448. package/dist/core/resource-loader-core.js.map +1 -1
  449. package/dist/core/resource-loader-internals.d.ts +2 -0
  450. package/dist/core/resource-loader-internals.d.ts.map +1 -1
  451. package/dist/core/resource-loader-internals.js.map +1 -1
  452. package/dist/core/resource-loader-reload.d.ts.map +1 -1
  453. package/dist/core/resource-loader-reload.js +2 -0
  454. package/dist/core/resource-loader-reload.js.map +1 -1
  455. package/dist/core/resource-loader-types.d.ts +2 -0
  456. package/dist/core/resource-loader-types.d.ts.map +1 -1
  457. package/dist/core/resource-loader-types.js.map +1 -1
  458. package/dist/core/resource-loader.d.ts +2 -0
  459. package/dist/core/resource-loader.d.ts.map +1 -1
  460. package/dist/core/resource-loader.js +1 -0
  461. package/dist/core/resource-loader.js.map +1 -1
  462. package/dist/core/sdk-types.d.ts +7 -6
  463. package/dist/core/sdk-types.d.ts.map +1 -1
  464. package/dist/core/sdk-types.js.map +1 -1
  465. package/dist/core/sdk.d.ts.map +1 -1
  466. package/dist/core/sdk.js +17 -6
  467. package/dist/core/sdk.js.map +1 -1
  468. package/dist/core/session-manager-core.d.ts +7 -1
  469. package/dist/core/session-manager-core.d.ts.map +1 -1
  470. package/dist/core/session-manager-core.js +11 -2
  471. package/dist/core/session-manager-core.js.map +1 -1
  472. package/dist/core/session-manager-entries.d.ts +2 -2
  473. package/dist/core/session-manager-entries.d.ts.map +1 -1
  474. package/dist/core/session-manager-entries.js +6 -4
  475. package/dist/core/session-manager-entries.js.map +1 -1
  476. package/dist/core/session-manager-list.d.ts.map +1 -1
  477. package/dist/core/session-manager-list.js +5 -1
  478. package/dist/core/session-manager-list.js.map +1 -1
  479. package/dist/core/session-manager-types.d.ts +25 -2
  480. package/dist/core/session-manager-types.d.ts.map +1 -1
  481. package/dist/core/session-manager-types.js.map +1 -1
  482. package/dist/core/session-manager.d.ts +1 -1
  483. package/dist/core/session-manager.d.ts.map +1 -1
  484. package/dist/core/session-manager.js.map +1 -1
  485. package/dist/core/settings-manager-basic-accessors.d.ts +1 -0
  486. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  487. package/dist/core/settings-manager-basic-accessors.js +8 -0
  488. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  489. package/dist/core/settings-manager-ui-accessors.d.ts +3 -1
  490. package/dist/core/settings-manager-ui-accessors.d.ts.map +1 -1
  491. package/dist/core/settings-manager-ui-accessors.js +9 -0
  492. package/dist/core/settings-manager-ui-accessors.js.map +1 -1
  493. package/dist/core/settings-manager.d.ts +1 -1
  494. package/dist/core/settings-manager.d.ts.map +1 -1
  495. package/dist/core/settings-manager.js.map +1 -1
  496. package/dist/core/settings-types.d.ts +4 -0
  497. package/dist/core/settings-types.d.ts.map +1 -1
  498. package/dist/core/settings-types.js.map +1 -1
  499. package/dist/core/skill-catalog.d.ts +35 -0
  500. package/dist/core/skill-catalog.d.ts.map +1 -0
  501. package/dist/core/skill-catalog.js +264 -0
  502. package/dist/core/skill-catalog.js.map +1 -0
  503. package/dist/core/skills-async.d.ts.map +1 -1
  504. package/dist/core/skills-async.js +12 -6
  505. package/dist/core/skills-async.js.map +1 -1
  506. package/dist/core/skills.d.ts +2 -0
  507. package/dist/core/skills.d.ts.map +1 -1
  508. package/dist/core/skills.js +8 -5
  509. package/dist/core/skills.js.map +1 -1
  510. package/dist/core/slash-commands.d.ts.map +1 -1
  511. package/dist/core/slash-commands.js +1 -5
  512. package/dist/core/slash-commands.js.map +1 -1
  513. package/dist/core/system-prompt.js +2 -2
  514. package/dist/core/system-prompt.js.map +1 -1
  515. package/dist/core/tools/ask-user-question/ask-user-question.d.ts +25 -2
  516. package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
  517. package/dist/core/tools/ask-user-question/ask-user-question.js +47 -18
  518. package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
  519. package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts.map +1 -1
  520. package/dist/core/tools/ask-user-question/view/components/multi-select-view.js +5 -2
  521. package/dist/core/tools/ask-user-question/view/components/multi-select-view.js.map +1 -1
  522. package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts.map +1 -1
  523. package/dist/core/tools/ask-user-question/view/components/submit-picker.js +3 -1
  524. package/dist/core/tools/ask-user-question/view/components/submit-picker.js.map +1 -1
  525. package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
  526. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +8 -1
  527. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
  528. package/dist/core/tools/bash.d.ts.map +1 -1
  529. package/dist/core/tools/bash.js +2 -0
  530. package/dist/core/tools/bash.js.map +1 -1
  531. package/dist/core/tools/edit.d.ts.map +1 -1
  532. package/dist/core/tools/edit.js +2 -0
  533. package/dist/core/tools/edit.js.map +1 -1
  534. package/dist/core/tools/find.d.ts.map +1 -1
  535. package/dist/core/tools/find.js +3 -1
  536. package/dist/core/tools/find.js.map +1 -1
  537. package/dist/core/tools/grep.js +1 -1
  538. package/dist/core/tools/grep.js.map +1 -1
  539. package/dist/core/tools/hashline-engine/format.d.ts +2 -0
  540. package/dist/core/tools/hashline-engine/format.d.ts.map +1 -1
  541. package/dist/core/tools/hashline-engine/format.js +4 -2
  542. package/dist/core/tools/hashline-engine/format.js.map +1 -1
  543. package/dist/core/tools/hashline-engine/messages.d.ts +2 -0
  544. package/dist/core/tools/hashline-engine/messages.d.ts.map +1 -1
  545. package/dist/core/tools/hashline-engine/messages.js +4 -2
  546. package/dist/core/tools/hashline-engine/messages.js.map +1 -1
  547. package/dist/core/tools/hashline-engine/parser.d.ts.map +1 -1
  548. package/dist/core/tools/hashline-engine/parser.js +16 -4
  549. package/dist/core/tools/hashline-engine/parser.js.map +1 -1
  550. package/dist/core/tools/hashline-engine/tokenizer.d.ts.map +1 -1
  551. package/dist/core/tools/hashline-engine/tokenizer.js +37 -27
  552. package/dist/core/tools/hashline-engine/tokenizer.js.map +1 -1
  553. package/dist/core/tools/hashline.d.ts.map +1 -1
  554. package/dist/core/tools/hashline.js +8 -2
  555. package/dist/core/tools/hashline.js.map +1 -1
  556. package/dist/core/tools/ls.d.ts.map +1 -1
  557. package/dist/core/tools/ls.js +2 -0
  558. package/dist/core/tools/ls.js.map +1 -1
  559. package/dist/core/tools/read.d.ts.map +1 -1
  560. package/dist/core/tools/read.js +8 -1
  561. package/dist/core/tools/read.js.map +1 -1
  562. package/dist/core/tools/search.d.ts.map +1 -1
  563. package/dist/core/tools/search.js +2 -0
  564. package/dist/core/tools/search.js.map +1 -1
  565. package/dist/core/tools/todos.d.ts +3 -12
  566. package/dist/core/tools/todos.d.ts.map +1 -1
  567. package/dist/core/tools/todos.js +19 -4
  568. package/dist/core/tools/todos.js.map +1 -1
  569. package/dist/core/tools/write.d.ts.map +1 -1
  570. package/dist/core/tools/write.js +2 -0
  571. package/dist/core/tools/write.js.map +1 -1
  572. package/dist/index-extensions.d.ts +1 -1
  573. package/dist/index-extensions.d.ts.map +1 -1
  574. package/dist/index-extensions.js +1 -1
  575. package/dist/index-extensions.js.map +1 -1
  576. package/dist/index.d.ts +8 -5
  577. package/dist/index.d.ts.map +1 -1
  578. package/dist/index.js +6 -2
  579. package/dist/index.js.map +1 -1
  580. package/dist/main-runtime-api-key.d.ts +1 -1
  581. package/dist/main-runtime-api-key.js.map +1 -1
  582. package/dist/main-session-options.d.ts +1 -1
  583. package/dist/main-session-options.js.map +1 -1
  584. package/dist/main.d.ts.map +1 -1
  585. package/dist/main.js +10 -2
  586. package/dist/main.js.map +1 -1
  587. package/dist/modes/interactive/components/chat-session-host-rendering.js +1 -1
  588. package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
  589. package/dist/modes/interactive/components/chat-session-host-state.d.ts +8 -0
  590. package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
  591. package/dist/modes/interactive/components/chat-session-host-state.js +8 -0
  592. package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
  593. package/dist/modes/interactive/components/chat-session-host.d.ts +26 -0
  594. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  595. package/dist/modes/interactive/components/chat-session-host.js +38 -0
  596. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  597. package/dist/modes/interactive/components/chat-transcript.d.ts +37 -0
  598. package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
  599. package/dist/modes/interactive/components/chat-transcript.js +54 -1
  600. package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
  601. package/dist/modes/interactive/components/fast-mode-selector.js +1 -1
  602. package/dist/modes/interactive/components/fast-mode-selector.js.map +1 -1
  603. package/dist/modes/interactive/components/model-selector.d.ts +1 -1
  604. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  605. package/dist/modes/interactive/components/model-selector.js +2 -1
  606. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  607. package/dist/modes/interactive/components/oauth-selector.d.ts +1 -1
  608. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  609. package/dist/modes/interactive/components/reserved-bottom-overlay.d.ts +95 -0
  610. package/dist/modes/interactive/components/reserved-bottom-overlay.d.ts.map +1 -0
  611. package/dist/modes/interactive/components/reserved-bottom-overlay.js +234 -0
  612. package/dist/modes/interactive/components/reserved-bottom-overlay.js.map +1 -0
  613. package/dist/modes/interactive/components/session-selector-search.d.ts +4 -0
  614. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  615. package/dist/modes/interactive/components/session-selector-search.js +4 -0
  616. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  617. package/dist/modes/interactive/components/settings-selector-handlers.d.ts.map +1 -1
  618. package/dist/modes/interactive/components/settings-selector-handlers.js +3 -0
  619. package/dist/modes/interactive/components/settings-selector-handlers.js.map +1 -1
  620. package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
  621. package/dist/modes/interactive/components/settings-selector-items.js +7 -0
  622. package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
  623. package/dist/modes/interactive/components/settings-selector-types.d.ts +3 -1
  624. package/dist/modes/interactive/components/settings-selector-types.d.ts.map +1 -1
  625. package/dist/modes/interactive/components/settings-selector-types.js.map +1 -1
  626. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  627. package/dist/modes/interactive/components/tool-execution.js +12 -1
  628. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  629. package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
  630. package/dist/modes/interactive/interactive-auth-routing.js.map +1 -1
  631. package/dist/modes/interactive/interactive-autocomplete.js +5 -4
  632. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  633. package/dist/modes/interactive/interactive-bash-compact.d.ts.map +1 -1
  634. package/dist/modes/interactive/interactive-bash-compact.js +2 -2
  635. package/dist/modes/interactive/interactive-bash-compact.js.map +1 -1
  636. package/dist/modes/interactive/interactive-extension-custom-ui.js +94 -6
  637. package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
  638. package/dist/modes/interactive/interactive-mode-base.d.ts +41 -3
  639. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  640. package/dist/modes/interactive/interactive-mode-base.js +80 -11
  641. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  642. package/dist/modes/interactive/interactive-mode-surface.d.ts +9 -1
  643. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  644. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  645. package/dist/modes/interactive/interactive-mode-types.d.ts +2 -0
  646. package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
  647. package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
  648. package/dist/modes/interactive/interactive-model-catalog-startup.d.ts +8 -1
  649. package/dist/modes/interactive/interactive-model-catalog-startup.d.ts.map +1 -1
  650. package/dist/modes/interactive/interactive-model-catalog-startup.js +13 -3
  651. package/dist/modes/interactive/interactive-model-catalog-startup.js.map +1 -1
  652. package/dist/modes/interactive/interactive-model-routing.js +3 -4
  653. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  654. package/dist/modes/interactive/interactive-render-chat.js +6 -0
  655. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  656. package/dist/modes/interactive/interactive-resource-disclosure.js +2 -2
  657. package/dist/modes/interactive/interactive-resource-disclosure.js.map +1 -1
  658. package/dist/modes/interactive/interactive-selectors.js +7 -3
  659. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  660. package/dist/modes/interactive/interactive-session-runtime.js +7 -1
  661. package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
  662. package/dist/modes/interactive/interactive-slash-commands.js +2 -2
  663. package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
  664. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  665. package/dist/modes/interactive/interactive-startup.js +34 -9
  666. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  667. package/dist/modes/interactive/interactive-tui.d.ts +21 -1
  668. package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
  669. package/dist/modes/interactive/interactive-tui.js +140 -38
  670. package/dist/modes/interactive/interactive-tui.js.map +1 -1
  671. package/dist/modes/interactive/model-catalog-refresh.d.ts +17 -0
  672. package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
  673. package/dist/modes/interactive/model-catalog-refresh.js +84 -0
  674. package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
  675. package/dist/modes/interactive/theme/dark.json +2 -0
  676. package/dist/modes/interactive/theme/light.json +2 -0
  677. package/dist/modes/interactive/theme/theme-class.d.ts +12 -3
  678. package/dist/modes/interactive/theme/theme-class.d.ts.map +1 -1
  679. package/dist/modes/interactive/theme/theme-class.js +6 -1
  680. package/dist/modes/interactive/theme/theme-class.js.map +1 -1
  681. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  682. package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
  683. package/dist/modes/interactive/theme/theme-controller.js +23 -10
  684. package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
  685. package/dist/modes/interactive/theme/theme-loading.d.ts.map +1 -1
  686. package/dist/modes/interactive/theme/theme-loading.js +1 -0
  687. package/dist/modes/interactive/theme/theme-loading.js.map +1 -1
  688. package/dist/modes/interactive/theme/theme-schema.d.ts +8 -0
  689. package/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  690. package/dist/modes/interactive/theme/theme-schema.js +3 -1
  691. package/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  692. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  693. package/dist/modes/interactive-engine/engine-custom-ui.d.ts +2 -0
  694. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  695. package/dist/modes/interactive-engine/engine-custom-ui.js +1 -0
  696. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  697. package/dist/modes/interactive-engine/protocol.d.ts +1 -0
  698. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  699. package/dist/modes/interactive-engine/protocol.js +1 -0
  700. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  701. package/dist/modes/interactive-engine/remote-component.d.ts +3 -1
  702. package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
  703. package/dist/modes/interactive-engine/remote-component.js +3 -2
  704. package/dist/modes/interactive-engine/remote-component.js.map +1 -1
  705. package/dist/modes/json-event.d.ts +6 -1
  706. package/dist/modes/json-event.d.ts.map +1 -1
  707. package/dist/modes/json-event.js +17 -2
  708. package/dist/modes/json-event.js.map +1 -1
  709. package/dist/modes/rpc/rpc-client.js.map +1 -1
  710. package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
  711. package/dist/modes/rpc/rpc-command-handler.js +5 -4
  712. package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
  713. package/dist/modes/rpc/rpc-provider-auth.js.map +1 -1
  714. package/dist/modes/rpc/rpc-responses.js.map +1 -1
  715. package/dist/modes/rpc/rpc-types.d.ts +1 -1
  716. package/dist/modes/rpc/rpc-types.js.map +1 -1
  717. package/dist/package-manager-cli.js.map +1 -1
  718. package/dist/server/create-harness.d.ts.map +1 -1
  719. package/dist/server/create-harness.js +11 -0
  720. package/dist/server/create-harness.js.map +1 -1
  721. package/dist/utils/abort.d.ts +9 -0
  722. package/dist/utils/abort.d.ts.map +1 -0
  723. package/dist/utils/abort.js +19 -0
  724. package/dist/utils/abort.js.map +1 -0
  725. package/dist/utils/tools-manager.d.ts +16 -1
  726. package/dist/utils/tools-manager.d.ts.map +1 -1
  727. package/dist/utils/tools-manager.js +19 -19
  728. package/dist/utils/tools-manager.js.map +1 -1
  729. package/docs/custom-provider.md +9 -8
  730. package/docs/development.md +0 -1
  731. package/docs/docs.json +0 -1
  732. package/docs/environment-variables.md +3 -0
  733. package/docs/extensions.md +100 -2
  734. package/docs/json.md +3 -1
  735. package/docs/keybindings.md +8 -3
  736. package/docs/models/artificial-analysis-index.md +2 -2
  737. package/docs/models/model-selection.md +32 -23
  738. package/docs/models/pareto-efficiency.md +25 -24
  739. package/docs/providers.md +95 -2
  740. package/docs/quickstart.md +4 -2
  741. package/docs/rpc.md +21 -7
  742. package/docs/sdk.md +26 -1
  743. package/docs/session-format.md +5 -2
  744. package/docs/settings.md +34 -3
  745. package/docs/skills.md +16 -1
  746. package/docs/terminal-setup.md +15 -2
  747. package/docs/themes.md +22 -4
  748. package/docs/tools.md +3 -1
  749. package/docs/tui.md +2 -0
  750. package/docs/usage.md +5 -4
  751. package/docs/workflows.md +96 -19
  752. package/npm-shrinkwrap.json +211 -232
  753. package/package.json +8 -8
  754. package/dist/builtin/i-have-adhd/CHANGELOG.md +0 -30
  755. package/dist/builtin/i-have-adhd/LICENSE +0 -21
  756. package/dist/builtin/i-have-adhd/README.md +0 -18
  757. package/dist/builtin/i-have-adhd/index.ts +0 -238
  758. package/dist/builtin/i-have-adhd/package.json +0 -56
  759. package/dist/builtin/i-have-adhd/skills/i-have-adhd/SKILL.md +0 -140
  760. package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +0 -359
  761. package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +0 -180
  762. package/docs/i-have-adhd.md +0 -36
@@ -21,6 +21,7 @@
21
21
 
22
22
  const TOKEN = window.__IMPECCABLE_TOKEN__;
23
23
  const PORT = window.__IMPECCABLE_PORT__;
24
+ const APP_ROOT = window.__IMPECCABLE_APP_ROOT__ || null;
24
25
  if (!TOKEN || !PORT) {
25
26
  window.__IMPECCABLE_LIVE_INIT__ = false; // reset so the real load can init
26
27
  return;
@@ -96,23 +97,20 @@
96
97
  return { value: c.value, label: c.label };
97
98
  });
98
99
 
99
- const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions'];
100
- const LIVE_UI_SURFACES = [
101
- { key: 'global-bottom-bar', ids: [PREFIX + '-global-bar', PREFIX + '-global-bar-brand', PREFIX + '-pick-toggle', PREFIX + '-insert-toggle', PREFIX + '-detect-toggle', PREFIX + '-detect-badge', PREFIX + '-design-toggle', PREFIX + '-page-chat', PREFIX + '-page-chat-input', PREFIX + '-page-chat-voice'] },
102
- { key: 'pending-copy-edit-dock', ids: [PREFIX + '-pending-dock'] },
103
- { key: 'element-selection-chrome', ids: [PREFIX + '-highlight', PREFIX + '-tooltip', PREFIX + '-bar', PREFIX + '-selection-pill', PREFIX + '-input', PREFIX + '-configure-voice', PREFIX + '-configure-bar-tooltip'] },
104
- { key: 'action-picker', ids: [PREFIX + '-picker'] },
105
- { key: 'edit-chrome', ids: [PREFIX + '-edit-badge'] },
106
- { key: 'generating-row', ids: [PREFIX + '-bar', PREFIX + '-shader'] },
107
- { key: 'variant-cycling-row', ids: [PREFIX + '-bar', PREFIX + '-params-panel'] },
108
- { key: 'variant-params-panel', ids: [PREFIX + '-params-panel'] },
109
- { key: 'saving-confirmed-rows', ids: [PREFIX + '-bar'] },
110
- { key: 'insert-mode-chrome', ids: [PREFIX + '-insert-line', PREFIX + '-insert-placeholder', PREFIX + '-placeholder-resize', PREFIX + '-insert-input', PREFIX + '-insert-voice', PREFIX + '-insert-create', PREFIX + '-insert-create-tooltip'] },
111
- { key: 'annotation-chrome', ids: [PREFIX + '-annot', PREFIX + '-annot-svg', PREFIX + '-annot-pins', PREFIX + '-annot-clear'] },
112
- { key: 'design-system-panel', ids: [PREFIX + '-design-host'] },
113
- { key: 'toasts-and-errors', ids: [PREFIX + '-toast'] },
114
- { key: 'css-isolation-boundary', ids: [PREFIX + '-root'] },
115
- ];
100
+ // The Live chrome inventory (which surfaces exist, and the element ids each
101
+ // one owns) comes from the canonical source, skill/scripts/live/ui-surfaces.mjs,
102
+ // which the /live.js assembler serializes into these globals alongside the
103
+ // token/port/vocabulary. This file is served raw and injected as a classic
104
+ // script, so it cannot import that module; the private impeccable-site repo
105
+ // imports it directly to check its Live UI lab holds a snapshot for every
106
+ // surface, which only works while the list has exactly one definition.
107
+ // Add a surface in ui-surfaces.mjs, not here.
108
+ const LIVE_CHROME_MOUNT_CONTRACT = Array.isArray(window.__IMPECCABLE_LIVE_MOUNT_CONTRACT__)
109
+ ? window.__IMPECCABLE_LIVE_MOUNT_CONTRACT__
110
+ : ['root', 'transport', 'state', 'actions'];
111
+ const LIVE_UI_SURFACES = Array.isArray(window.__IMPECCABLE_LIVE_UI_SURFACES__)
112
+ ? window.__IMPECCABLE_LIVE_UI_SURFACES__
113
+ : [];
116
114
  const LIVE_UI_COMPONENT_IDS = [...new Set(LIVE_UI_SURFACES.flatMap((surface) => surface.ids))];
117
115
 
118
116
  //
@@ -131,8 +129,14 @@
131
129
  // must never regress: a `browser_resumed`/behind checkpoint re-broadcasts an
132
130
  // earlier phase (the server regresses the snapshot phase to `generating` on a
133
131
  // behind checkpoint), and without this the bar jumps backward mid-generation.
134
- // Unranked phases (params sidecar flow, unknown values) always pass so we
135
- // never block a phase we do not model.
132
+ // Unranked phases always pass so we never block a phase we do not model.
133
+ //
134
+ // Every `agent_phase` name here is emitted by recordAgentPhase() in
135
+ // live-server.mjs and listed in AGENT_PHASES in live/vocabulary.mjs, which the
136
+ // event validator enforces. This file is served raw and injected as an IIFE,
137
+ // so it cannot import that list; adding a phase means adding it in both.
138
+ // `queued`, `generating`, `variants_progress`, and `variants_ready` are set
139
+ // locally by this file and never arrive over the wire.
136
140
  const PHASE_RANK = {
137
141
  queued: 0,
138
142
  picked_up: 1,
@@ -142,17 +146,10 @@
142
146
  generation_ready: 5,
143
147
  generating: 5,
144
148
  variants_progress: 5,
145
- first_variant_generating: 6,
146
- first_variant_validating: 7,
147
149
  first_reviewable: 8,
148
- remaining_variants_generating: 9,
149
- remaining_variants_validating: 10,
150
150
  second_reviewable: 11,
151
151
  all_variants_ready: 12,
152
152
  variants_ready: 12,
153
- variant_parameters_generating: 13,
154
- variant_parameters_validating: 14,
155
- parameters_ready: 15,
156
153
  };
157
154
  function shouldAdvancePhase(current, next) {
158
155
  if (!next || next === current) return false;
@@ -167,6 +164,13 @@
167
164
  let svelteComponentSession = null;
168
165
  let svelteRuntimePromise = null;
169
166
  let pendingSvelteComponentRetryObserver = null;
167
+ // The persistent mount-error card. A failed import/mount used to wipe local
168
+ // session state and flash a 5s toast, which destroyed the only handle the
169
+ // user had on a session the server still considered live. The card stays up
170
+ // until the variant mounts, the user retries, or a new cycle starts.
171
+ let mountErrorEl = null;
172
+ let mountErrorState = null;
173
+ let lastReportedMountFailure = null;
170
174
  let currentSourceFile = null;
171
175
  let currentPreviewFile = null;
172
176
  let currentPreviewMode = null;
@@ -2025,6 +2029,9 @@
2025
2029
  state = next;
2026
2030
  window.__IMPECCABLE_LIVE_STATE__ = next;
2027
2031
  syncPageInteractionCursor();
2032
+ // Whether a queued steer is still behind a generation is a function of this
2033
+ // state, so the hint has to move with it, not only with the 5s poll.
2034
+ syncSteerQueueHint();
2028
2035
  }
2029
2036
 
2030
2037
  /** Element used to position the floating bar / shader during a session. */
@@ -2588,10 +2595,8 @@
2588
2595
  if (generationPhase === 'scaffolding') return 'Finding the source...';
2589
2596
  if (generationPhase === 'source_ready') return 'Source ready. Generating...';
2590
2597
  if (generationPhase === 'scaffold_fallback') return 'Agent is locating the source...';
2591
- if (generationPhase === 'first_variant_generating') return 'Designing the first variant...';
2592
- if (generationPhase === 'first_variant_validating') return 'Checking the first variant...';
2593
- if (generationPhase === 'remaining_variants_generating') return 'Exploring two more directions...';
2594
- if (generationPhase === 'remaining_variants_validating') return 'Checking the remaining variants...';
2598
+ if (generationPhase === 'first_reviewable') return 'First variant is ready. Exploring more...';
2599
+ if (generationPhase === 'second_reviewable') return 'Checking the remaining variants...';
2595
2600
  return 'Generating ' + expectedVariants + ' variants...';
2596
2601
  }
2597
2602
 
@@ -2599,6 +2604,30 @@
2599
2604
 
2600
2605
  const TUNE_ICON_SVG = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" style="flex-shrink:0"><line x1="4" y1="8" x2="20" y2="8"/><circle cx="14" cy="8" r="2.4" fill="currentColor" stroke="none"/><line x1="4" y1="16" x2="20" y2="16"/><circle cx="10" cy="16" r="2.4" fill="currentColor" stroke="none"/></svg>';
2601
2606
 
2607
+ /**
2608
+ * Which variant the user is actually looking at. For component previews the
2609
+ * mounted component is the truth; `visibleVariant` is the intent, and the two
2610
+ * differ while a mount is in flight.
2611
+ */
2612
+ function cyclingShownVariant() {
2613
+ return svelteComponentSession?.sessionId === currentSessionId && svelteComponentSession.mountedVariant > 0
2614
+ ? svelteComponentSession.mountedVariant
2615
+ : visibleVariant;
2616
+ }
2617
+
2618
+ /**
2619
+ * The single counter string. It is built here rather than at each call site
2620
+ * because the row builder and the incremental sync used to disagree on the
2621
+ * denominator: one showed the planned count, the other the arrived count, so
2622
+ * "2/3" turned into "2/2" on the next sync without anything changing on
2623
+ * screen. Arrived wins once anything has arrived; expected covers the window
2624
+ * before the first variant lands.
2625
+ */
2626
+ function cyclingCounterText() {
2627
+ const total = arrivedVariants > 0 ? arrivedVariants : expectedVariants;
2628
+ return cyclingShownVariant() + '/' + total;
2629
+ }
2630
+
2602
2631
  function buildCyclingRow() {
2603
2632
  if (!ensureCyclingRenderable('build-cycling-row')) {
2604
2633
  return el('div', { display: 'none' });
@@ -2612,7 +2641,7 @@
2612
2641
  const prev = navBtn('\u2190');
2613
2642
  prev.id = PREFIX + '-variant-prev';
2614
2643
  prev.addEventListener('click', (e) => { e.stopPropagation(); cycleVariant(-1); });
2615
- if (visibleVariant <= 1) prev.style.opacity = '0.3';
2644
+ if (cyclingShownVariant() <= 1) prev.style.opacity = '0.3';
2616
2645
  row.appendChild(prev);
2617
2646
 
2618
2647
  // Dots (clickable)
@@ -2624,14 +2653,14 @@
2624
2653
  color: BP.textDim, minWidth: '24px', textAlign: 'center',
2625
2654
  });
2626
2655
  counter.id = PREFIX + '-variant-counter';
2627
- counter.textContent = visibleVariant + '/' + expectedVariants;
2656
+ counter.textContent = cyclingCounterText();
2628
2657
  row.appendChild(counter);
2629
2658
 
2630
2659
  // Next
2631
2660
  const next = navBtn('\u2192');
2632
2661
  next.id = PREFIX + '-variant-next';
2633
2662
  next.addEventListener('click', (e) => { e.stopPropagation(); cycleVariant(1); });
2634
- if (visibleVariant >= arrivedVariants) next.style.opacity = '0.3';
2663
+ if (cyclingShownVariant() >= arrivedVariants) next.style.opacity = '0.3';
2635
2664
  row.appendChild(next);
2636
2665
 
2637
2666
  // Tune chip stays visible while the deferred parameter phase is running,
@@ -2996,7 +3025,7 @@
2996
3025
  console.warn('[impeccable] Refusing to render empty variant cycling state:', reason);
2997
3026
  const message = 'No variants were mounted. Please try again.';
2998
3027
  if (svelteComponentSession?.sessionId === currentSessionId) {
2999
- abortSvelteComponentInjection(currentSessionId, message);
3028
+ resetSvelteComponentSession(currentSessionId, message);
3000
3029
  return;
3001
3030
  }
3002
3031
  cleanup();
@@ -3734,7 +3763,10 @@
3734
3763
  const container = copyEditContainerContext(contextElement);
3735
3764
  if (container) for (const op of ops) op.container = container;
3736
3765
  try {
3737
- const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
3766
+ // Token in the query string as well as the body: the URL token is what
3767
+ // authorizes the CORS preflight when the page runs on a non-loopback
3768
+ // dev host (ddev, Valet), since the preflight carries no request body.
3769
+ const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
3738
3770
  method: 'POST',
3739
3771
  headers: { 'Content-Type': 'application/json' },
3740
3772
  body: JSON.stringify({
@@ -4926,11 +4958,9 @@
4926
4958
  }
4927
4959
 
4928
4960
  function syncCyclingControls() {
4929
- const shown = svelteComponentSession?.sessionId === currentSessionId && svelteComponentSession.mountedVariant > 0
4930
- ? svelteComponentSession.mountedVariant
4931
- : visibleVariant;
4961
+ const shown = cyclingShownVariant();
4932
4962
  const counter = uiGetById(PREFIX + '-variant-counter');
4933
- if (counter && arrivedVariants > 0) counter.textContent = shown + '/' + arrivedVariants;
4963
+ if (counter) counter.textContent = cyclingCounterText();
4934
4964
  const prev = uiGetById(PREFIX + '-variant-prev');
4935
4965
  const next = uiGetById(PREFIX + '-variant-next');
4936
4966
  if (prev) prev.style.opacity = shown <= 1 ? '0.3' : '1';
@@ -5160,15 +5190,74 @@
5160
5190
  }
5161
5191
  }
5162
5192
 
5163
- function resolveComponentModuleUrl(manifest, modulePath) {
5164
- return new URL(String(modulePath || ''), location.origin).href;
5193
+ // The dev server may serve under a non-root base (vite `base`) or a root
5194
+ // that differs from where the helper wrote the preview tree. Root-relative
5195
+ // URLs are tried against the detected base first; the /@fs/ absolute form
5196
+ // is the fallback that works regardless of base and root, as long as the
5197
+ // path is inside the server's fs.allow.
5198
+ let detectedDevBase = null;
5199
+ function detectDevServerBase() {
5200
+ if (detectedDevBase !== null) return detectedDevBase;
5201
+ detectedDevBase = '/';
5202
+ const scripts = document.querySelectorAll('script[type="module"][src]');
5203
+ for (const script of scripts) {
5204
+ const src = script.getAttribute('src') || '';
5205
+ const idx = src.indexOf('/@vite/client');
5206
+ if (idx > 0) { detectedDevBase = src.slice(0, idx) + '/'; break; }
5207
+ if (idx === 0) { detectedDevBase = '/'; break; }
5208
+ }
5209
+ return detectedDevBase;
5210
+ }
5211
+
5212
+ function componentModuleCandidates(manifest, modulePath, absPath) {
5213
+ const base = detectDevServerBase();
5214
+ const rel = String(modulePath || '').replace(/^\/+/, '');
5215
+ const candidates = [new URL(base + rel, location.origin).href];
5216
+ if (base !== '/') candidates.push(new URL('/' + rel, location.origin).href);
5217
+ if (absPath) {
5218
+ const fsRel = '@fs/' + String(absPath).replace(/^\/+/, '');
5219
+ candidates.push(new URL(base + fsRel, location.origin).href);
5220
+ // Vite versions differ on whether @fs is served under base or at the
5221
+ // server root; with a non-root base, try both.
5222
+ if (base !== '/') candidates.push(new URL('/' + fsRel, location.origin).href);
5223
+ }
5224
+ return candidates;
5225
+ }
5226
+
5227
+ async function importFirstReachable(candidates, bust) {
5228
+ let lastErr = null;
5229
+ for (const candidate of candidates) {
5230
+ try {
5231
+ const url = bust ? candidate + (candidate.includes('?') ? '&' : '?') + 't=' + Date.now() : candidate;
5232
+ const mod = await import(/* @vite-ignore */ url);
5233
+ return { mod, url: candidate };
5234
+ } catch (err) {
5235
+ lastErr = err;
5236
+ }
5237
+ }
5238
+ throw Object.assign(lastErr || new Error('no module candidates'), {
5239
+ impeccableTriedUrls: candidates,
5240
+ });
5241
+ }
5242
+
5243
+ // Distinguishes "this variant is broken" from "the preview tree is not
5244
+ // reachable from the dev server at all" (wrong root, unserved directory).
5245
+ async function probePreviewTree(manifest) {
5246
+ if (!manifest?.probeModule) return { ok: true, skipped: true };
5247
+ const candidates = componentModuleCandidates(manifest, manifest.probeModule, manifest.probeModuleAbs);
5248
+ try {
5249
+ await importFirstReachable(candidates, false);
5250
+ return { ok: true };
5251
+ } catch (err) {
5252
+ return { ok: false, tried: err.impeccableTriedUrls || candidates };
5253
+ }
5165
5254
  }
5166
5255
 
5167
5256
  function loadSvelteRuntime(runtimeModule, manifest) {
5168
5257
  const modulePath = runtimeModule || '/src/lib/impeccable/__runtime.js';
5169
- const url = resolveComponentModuleUrl(manifest, modulePath);
5170
5258
  if (!svelteRuntimePromise) {
5171
- svelteRuntimePromise = import(/* @vite-ignore */ url);
5259
+ const candidates = componentModuleCandidates(manifest, modulePath, manifest?.runtimeModuleAbs);
5260
+ svelteRuntimePromise = importFirstReachable(candidates, false).then((r) => r.mod);
5172
5261
  }
5173
5262
  return svelteRuntimePromise;
5174
5263
  }
@@ -5178,7 +5267,7 @@
5178
5267
  // attribute with JSON braces can't survive the Svelte compiler. Returns a map of
5179
5268
  // { "1": [...params], "2": [...] }; an empty object when the agent declared none.
5180
5269
  async function loadSvelteComponentParams(manifest) {
5181
- const dir = String(manifest?.componentDir || '').replace(/^\/+/, '');
5270
+ const dir = String(manifest?.revisionDir || manifest?.componentDir || '').replace(/^\/+/, '');
5182
5271
  if (!dir) return {};
5183
5272
  const paramsPath = dir + '/params.json';
5184
5273
  const url = 'http://localhost:' + PORT + '/source?token=' + TOKEN + '&path=' + encodeURIComponent(paramsPath);
@@ -5197,42 +5286,14 @@
5197
5286
  }
5198
5287
  }
5199
5288
 
5200
- async function loadSvelteComponentVariantSource(manifest, variantNum) {
5201
- const dir = String(manifest?.componentDir || '').replace(/^\/+/, '');
5202
- if (!dir || !variantNum) return '';
5203
- const extension = manifest.componentExtension || 'svelte';
5204
- const sourcePath = dir + '/v' + variantNum + '.' + extension;
5205
- const url = 'http://localhost:' + PORT + '/source?token=' + TOKEN + '&path=' + encodeURIComponent(sourcePath);
5206
- try {
5207
- const res = await fetch(url);
5208
- if (!res.ok) return '';
5209
- return await res.text();
5210
- } catch {
5211
- return '';
5212
- }
5213
- }
5214
5289
 
5215
- function extractSvelteComponentStyle(source) {
5216
- const match = String(source || '').match(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/i);
5217
- return match ? match[1].trim() : '';
5218
- }
5219
5290
 
5220
- async function applySvelteComponentVariantStyle(variantNum) {
5221
- if (!svelteComponentSession || !variantNum) return;
5222
- const { manifest, sessionId } = svelteComponentSession;
5223
- const source = await loadSvelteComponentVariantSource(manifest, variantNum);
5224
- const css = extractSvelteComponentStyle(source);
5225
- removeSvelteComponentVariantStyle(svelteComponentSession);
5226
- if (!css) return;
5227
- const scopedCss = scopeCssToSveltePreview(css, sessionId);
5228
- if (!scopedCss) return;
5229
- const style = document.createElement('style');
5230
- style.dataset.impeccableSvelteComponentStyle = sessionId;
5231
- style.dataset.impeccableVariant = String(variantNum);
5232
- style.textContent = scopedCss;
5233
- document.head.appendChild(style);
5234
- svelteComponentSession.styleEl = style;
5235
- }
5291
+ // NOTE: the compiled component imported from the dev server already carries
5292
+ // its own scoped styles (vite-plugin-svelte injects them on module
5293
+ // evaluation). The old second injection re-fetched the raw source through
5294
+ // the helper and re-prefixed every selector un-hashed, so the same rules
5295
+ // applied twice with different specificity: preview and accepted cascades
5296
+ // disagreed. The single compiled copy is the truth now.
5236
5297
 
5237
5298
  function removeSvelteComponentVariantStyle(session = svelteComponentSession) {
5238
5299
  const style = session?.styleEl;
@@ -5353,6 +5414,9 @@
5353
5414
  const contract = manifest?.propContract || [];
5354
5415
  const values = {};
5355
5416
  if (!liveEl || contract.length === 0) return values;
5417
+ if (Number(manifest.contractVersion) === 2) {
5418
+ return buildSveltePropValuesV2(liveEl, manifest);
5419
+ }
5356
5420
  const sourceOriginal = parseOriginalMarkupElement(manifest.originalMarkup || '');
5357
5421
  if (!sourceOriginal) return values;
5358
5422
  const map = buildSvelteExpressionTextMap(sourceOriginal, liveEl);
@@ -5363,19 +5427,199 @@
5363
5427
  return values;
5364
5428
  }
5365
5429
 
5430
+ // Contract v2 hydration. The scaffolder preserved control flow, so props
5431
+ // come in kinds: `collection` hydrates from the live DOM's rendered items
5432
+ // (count by the item root selector, texts by slot order), `condition` from
5433
+ // whether the branch's probe element is currently rendered, `text` from the
5434
+ // v1 index-zip run over the markup WITH control-flow regions stripped and
5435
+ // the live tree WITH item elements excluded, so loop tokens can never shift
5436
+ // slots again. `handler` props keep their no-op defaults.
5437
+ function buildSveltePropValuesV2(liveEl, manifest) {
5438
+ const contract = manifest.propContract || [];
5439
+ const values = {};
5440
+ const itemElsByProp = new Map();
5441
+
5442
+ for (const entry of contract) {
5443
+ if (entry.kind === 'collection' && entry.item && entry.item.rootTag) {
5444
+ const selector = entry.item.rootTag + (entry.item.rootClasses || []).map((c) => '.' + cssEscapeIdent(c)).join('');
5445
+ let matches = [];
5446
+ try { matches = Array.from(liveEl.querySelectorAll(selector)); } catch { matches = []; }
5447
+ itemElsByProp.set(entry.prop, matches);
5448
+ const statics = new Set((entry.item.staticTexts || []).map((t) => String(t).trim()));
5449
+ const slots = entry.item.textSlots || [];
5450
+ values[entry.prop] = matches.map((itemEl, index) => {
5451
+ const texts = collectVisibleTexts(itemEl).filter((t) => !statics.has(t));
5452
+ const item = {};
5453
+ slots.forEach((slot, i) => { item[slot.key] = texts[i] != null ? texts[i] : ''; });
5454
+ // Attribute-bound values (href={link.href}) hydrate from the
5455
+ // rendered attribute on the live item element or a descendant.
5456
+ for (const slot of entry.item.attrSlots || []) {
5457
+ if (item[slot.key] != null || !slot.tag) continue;
5458
+ const sel = slot.tag + (slot.classes || []).map((c) => '.' + cssEscapeIdent(c)).join('');
5459
+ let el = null;
5460
+ try { el = itemEl.matches(sel) ? itemEl : itemEl.querySelector(sel); } catch { el = null; }
5461
+ const value = el ? el.getAttribute(slot.attr) : null;
5462
+ if (value != null) item[slot.key] = value;
5463
+ }
5464
+ // Keyed each: the key field is never rendered, so hydrate it with a
5465
+ // unique per-index value or Svelte throws each_key_duplicate.
5466
+ if (entry.item.keyField && item[entry.item.keyField] == null) {
5467
+ item[entry.item.keyField] = 'impeccable-live-' + index;
5468
+ }
5469
+ return item;
5470
+ });
5471
+ } else if (entry.kind === 'condition') {
5472
+ if (entry.probe && entry.probe.tag) {
5473
+ const selector = entry.probe.tag + (entry.probe.classes || []).map((c) => '.' + cssEscapeIdent(c)).join('');
5474
+ try { values[entry.prop] = !!liveEl.querySelector(selector); } catch { /* keep default */ }
5475
+ } else if (entry.probe && entry.probe.className) {
5476
+ // class:name directive: the live DOM answers directly, either on
5477
+ // the picked element itself or on a descendant carrying the class.
5478
+ try {
5479
+ values[entry.prop] = liveEl.classList.contains(entry.probe.className)
5480
+ || !!liveEl.querySelector('.' + cssEscapeIdent(entry.probe.className));
5481
+ } catch { /* keep default */ }
5482
+ }
5483
+ }
5484
+ }
5485
+
5486
+ // Text props outside control flow: strip block regions from the source
5487
+ // markup, exclude live text nodes inside any hydrated item element, then
5488
+ // run the existing zip.
5489
+ const textEntries = contract.filter((e) => e.kind === 'text' || e.kind === 'raw');
5490
+ if (textEntries.length > 0) {
5491
+ const strippedMarkup = stripSvelteBlockRegions(manifest.originalMarkup || '');
5492
+ const sourceOriginal = parseOriginalMarkupElement(strippedMarkup);
5493
+ if (sourceOriginal) {
5494
+ const excluded = [];
5495
+ for (const els of itemElsByProp.values()) excluded.push(...els);
5496
+ const filteredLive = cloneWithoutElements(liveEl, excluded);
5497
+ const map = buildSvelteExpressionTextMap(sourceOriginal, filteredLive);
5498
+ for (const entry of textEntries) {
5499
+ const token = '{' + entry.expr + '}';
5500
+ if (map.has(token)) values[entry.prop] = map.get(token) || '';
5501
+ }
5502
+ }
5503
+ }
5504
+ return values;
5505
+ }
5506
+
5507
+ function cssEscapeIdent(value) {
5508
+ try { return CSS.escape(value); } catch { return String(value).replace(/[^a-zA-Z0-9_-]/g, ''); }
5509
+ }
5510
+
5511
+ function collectVisibleTexts(rootEl) {
5512
+ const texts = [];
5513
+ const walker = document.createTreeWalker(rootEl, NodeFilter.SHOW_TEXT);
5514
+ let node;
5515
+ while ((node = walker.nextNode())) {
5516
+ const trimmed = String(node.textContent || '').trim();
5517
+ if (trimmed) texts.push(trimmed);
5518
+ }
5519
+ return texts;
5520
+ }
5521
+
5522
+ // Remove balanced {#each}...{/each} and {#if}...{/if} regions (including
5523
+ // the delimiters) from a markup string. Nesting-aware. {#key} blocks keep
5524
+ // their CONTENT (it always renders) but lose their delimiter tokens, which
5525
+ // would otherwise consume live text slots in the zip and shift every
5526
+ // following expression.
5527
+ function stripSvelteBlockRegions(markup) {
5528
+ let out = String(markup || '');
5529
+ out = stripSvelteKeyDelimiters(out);
5530
+ for (const kind of ['each', 'if']) {
5531
+ const open = '{#' + kind;
5532
+ const close = '{/' + kind + '}';
5533
+ for (;;) {
5534
+ const start = out.indexOf(open);
5535
+ if (start === -1) break;
5536
+ let depth = 0;
5537
+ let i = start;
5538
+ let end = -1;
5539
+ while (i < out.length) {
5540
+ if (out.startsWith(open, i)) { depth++; i += open.length; continue; }
5541
+ if (out.startsWith(close, i)) {
5542
+ depth--;
5543
+ i += close.length;
5544
+ if (depth === 0) { end = i; break; }
5545
+ continue;
5546
+ }
5547
+ i++;
5548
+ }
5549
+ if (end === -1) break;
5550
+ out = out.slice(0, start) + out.slice(end);
5551
+ }
5552
+ }
5553
+ return out;
5554
+ }
5555
+
5556
+ function stripSvelteKeyDelimiters(markup) {
5557
+ let out = String(markup || '');
5558
+ for (;;) {
5559
+ const start = out.indexOf('{#key');
5560
+ if (start === -1) break;
5561
+ // The opening tag runs to its matching close brace (expressions inside
5562
+ // may nest braces).
5563
+ let depth = 0;
5564
+ let i = start;
5565
+ let openEnd = -1;
5566
+ while (i < out.length) {
5567
+ if (out[i] === '{') depth++;
5568
+ else if (out[i] === '}') {
5569
+ depth--;
5570
+ if (depth === 0) { openEnd = i + 1; break; }
5571
+ }
5572
+ i++;
5573
+ }
5574
+ if (openEnd === -1) break;
5575
+ out = out.slice(0, start) + out.slice(openEnd);
5576
+ }
5577
+ return out.split('{/key}').join('');
5578
+ }
5579
+
5580
+ function cloneWithoutElements(rootEl, excludedEls) {
5581
+ if (!excludedEls || excludedEls.length === 0) return rootEl;
5582
+ const excludedSet = new Set(excludedEls);
5583
+ // Mark originals, clone, then strip marked clones: identity does not
5584
+ // survive cloneNode, attributes do.
5585
+ const MARK = 'data-impeccable-hydration-excluded';
5586
+ for (const el of excludedSet) { try { el.setAttribute(MARK, '1'); } catch { /* detached */ } }
5587
+ let clone;
5588
+ try {
5589
+ clone = rootEl.cloneNode(true);
5590
+ clone.querySelectorAll('[' + MARK + ']').forEach((el) => el.remove());
5591
+ } finally {
5592
+ for (const el of excludedSet) { try { el.removeAttribute(MARK); } catch { /* detached */ } }
5593
+ }
5594
+ return clone || rootEl;
5595
+ }
5596
+
5366
5597
  async function mountSvelteComponentVariant(variantNum) {
5367
5598
  if (!svelteComponentSession || !variantNum) return false;
5368
5599
  const { manifest, mountTargetEl, sessionId } = svelteComponentSession;
5600
+ // Resolved before the first await so the failure report can name the module
5601
+ // the browser could not reach, whichever step threw.
5602
+ const extension = manifest.componentExtension || 'svelte';
5603
+ // Prefer the server-stamped revision dir: its path changes on every
5604
+ // publish, which is what defeats stale transform caches for files the
5605
+ // dev server does not watch.
5606
+ const dirRel = manifest.revisionDir || manifest.componentDir || '';
5607
+ const dirAbs = manifest.revisionDirAbs || manifest.componentDirAbs || null;
5608
+ const moduleBase = manifest.componentModuleBase
5609
+ || ('/' + String(dirRel).replace(/^\/+/, ''));
5610
+ const modulePath = String(moduleBase).replace(/\/+$/, '') + '/v' + variantNum + '.' + extension;
5611
+ const moduleAbs = dirAbs
5612
+ ? String(dirAbs).replace(/\/+$/, '') + '/v' + variantNum + '.' + extension
5613
+ : null;
5614
+ const candidates = componentModuleCandidates(manifest, modulePath, moduleAbs);
5615
+ let moduleUrl = candidates[0];
5369
5616
  try {
5370
5617
  const previousAnchor = getMountedSvelteComponentAnchor(svelteComponentSession) || selectedElement;
5371
5618
  svelteComponentSession.swapAnchor = makeFrozenAnchor(previousAnchor) || svelteComponentSession.swapAnchor || null;
5372
5619
  const runtime = await loadSvelteRuntime(manifest.runtimeModule, manifest);
5373
- const extension = manifest.componentExtension || 'svelte';
5374
- const moduleBase = manifest.componentModuleBase
5375
- || ('/' + String(manifest.componentDir || '').replace(/^\/+/, ''));
5376
- const modulePath = String(moduleBase).replace(/\/+$/, '') + '/v' + variantNum + '.' + extension;
5377
- const moduleUrl = resolveComponentModuleUrl(manifest, modulePath) + '?t=' + Date.now();
5378
- const mod = await import(/* @vite-ignore */ moduleUrl);
5620
+ const imported = await importFirstReachable(candidates, true);
5621
+ moduleUrl = imported.url;
5622
+ const mod = imported.mod;
5379
5623
  const Component = mod.default;
5380
5624
  if (svelteComponentSession.mountedInstance && runtime.unmount) {
5381
5625
  await runtime.unmount(svelteComponentSession.mountedInstance);
@@ -5388,7 +5632,7 @@
5388
5632
  });
5389
5633
  svelteComponentSession.mountedVariant = variantNum;
5390
5634
  svelteComponentSession.runtime = runtime;
5391
- await applySvelteComponentVariantStyle(variantNum);
5635
+ removeSvelteComponentVariantStyle(svelteComponentSession);
5392
5636
  if (state === 'CYCLING') syncCyclingControls();
5393
5637
  const nextAnchor = getMountedSvelteComponentAnchor(svelteComponentSession);
5394
5638
  if (nextAnchor) {
@@ -5409,16 +5653,44 @@
5409
5653
  selectedElement = settledAnchor;
5410
5654
  });
5411
5655
  }
5656
+ // Render truth, not publish truth: this is the only point in the whole
5657
+ // pipeline that proves the user can see variant N.
5658
+ reportVariantMounted(sessionId, variantNum, moduleUrl);
5659
+ if (mountErrorState?.sessionId === sessionId && mountErrorState.variant === variantNum) {
5660
+ clearMountErrorCard();
5661
+ }
5412
5662
  return true;
5413
5663
  } catch (err) {
5414
5664
  if (svelteComponentSession?.sessionId === sessionId) {
5415
5665
  svelteComponentSession.swapAnchor = null;
5416
5666
  }
5417
5667
  console.error('[impeccable] Failed to mount component variant ' + variantNum + ' for ' + sessionId + ':', err);
5668
+ reportVariantMountFailed(sessionId, variantNum, moduleUrl, err);
5669
+ // Every mount failure gets the card, so the variant-switch path (which
5670
+ // used to revert with no feedback whatsoever) says what broke too.
5671
+ showMountErrorCard(sessionId, {
5672
+ variant: variantNum,
5673
+ url: moduleUrl,
5674
+ message: await describeMountFailure(manifest, err),
5675
+ });
5418
5676
  return false;
5419
5677
  }
5420
5678
  }
5421
5679
 
5680
+ // Distinguishes a broken variant from an unreachable preview tree; the
5681
+ // recovery differs (fix the component vs fix the root/dev-server pair).
5682
+ async function describeMountFailure(manifest, err) {
5683
+ try {
5684
+ const probe = await probePreviewTree(manifest);
5685
+ if (probe.ok === false) {
5686
+ return 'The preview tree is not reachable from the dev server (probe failed on '
5687
+ + (probe.tried || []).join(', ')
5688
+ + '). The resolved app root and the dev server root likely disagree; restart live from the app the dev server serves.';
5689
+ }
5690
+ } catch { /* probe is best-effort */ }
5691
+ return 'The compiled component could not be imported or mounted. ' + (err?.message || 'Unknown error');
5692
+ }
5693
+
5422
5694
  function teardownSvelteComponentSession(restoreOriginal) {
5423
5695
  if (!svelteComponentSession) return;
5424
5696
  const { wrapperEl, detachedOriginal, runtime, mountedInstance } = svelteComponentSession;
@@ -5471,12 +5743,29 @@
5471
5743
  }
5472
5744
 
5473
5745
  async function injectSvelteComponentsFromManifest(manifestPath, sessionId) {
5746
+ // Every (re)injection is a fresh attempt: reset the failure dedupe so a
5747
+ // republish that is STILL broken at the same URL reports again instead of
5748
+ // being swallowed while the agent believes the repair landed.
5749
+ lastReportedMountFailure = null;
5474
5750
  const url = 'http://localhost:' + PORT + '/source?token=' + TOKEN + '&path=' + encodeURIComponent(manifestPath);
5475
5751
  try {
5476
5752
  const res = await fetch(url);
5477
5753
  if (!res.ok) throw new Error(String(res.status));
5478
5754
  const manifest = JSON.parse(await res.text());
5479
- if (manifest.id !== sessionId) return;
5755
+ if (manifest.id !== sessionId) {
5756
+ // A manifest at the expected path belonging to a different session is
5757
+ // an agent-side publish error. Left as a bare return it stranded the
5758
+ // bar in GENERATING with no explanation and no event.
5759
+ const mismatch = 'Manifest at ' + manifestPath + ' belongs to session ' + (manifest.id || 'unknown') + ', not ' + sessionId + '.';
5760
+ reportVariantMountFailed(sessionId, visibleVariant || 1, manifestPath, mismatch);
5761
+ showMountErrorCard(sessionId, {
5762
+ variant: visibleVariant || 0,
5763
+ url: manifestPath,
5764
+ message: 'The variant manifest is for a different session. Ask the agent to republish.',
5765
+ previewFile: manifestPath,
5766
+ });
5767
+ return;
5768
+ }
5480
5769
 
5481
5770
  const paramsByVariant = await loadSvelteComponentParams(manifest);
5482
5771
  const availableVariants = Number(manifest.arrivedVariants) || Number(manifest.count) || 1;
@@ -5500,7 +5789,14 @@
5500
5789
  arrivedVariants = availableVariants;
5501
5790
  expectedVariants = Number(manifest.count) || expectedVariants || arrivedVariants;
5502
5791
  visibleVariant = visibleVariant > 0 && visibleVariant <= arrivedVariants ? visibleVariant : 1;
5503
- await mountSvelteComponentVariant(visibleVariant || 1);
5792
+ const remounted = await mountSvelteComponentVariant(visibleVariant || 1);
5793
+ if (!remounted) {
5794
+ // The mount already reported the failure and raised the card.
5795
+ // Advancing to CYCLING here would show a bar claiming variants are
5796
+ // ready over a page where nothing rendered.
5797
+ saveSession();
5798
+ return;
5799
+ }
5504
5800
  setLiveState('CYCLING');
5505
5801
  showOrUpdateCyclingBar();
5506
5802
  saveSession();
@@ -5575,9 +5871,11 @@
5575
5871
  const mounted = await mountSvelteComponentVariant(visibleVariant);
5576
5872
  if (!mounted) {
5577
5873
  // The compiled component threw (e.g. a Svelte compile error in the
5578
- // variant file). Don't strand the bar in an empty CYCLING state; restore
5579
- // the original element and reset to PICKING so the user can retry.
5580
- abortSvelteComponentInjection(sessionId, 'A variant failed to compile. Fix the component and re-run.');
5874
+ // variant file). mountSvelteComponentVariant already reported the
5875
+ // failure and raised the card; tear the half-built preview down but
5876
+ // keep the session so Retry and a republish still have something to
5877
+ // act on.
5878
+ abortSvelteComponentInjection(sessionId);
5581
5879
  return;
5582
5880
  }
5583
5881
 
@@ -5594,7 +5892,15 @@
5594
5892
  console.log('[impeccable] Mounted ' + arrivedVariants + ' ' + manifest.framework + ' component variants.');
5595
5893
  } catch (err) {
5596
5894
  console.error('[impeccable] Failed to mount component-preview variants:', err);
5597
- abortSvelteComponentInjection(sessionId, 'Could not load variants. Fix the error and re-run.');
5895
+ // Report the manifest PATH, never the fetch URL: that URL carries the
5896
+ // live helper token and this string is journaled.
5897
+ reportVariantMountFailed(sessionId, visibleVariant || 1, manifestPath, err);
5898
+ abortSvelteComponentInjection(sessionId, {
5899
+ variant: visibleVariant || 0,
5900
+ url: manifestPath,
5901
+ message: 'Could not read the variant manifest. ' + (err?.message || 'Unknown error'),
5902
+ previewFile: manifestPath,
5903
+ });
5598
5904
  }
5599
5905
  }
5600
5906
 
@@ -5615,10 +5921,187 @@
5615
5921
  pendingSvelteComponentRetryObserver.observe(document.body, { childList: true, subtree: true });
5616
5922
  }
5617
5923
 
5618
- // Reset cleanly when a Svelte component session can't mount: tear the wrapper
5619
- // down (restoring the original element), clear persisted session state, and
5620
- // return the bar to PICKING. Avoids the stuck 0/0 CYCLING bar.
5621
- function abortSvelteComponentInjection(sessionId, message) {
5924
+ //
5925
+ // Mount acknowledgements
5926
+ //
5927
+ // The agent's `done` says it published files. Only the browser knows whether
5928
+ // the import resolved and the component reached the DOM. These two events
5929
+ // carry that answer back, so the journal, `live-status`, and `live-resume`
5930
+ // can tell "the user is comparing variants" from "nothing ever rendered".
5931
+
5932
+ // Mirror of the caps in live/event-validation.mjs. Trimming here keeps a
5933
+ // stack-trace-sized error from being rejected outright and lost.
5934
+ const MOUNT_URL_MAX = 2000;
5935
+ const MOUNT_ERROR_MAX = 1000;
5936
+
5937
+ function reportVariantMounted(sessionId, variantNum, moduleUrl) {
5938
+ const variant = Math.floor(Number(variantNum) || 0);
5939
+ if (!sessionId || variant < 1) return;
5940
+ sendEvent({
5941
+ type: 'variant_mounted',
5942
+ id: sessionId,
5943
+ variant,
5944
+ url: moduleUrl ? String(moduleUrl).slice(0, MOUNT_URL_MAX) : undefined,
5945
+ });
5946
+ }
5947
+
5948
+ function reportVariantMountFailed(sessionId, variantNum, moduleUrl, error) {
5949
+ if (!sessionId) return;
5950
+ const parsed = Math.floor(Number(variantNum) || 0);
5951
+ const variant = parsed >= 1 ? parsed : 1;
5952
+ const url = String(moduleUrl || 'unknown').slice(0, MOUNT_URL_MAX);
5953
+ const message = String(error?.message || error || 'Unknown mount error').slice(0, MOUNT_ERROR_MAX);
5954
+ // Progressive delivery and the Retry button both re-enter the same failure.
5955
+ // Report each distinct one once so the agent's poll queue and the journal
5956
+ // stay readable; a genuinely new failure (different variant, URL, or
5957
+ // message) still gets through.
5958
+ const key = sessionId + '|' + variant + '|' + url + '|' + message;
5959
+ if (lastReportedMountFailure === key) return;
5960
+ lastReportedMountFailure = key;
5961
+ sendEvent({ type: 'variant_mount_failed', id: sessionId, variant, url, error: message });
5962
+ }
5963
+
5964
+ function truncateMiddle(value, max) {
5965
+ const text = String(value || '');
5966
+ if (text.length <= max) return text;
5967
+ const head = Math.ceil((max - 1) / 2);
5968
+ const tail = max - 1 - head;
5969
+ return text.slice(0, head) + '…' + text.slice(text.length - tail);
5970
+ }
5971
+
5972
+ /**
5973
+ * Persistent failure surface. Replaces the old 5s toast: a toast that
5974
+ * disappears while the session is unusable is indistinguishable from no
5975
+ * feedback at all, and the wipe that came with it deleted the only handle on
5976
+ * a session the server still considered live.
5977
+ */
5978
+ function showMountErrorCard(sessionId, details) {
5979
+ mountErrorState = {
5980
+ sessionId: sessionId || currentSessionId || null,
5981
+ variant: Math.floor(Number(details?.variant) || 0),
5982
+ url: details?.url ? String(details.url) : '',
5983
+ message: details?.message || 'A variant failed to load.',
5984
+ previewFile: details?.previewFile || currentPreviewFile || null,
5985
+ };
5986
+ renderMountErrorCard();
5987
+ }
5988
+
5989
+ function clearMountErrorCard() {
5990
+ mountErrorState = null;
5991
+ if (mountErrorEl) {
5992
+ mountErrorEl.remove();
5993
+ mountErrorEl = null;
5994
+ }
5995
+ }
5996
+
5997
+ function mountErrorCardBottomOffset() {
5998
+ const barRect = globalBarEl?.getBoundingClientRect();
5999
+ return barRect && barRect.height > 0
6000
+ ? Math.max(16, window.innerHeight - barRect.top + 12)
6001
+ : 16;
6002
+ }
6003
+
6004
+ function renderMountErrorCard() {
6005
+ if (!mountErrorState) return;
6006
+ if (mountErrorEl) mountErrorEl.remove();
6007
+ const P = BP || barPaletteForTheme(detectPageTheme());
6008
+ const card = el('div', {
6009
+ position: 'fixed', bottom: mountErrorCardBottomOffset() + 'px', left: '50%',
6010
+ transform: 'translateX(-50%)',
6011
+ display: 'flex', flexDirection: 'column', gap: '6px',
6012
+ background: P.surface, color: P.text,
6013
+ border: '1px solid oklch(65% 0.18 30 / 0.55)',
6014
+ borderRadius: '8px', padding: '10px 12px',
6015
+ fontFamily: FONT, fontSize: '12px',
6016
+ boxShadow: P.shadow, zIndex: Z.toast,
6017
+ maxWidth: 'min(520px, calc(100vw - 32px))',
6018
+ pointerEvents: 'auto', textAlign: 'left',
6019
+ });
6020
+ card.id = PREFIX + '-mount-error';
6021
+
6022
+ const head = el('div', { display: 'flex', alignItems: 'center', gap: '8px' });
6023
+ const glyph = el('span', { fontSize: '13px', lineHeight: '1', color: 'oklch(62% 0.19 30)', flexShrink: '0' });
6024
+ glyph.textContent = '⚠';
6025
+ head.appendChild(glyph);
6026
+ const title = el('span', { fontWeight: '600', flex: '1' });
6027
+ title.textContent = mountErrorState.variant > 0
6028
+ ? 'Variant ' + mountErrorState.variant + ' failed to load'
6029
+ : 'Variants failed to load';
6030
+ head.appendChild(title);
6031
+ const dismiss = el('button', {
6032
+ border: 'none', background: 'transparent', color: P.textDim,
6033
+ cursor: 'pointer', fontFamily: FONT, fontSize: '14px', lineHeight: '1',
6034
+ padding: '0 2px', flexShrink: '0',
6035
+ });
6036
+ dismiss.textContent = '×';
6037
+ dismiss.setAttribute('aria-label', 'Dismiss');
6038
+ dismiss.addEventListener('click', (e) => {
6039
+ e.stopPropagation();
6040
+ clearMountErrorCard();
6041
+ // The card was the only recovery affordance while the bar is hidden;
6042
+ // dismissing it must hand the user back a usable surface. PICKING
6043
+ // reactivates the global mark and the picker. The saved session and
6044
+ // server truth survive, so a later republish (SSE `done`) still
6045
+ // resurrects the comparison through the normal handlers.
6046
+ if (state === 'GENERATING') setLiveState('PICKING');
6047
+ });
6048
+ head.appendChild(dismiss);
6049
+ card.appendChild(head);
6050
+
6051
+ const body = el('div', { color: P.textDim, lineHeight: '1.4' });
6052
+ body.textContent = mountErrorState.message;
6053
+ card.appendChild(body);
6054
+
6055
+ if (mountErrorState.url) {
6056
+ const urlLine = el('div', {
6057
+ fontFamily: MONO, fontSize: '11px', color: P.textDim,
6058
+ wordBreak: 'break-all', opacity: '0.85',
6059
+ });
6060
+ urlLine.textContent = truncateMiddle(mountErrorState.url, 72);
6061
+ urlLine.title = mountErrorState.url;
6062
+ card.appendChild(urlLine);
6063
+ }
6064
+
6065
+ const actions = el('div', { display: 'flex', gap: '8px', marginTop: '2px' });
6066
+ const retry = el('button', {
6067
+ border: '1px solid ' + P.hairline, background: 'transparent',
6068
+ color: P.text, fontFamily: FONT, fontSize: '12px', fontWeight: '500',
6069
+ borderRadius: '5px', padding: '4px 10px', cursor: 'pointer',
6070
+ });
6071
+ retry.textContent = 'Retry';
6072
+ retry.dataset.impeccableMountRetry = 'true';
6073
+ retry.addEventListener('click', (e) => { e.stopPropagation(); retryMountErrorCard(); });
6074
+ actions.appendChild(retry);
6075
+ card.appendChild(actions);
6076
+
6077
+ mountErrorEl = card;
6078
+ uiAppend(card);
6079
+ defangOutsideHandlers(card);
6080
+ }
6081
+
6082
+ function retryMountErrorCard() {
6083
+ const info = mountErrorState;
6084
+ if (!info) return;
6085
+ const sessionId = info.sessionId || currentSessionId;
6086
+ const manifestPath = info.previewFile || currentPreviewFile;
6087
+ clearMountErrorCard();
6088
+ if (!sessionId || !manifestPath) {
6089
+ showToast('No variant manifest to retry. Ask the agent to republish.', 5000);
6090
+ return;
6091
+ }
6092
+ // A retry must be able to report the same failure again, otherwise a second
6093
+ // attempt against an unchanged broken module would look silent.
6094
+ lastReportedMountFailure = null;
6095
+ if (state !== 'CYCLING') setLiveState('GENERATING');
6096
+ injectSvelteComponentsFromManifest(manifestPath, sessionId);
6097
+ }
6098
+
6099
+ // Tear down a component preview that could not mount, WITHOUT touching
6100
+ // session identity. The old version cleared localStorage, nulled
6101
+ // currentSessionId, and reset to PICKING, which orphaned a session the server
6102
+ // still had in its journal and made every recovery path unreachable. The DOM
6103
+ // teardown and observer cleanup are still right; the state wipe never was.
6104
+ function abortSvelteComponentInjection(sessionId, details) {
5622
6105
  try {
5623
6106
  if (svelteComponentSession?.sessionId === sessionId) {
5624
6107
  teardownSvelteComponentSession(true);
@@ -5630,11 +6113,46 @@
5630
6113
  console.warn('[impeccable] Svelte component abort cleanup failed:', err);
5631
6114
  }
5632
6115
  hideShaderOverlay();
6116
+ if (pendingSvelteComponentRetryObserver) { pendingSvelteComponentRetryObserver.disconnect(); pendingSvelteComponentRetryObserver = null; }
6117
+ if (pendingVariantAnchorRetryObserver) { pendingVariantAnchorRetryObserver.disconnect(); pendingVariantAnchorRetryObserver = null; }
6118
+ // The generate submit armed a scroll lock and a variant observer; a page
6119
+ // the user cannot scroll, watched by a stale observer, is exactly the
6120
+ // wrong place to show a card asking them to act.
6121
+ stopScrollLock();
6122
+ if (variantObserver) { variantObserver.disconnect(); variantObserver = null; }
6123
+ removeVariantStateStylesheet();
6124
+ hideBar(true);
6125
+ // currentSessionId, the saved session, and the file metadata all survive on
6126
+ // purpose: Retry, a republish from the agent, and a page reload all need
6127
+ // them. saveSession keeps the localStorage cache in step with the server.
6128
+ saveSession();
6129
+ if (details) showMountErrorCard(sessionId, details);
6130
+ else if (!mountErrorState) {
6131
+ showMountErrorCard(sessionId, { message: 'Variants could not be mounted. Retry, or ask the agent to republish.' });
6132
+ }
6133
+ }
6134
+
6135
+ // Hard reset for the one case that is not a mount failure: a cycling state
6136
+ // with nothing to cycle. There is no variant to retry and no URL to report,
6137
+ // so the session really is over.
6138
+ function resetSvelteComponentSession(sessionId, message) {
6139
+ try {
6140
+ if (svelteComponentSession?.sessionId === sessionId) {
6141
+ teardownSvelteComponentSession(true);
6142
+ } else {
6143
+ const orphan = document.querySelector('[data-impeccable-variants="' + sessionId + '"]');
6144
+ if (orphan) orphan.remove();
6145
+ }
6146
+ } catch (err) {
6147
+ console.warn('[impeccable] Svelte component reset cleanup failed:', err);
6148
+ }
6149
+ hideShaderOverlay();
5633
6150
  if (variantObserver) { variantObserver.disconnect(); variantObserver = null; }
5634
6151
  if (pendingSvelteComponentRetryObserver) { pendingSvelteComponentRetryObserver.disconnect(); pendingSvelteComponentRetryObserver = null; }
5635
6152
  if (pendingVariantAnchorRetryObserver) { pendingVariantAnchorRetryObserver.disconnect(); pendingVariantAnchorRetryObserver = null; }
5636
6153
  stopScrollLock();
5637
6154
  removeVariantStateStylesheet();
6155
+ clearMountErrorCard();
5638
6156
  clearSession();
5639
6157
  clearHandled();
5640
6158
  resetSessionFileMeta();
@@ -5656,6 +6174,22 @@
5656
6174
  const COMPLETED_SOURCE_FALLBACK_RETRIES = 3;
5657
6175
  const COMPLETED_SOURCE_FALLBACK_RETRY_MS = 1200;
5658
6176
 
6177
+ /**
6178
+ * Terminal recovery for a session whose source-side scaffolding no longer
6179
+ * exists. The discard event is best-effort: with no agent polling it parks
6180
+ * the durable session in discard_requested, which no resume path adopts;
6181
+ * with an agent attached it triggers the normal discard finalization.
6182
+ */
6183
+ function discardOrphanedSession(reason) {
6184
+ const sessionId = currentSessionId;
6185
+ if (!sessionId) return;
6186
+ console.warn('[impeccable] Discarding orphaned session ' + sessionId + ': ' + reason);
6187
+ sendEvent({ type: 'discard', id: sessionId, orphaned: true }).catch(() => {});
6188
+ markSessionHandled();
6189
+ cleanup({ instantChrome: true });
6190
+ showToast('The previous live session no longer matches the source file, so it was discarded. Pick an element to start fresh.', 6000);
6191
+ }
6192
+
5659
6193
  /**
5660
6194
  * No-HMR fallback: fetch the raw source file from the live server,
5661
6195
  * parse it, extract the variant wrapper, and inject it into the live DOM.
@@ -5692,6 +6226,25 @@
5692
6226
  srcWrapper = doc.querySelector('[data-impeccable-variants="' + sessionId + '"]');
5693
6227
  if (!srcWrapper) {
5694
6228
  console.warn('[impeccable] Variant wrapper not found in source file.');
6229
+ // A resumed cycling session whose wrapper is gone from source is an
6230
+ // ORPHAN: the file was edited or regenerated out from under it, so
6231
+ // no reload, HMR push, or server restart can ever complete it, and
6232
+ // the frozen picker it leaves behind used to need a manual
6233
+ // live-complete --discarded. Retry a few reads first (an agent
6234
+ // rewrite or HMR patch may be mid-flight), then self-discard and
6235
+ // hand the surface back to the picker.
6236
+ if (opts.orphanDiscard && sessionId === currentSessionId) {
6237
+ const attempt = opts._orphanAttempt || 0;
6238
+ if (attempt < COMPLETED_SOURCE_FALLBACK_RETRIES) {
6239
+ setTimeout(() => {
6240
+ if (sessionId !== currentSessionId) return;
6241
+ if (state !== 'GENERATING' && state !== 'CYCLING') return;
6242
+ injectVariantsFromSource(filePath, sessionId, { ...opts, _orphanAttempt: attempt + 1 });
6243
+ }, COMPLETED_SOURCE_FALLBACK_RETRY_MS);
6244
+ } else {
6245
+ discardOrphanedSession('variant wrapper missing from source');
6246
+ }
6247
+ }
5695
6248
  return;
5696
6249
  }
5697
6250
 
@@ -6394,12 +6947,8 @@
6394
6947
  // checkpoint may carry an earlier phase for internal bookkeeping,
6395
6948
  // but the bar must not move backward.
6396
6949
  if (shouldAdvancePhase(generationPhase, msg.phase)) generationPhase = msg.phase;
6397
- if (msg.phase === 'variant_parameters_generating' || msg.phase === 'variant_parameters_validating') {
6398
- parameterGenerationState = 'loading';
6399
- }
6400
- if (msg.phase === 'parameters_ready' && parameterGenerationState !== 'ready') {
6401
- parameterGenerationState = 'loading';
6402
- }
6950
+ // The deferred parameter pass reports through `variant_progress`
6951
+ // with publicationKind 'params', not through agent_phase.
6403
6952
  updateBarContent(state === 'CYCLING' ? 'cycling' : 'generating');
6404
6953
  saveSession();
6405
6954
  }
@@ -6588,6 +7137,13 @@
6588
7137
  if (currentSessionId) saveSession();
6589
7138
  }
6590
7139
 
7140
+ // Progress events must never overtake the event that CREATES their session:
7141
+ // the Go-time checkpoint and the generate POST are concurrent fetches, and
7142
+ // when the checkpoint lands first the server rightly refuses it as
7143
+ // unknown_session — which must mean "foreign leftovers", not "you raced
7144
+ // your own Go click". The gate serializes creation before progress.
7145
+ let sessionCreationGate = Promise.resolve();
7146
+
6591
7147
  function sendEvent(msg, opts) {
6592
7148
  msg.token = TOKEN;
6593
7149
  function handleFailure(err) {
@@ -6598,15 +7154,45 @@
6598
7154
  console.debug('[impeccable] Dropped optional live event:', err);
6599
7155
  return null;
6600
7156
  }
6601
- return fetch('http://localhost:' + PORT + '/events', {
7157
+ // Token in the query string as well as the body: the URL token is what
7158
+ // authorizes the CORS preflight when the page runs on a non-loopback
7159
+ // dev host (ddev, Valet), since the preflight carries no request body.
7160
+ const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
6602
7161
  method: 'POST',
6603
7162
  headers: { 'Content-Type': 'application/json' },
6604
7163
  body: JSON.stringify(msg),
6605
7164
  }).then(async res => {
6606
7165
  if (res.ok) return res;
6607
7166
  const body = await res.json().catch(() => ({}));
7167
+ // The server refused to journal progress for a session it has never
7168
+ // seen: this browser is carrying state from another project or a
7169
+ // wiped store (two apps sharing a localhost port). Continuing to
7170
+ // report it would freeze the picker behind a session that can never
7171
+ // complete, so drop the local state and hand the surface back.
7172
+ if (body.error === 'unknown_session' && msg.type === 'checkpoint'
7173
+ && msg.id && msg.id === currentSessionId) {
7174
+ abandonForeignSession(msg.id);
7175
+ return null;
7176
+ }
6608
7177
  return handleFailure(new Error(body.error || ('HTTP ' + res.status + ' ' + res.statusText)));
6609
7178
  }).catch(handleFailure);
7179
+
7180
+ if (msg.type === 'generate' || msg.type === 'steer') {
7181
+ const creation = doSend();
7182
+ sessionCreationGate = creation.then(() => {}, () => {});
7183
+ return creation;
7184
+ }
7185
+ return sessionCreationGate.then(doSend);
7186
+ }
7187
+
7188
+ let abandonedForeignSessionId = null;
7189
+ function abandonForeignSession(sessionId) {
7190
+ if (abandonedForeignSessionId === sessionId || sessionId !== currentSessionId) return;
7191
+ abandonedForeignSessionId = sessionId;
7192
+ console.warn('[impeccable] The live server has no record of session ' + sessionId + '; clearing stale local state.');
7193
+ markSessionHandled();
7194
+ cleanup({ instantChrome: true });
7195
+ showToast('A saved live session belonged to a different project, so it was cleared. Pick an element to start fresh.', 6000);
6610
7196
  }
6611
7197
 
6612
7198
  function checkpointPayload(reason) {
@@ -6989,6 +7575,10 @@
6989
7575
  disableInlineEdit();
6990
7576
  stripManualEditRuntimeState(selectedElement);
6991
7577
 
7578
+ // A new cycle publishes new modules, so the previous cycle's mount failure
7579
+ // is about files that no longer matter.
7580
+ clearMountErrorCard();
7581
+ lastReportedMountFailure = null;
6992
7582
  pendingAcceptedSession = null;
6993
7583
  // A new session supersedes any accept still awaiting its result; a late
6994
7584
  // failure toast for the previous session would only mislead here.
@@ -7071,6 +7661,10 @@
7071
7661
  if (!canCreateInsert({ prompt, comments: snapshot.comments, strokes: snapshot.strokes })) return;
7072
7662
 
7073
7663
  stopVoice({ suppressSubmit: true });
7664
+ // A new cycle publishes new modules, so the previous cycle's mount failure
7665
+ // is about files that no longer matter.
7666
+ clearMountErrorCard();
7667
+ lastReportedMountFailure = null;
7074
7668
  pendingAcceptedSession = null;
7075
7669
  // A new session supersedes any accept still awaiting its result; a late
7076
7670
  // failure toast for the previous session would only mislead here.
@@ -8193,8 +8787,62 @@ void main() {
8193
8787
  return Math.floor(num);
8194
8788
  }
8195
8789
 
8790
+ /**
8791
+ * A durable server session this page can adopt when the browser has no local
8792
+ * record of it. Requires an explicit pageUrl match: a summary with no page is
8793
+ * not evidence that it belongs to THIS page, and adopting it would hijack an
8794
+ * unrelated route.
8795
+ */
8796
+ // Phases in which the user is (or should be) comparing variants. Only these
8797
+ // are adoptable by a browser with no local record. Steer and manual-edit
8798
+ // sessions have no wrapper to restore, and accept/carbonize phases are
8799
+ // agent-side work: a reload mid-carbonize must not resurrect the bar over a
8800
+ // page whose comparison is already decided (a slow-CI reload hit exactly
8801
+ // that window and left the bar stranded after accept).
8802
+ const ADOPTABLE_SESSION_PHASES = new Set([
8803
+ 'generate_requested', 'variants_ready', 'generating', 'cycling',
8804
+ ]);
8805
+
8806
+ function findAdoptableServerSession(activeSessions) {
8807
+ if (!Array.isArray(activeSessions)) return null;
8808
+ return activeSessions.find((session) => (
8809
+ session?.id
8810
+ && !isTerminalSessionSummary(session)
8811
+ && !isSessionHandled(session.id)
8812
+ && session.pageUrl
8813
+ && pageMatchesCurrent(session.pageUrl)
8814
+ && (session.previewFile || session.sourceFile)
8815
+ && Number(session.expectedVariants) > 0
8816
+ && ADOPTABLE_SESSION_PHASES.has(String(session.phase || ''))
8817
+ )) || null;
8818
+ }
8819
+
8820
+ // Shape a server summary like a saved local session so one restore path
8821
+ // serves both. The server has no browser state machine, so an adopted session
8822
+ // always re-enters GENERATING and lets the injection settle the final state.
8823
+ function serverSessionAsSavedShape(session) {
8824
+ return {
8825
+ id: session.id,
8826
+ state: 'GENERATING',
8827
+ expected: Number(session.expectedVariants) || 0,
8828
+ arrived: Number(session.arrivedVariants) || 0,
8829
+ visible: Number(session.visibleVariant) || 0,
8830
+ sourceFile: session.sourceFile || undefined,
8831
+ previewFile: session.previewFile || undefined,
8832
+ previewMode: session.previewMode || undefined,
8833
+ pageUrl: session.pageUrl || undefined,
8834
+ paramValues: session.paramValues && typeof session.paramValues === 'object' ? session.paramValues : {},
8835
+ };
8836
+ }
8837
+
8196
8838
  function restoreSessionWithoutWrapper(reason, activeSessions) {
8197
- const saved = loadSession();
8839
+ const cached = loadSession();
8840
+ // localStorage is a cache, not a gate. A cleared tab, a second browser
8841
+ // profile, or a teardown that dropped local state all leave the durable
8842
+ // server session as the only record of work in progress; adopt it instead
8843
+ // of stranding a session the server still considers live.
8844
+ const adopted = cached?.id ? null : findAdoptableServerSession(activeSessions);
8845
+ const saved = cached?.id ? cached : (adopted ? serverSessionAsSavedShape(adopted) : null);
8198
8846
  if (!saved?.id || isSessionHandled(saved.id)) return false;
8199
8847
  const savedState = String(saved.state || '').toUpperCase();
8200
8848
  if (savedState !== 'GENERATING' && savedState !== 'CYCLING') return false;
@@ -8231,7 +8879,14 @@ void main() {
8231
8879
  ? currentPreviewFile
8232
8880
  : (currentSourceFile || currentPreviewFile);
8233
8881
  if (restoreFile) {
8234
- injectVariantsFromSource(restoreFile, currentSessionId);
8882
+ // A restored CYCLING session promises variants already written into
8883
+ // source; if they are not there (after retries), the session is an
8884
+ // orphan and must self-discard instead of freezing the picker (#439).
8885
+ // GENERATING restores make no such promise: deferred-wrapper flows
8886
+ // legitimately have no wrapper in source until the agent's write lands.
8887
+ injectVariantsFromSource(restoreFile, currentSessionId, {
8888
+ orphanDiscard: savedState === 'CYCLING' && !isFrameworkComponentPreviewMode(currentPreviewMode),
8889
+ });
8235
8890
  return true;
8236
8891
  }
8237
8892
 
@@ -8281,6 +8936,7 @@ void main() {
8281
8936
  // it here would overwrite the Go-time value every time state changes.
8282
8937
  sessionState.saveSession({
8283
8938
  id: currentSessionId,
8939
+ appRoot: APP_ROOT || undefined,
8284
8940
  state,
8285
8941
  action: selectedAction,
8286
8942
  count: selectedCount,
@@ -8302,7 +8958,17 @@ void main() {
8302
8958
  }
8303
8959
 
8304
8960
  function loadSession() {
8305
- return sessionState.loadSession();
8961
+ const saved = sessionState.loadSession();
8962
+ // localStorage is per-origin, and two projects routinely reuse the same
8963
+ // localhost port. A saved session stamped with another project's appRoot
8964
+ // is that project's leftover, never a session this server can complete;
8965
+ // resuming it freezes the picker behind an unfinishable banner.
8966
+ if (saved?.appRoot && APP_ROOT && saved.appRoot !== APP_ROOT) {
8967
+ console.warn('[impeccable] Ignoring saved live session from another project (' + saved.appRoot + ').');
8968
+ sessionState.clearSession();
8969
+ return null;
8970
+ }
8971
+ return saved;
8306
8972
  }
8307
8973
 
8308
8974
  function clearSession() {
@@ -8329,6 +8995,8 @@ void main() {
8329
8995
  const restoreOriginal = options?.restoreOriginal === true;
8330
8996
  const instantChrome = options?.instantChrome === true;
8331
8997
  const cleanupSessionId = currentSessionId;
8998
+ clearMountErrorCard();
8999
+ lastReportedMountFailure = null;
8332
9000
  if (svelteComponentSession?.sessionId === cleanupSessionId) {
8333
9001
  teardownSvelteComponentSession(true);
8334
9002
  } else if (cleanupSessionId) {
@@ -8671,6 +9339,8 @@ void main() {
8671
9339
  let pageChatInput = null;
8672
9340
  let pageChatHint = null;
8673
9341
  let pageChatVoiceBtn = null;
9342
+ let pageChatSendBtn = null;
9343
+ let pageChatQueueHintEl = null;
8674
9344
  let pageChatExpanded = false;
8675
9345
  let steerLocked = false;
8676
9346
  let steerRequestId = null;
@@ -8685,7 +9355,7 @@ void main() {
8685
9355
  /** @type {{ mode: 'steer'|'configure', input: HTMLInputElement, submit: () => void, beforeStart?: () => void } | null} */
8686
9356
  let voiceCtx = null;
8687
9357
  const PAGE_CHAT_COLLAPSED_W = '104px';
8688
- const PAGE_CHAT_PROCESSING_W = '76px';
9358
+ const PAGE_CHAT_QUEUED_W = '212px';
8689
9359
  const PAGE_CHAT_PLACEHOLDER_COLLAPSED = 'Steer…';
8690
9360
  const PAGE_CHAT_PLACEHOLDER_EXPANDED = 'Steer the page…';
8691
9361
  const STEER_AWAIT_TIMEOUT_MS = 120000;
@@ -8699,6 +9369,10 @@ void main() {
8699
9369
  // every normal generation, and telling the user to start a poll loop then is
8700
9370
  // wrong advice about a healthy session.
8701
9371
  const AGENT_BUSY_TIP = 'Agent is working - steering resumes when it finishes';
9372
+ // Same distinction, said where the steer request is waiting. A submitted
9373
+ // steer that lands while a generate holds the poll lease is not stuck, it is
9374
+ // second in line, and the pulsing dots alone read as "nothing is happening".
9375
+ const STEER_QUEUED_HINT = 'Queued behind current generation';
8702
9376
  const GLOBAL_BAR_SECTION_GAP = 8;
8703
9377
  const GLOBAL_BAR_INNER_GAP = 2;
8704
9378
  const GLOBAL_BAR_INNER_PAD_LEFT = 2;
@@ -8851,10 +9525,85 @@ void main() {
8851
9525
  }
8852
9526
 
8853
9527
  function syncPageChatVisual() {
8854
- if (!pageChatInput || steerLocked) return;
9528
+ if (!pageChatInput || steerLocked) {
9529
+ syncPageChatSendButton();
9530
+ return;
9531
+ }
8855
9532
  const hasText = pageChatInput.value.length > 0;
8856
9533
  if (hasText && !pageChatExpanded) expandPageChat({ focus: false });
8857
9534
  else if (!hasText && pageChatExpanded) collapsePageChat();
9535
+ syncPageChatSendButton();
9536
+ }
9537
+
9538
+ /**
9539
+ * Send is visible once the pill is open for typing and enabled once there is
9540
+ * something to send. It disappears entirely while a steer is in flight, the
9541
+ * same way the mic does: a second submit during the lock has nowhere to go.
9542
+ */
9543
+ function syncPageChatSendButton() {
9544
+ if (!pageChatSendBtn) return;
9545
+ const P = pageChatPalette();
9546
+ const hasText = !!pageChatInput?.value.trim();
9547
+ const focused = pageChatInput && activeElementDeep() === pageChatInput;
9548
+ const visible = !steerLocked && (pageChatExpanded || hasText || focused);
9549
+ pageChatSendBtn.style.display = visible ? 'inline-flex' : 'none';
9550
+ pageChatSendBtn.disabled = !visible || !hasText;
9551
+ pageChatSendBtn.style.background = P.accent;
9552
+ pageChatSendBtn.style.color = C.ink;
9553
+ pageChatSendBtn.style.borderLeft = '1px solid ' + P.hairline;
9554
+ pageChatSendBtn.style.opacity = pageChatSendBtn.disabled ? '0.42' : '1';
9555
+ pageChatSendBtn.style.cursor = pageChatSendBtn.disabled ? 'not-allowed' : 'pointer';
9556
+ pageChatSendBtn.title = pageChatSendBtn.disabled ? 'Type what to change first' : 'Send (Enter)';
9557
+ }
9558
+
9559
+ /**
9560
+ * A submitted steer is queued, not ignored, whenever no poll is parked and
9561
+ * the browser knows a generation is in flight: the agent holds the lease and
9562
+ * will not see the steer until it finishes. Saying so beats three dots that
9563
+ * look identical to a lost request.
9564
+ */
9565
+ function steerQueuedBehindGeneration() {
9566
+ return steerLocked && !agentPollingConnected && agentHasWorkInFlight();
9567
+ }
9568
+
9569
+ function buildSteerQueueHint() {
9570
+ const P = pageChatPalette();
9571
+ const hint = el('span', {
9572
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'flex-end',
9573
+ flex: '1', minWidth: '0', marginLeft: 'auto',
9574
+ padding: '0 10px 0 6px',
9575
+ fontFamily: FONT, fontSize: '10.5px', fontWeight: '500',
9576
+ color: P.patinaPale,
9577
+ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
9578
+ pointerEvents: 'none',
9579
+ });
9580
+ hint.id = PREFIX + '-page-chat-queue';
9581
+ return hint;
9582
+ }
9583
+
9584
+ function syncSteerQueueHint() {
9585
+ if (!pageChatEl) return;
9586
+ const queued = steerQueuedBehindGeneration();
9587
+ if (queued) {
9588
+ if (!pageChatQueueHintEl) {
9589
+ pageChatQueueHintEl = buildSteerQueueHint();
9590
+ pageChatEl.appendChild(pageChatQueueHintEl);
9591
+ }
9592
+ pageChatQueueHintEl.textContent = STEER_QUEUED_HINT;
9593
+ if (pageChatDotsEl) pageChatDotsEl.style.display = 'none';
9594
+ pageChatEl.style.width = PAGE_CHAT_QUEUED_W;
9595
+ pageChatEl.setAttribute('aria-label', STEER_QUEUED_HINT);
9596
+ return;
9597
+ }
9598
+ if (pageChatQueueHintEl?.parentNode) {
9599
+ pageChatQueueHintEl.remove();
9600
+ pageChatQueueHintEl = null;
9601
+ if (steerLocked) {
9602
+ pageChatEl.style.width = pageChatExpanded ? pageChatExpandedWidth() : PAGE_CHAT_COLLAPSED_W;
9603
+ pageChatEl.setAttribute('aria-label', 'Processing steer request');
9604
+ }
9605
+ }
9606
+ if (pageChatDotsEl) pageChatDotsEl.style.display = '';
8858
9607
  }
8859
9608
 
8860
9609
  function shouldFocusSteerChat() {
@@ -9026,6 +9775,7 @@ void main() {
9026
9775
 
9027
9776
  function syncPageChatFocusRing() {
9028
9777
  if (!pageChatEl || !pageChatInput) return;
9778
+ syncPageChatSendButton();
9029
9779
  const focused = activeElementDeep() === pageChatInput;
9030
9780
  const typingReady = focused && !steerLocked;
9031
9781
  pageChatEl.dataset.inputFocused = focused ? 'true' : 'false';
@@ -9179,12 +9929,28 @@ void main() {
9179
9929
  steerAwaitTimer = setTimeout(() => {
9180
9930
  if (!steerLocked || steerRequestId !== id) return;
9181
9931
  unlockSteerChat({
9182
- error: 'Steer timed out waiting for the agent. Check that live-poll is running and replies with steer_done.',
9932
+ error: steerTimeoutMessage(),
9183
9933
  restoreMessage: steerPendingMessage,
9184
9934
  });
9185
9935
  }, STEER_AWAIT_TIMEOUT_MS);
9186
9936
  }
9187
9937
 
9938
+ /**
9939
+ * Two minutes of silence has three different causes and only one of them is
9940
+ * "live-poll is not running". Naming the wrong one sends the user to restart
9941
+ * a poll loop that was never the problem.
9942
+ */
9943
+ function steerTimeoutMessage() {
9944
+ const head = 'Steer timed out after 2 minutes. ';
9945
+ if (steerQueuedBehindGeneration()) {
9946
+ return head + 'The agent is still busy with the current generation - your message was not lost, but it never got picked up. Send it again once the variants land.';
9947
+ }
9948
+ if (!agentPollingConnected) {
9949
+ return head + 'No agent is polling right now. Run live-poll.mjs, then send it again.';
9950
+ }
9951
+ return head + 'The agent picked it up but never replied with steer_done. Check the agent session for a stalled or failed steer.';
9952
+ }
9953
+
9188
9954
  function lockSteerChat() {
9189
9955
  if (!pageChatEl || !pageChatInput) return;
9190
9956
  stopVoice({ suppressSubmit: true });
@@ -9208,6 +9974,7 @@ void main() {
9208
9974
  pageChatDotsEl = buildSteerProcessingDots();
9209
9975
  pageChatEl.appendChild(pageChatDotsEl);
9210
9976
  }
9977
+ syncSteerQueueHint();
9211
9978
  syncPageChatFocusRing();
9212
9979
  syncPageChatChrome();
9213
9980
  }
@@ -9249,6 +10016,10 @@ void main() {
9249
10016
  pageChatDotsEl.remove();
9250
10017
  pageChatDotsEl = null;
9251
10018
  }
10019
+ if (pageChatQueueHintEl?.parentNode) {
10020
+ pageChatQueueHintEl.remove();
10021
+ pageChatQueueHintEl = null;
10022
+ }
9252
10023
  steerPendingMessage = keepExpanded ? restoreMessage : '';
9253
10024
  steerInputWasFocused = false;
9254
10025
  syncPageChatChrome();
@@ -9486,10 +10257,11 @@ void main() {
9486
10257
  const id = id8();
9487
10258
  steerRequestId = id;
9488
10259
  steerPendingMessage = text;
9489
- if (steerInputWasFocused) sendSteerCheckpoint(id, 'steer_input_focused', { focused: true });
9490
10260
  lockSteerChat();
9491
10261
  scheduleSteerAwaitTimeout(id);
9492
- sendSteerCheckpoint(id, 'steer_submitted', { message: text, pageUrl: location.href });
10262
+ // Checkpoints follow the steer event, never precede it: the steer event
10263
+ // is what creates the session journal server-side, and a checkpoint for
10264
+ // a not-yet-created session is rejected as unknown_session.
9493
10265
  sendEvent({
9494
10266
  type: 'steer',
9495
10267
  id,
@@ -9497,9 +10269,11 @@ void main() {
9497
10269
  pageUrl: location.href,
9498
10270
  }).then((res) => {
9499
10271
  if (!res) {
9500
- sendSteerCheckpoint(id, 'steer_send_failed', { message: text });
9501
10272
  unlockSteerChat({ error: 'Could not reach live server', restoreMessage: text });
10273
+ return;
9502
10274
  }
10275
+ if (steerInputWasFocused) sendSteerCheckpoint(id, 'steer_input_focused', { focused: true });
10276
+ sendSteerCheckpoint(id, 'steer_submitted', { message: text, pageUrl: location.href });
9503
10277
  });
9504
10278
  }
9505
10279
 
@@ -9615,10 +10389,41 @@ void main() {
9615
10389
  pageChatVoiceBtn.setAttribute('aria-label', 'Voice input');
9616
10390
  pageChatVoiceBtn.innerHTML = ICON_PAGE_VOICE;
9617
10391
 
10392
+ // Visible Send, same affordance the element-level Go bar gets from
10393
+ // buildConfigureSubmitButton. Enter still submits; the button exists so a
10394
+ // typed steer does not look like a dead-end text field.
10395
+ pageChatSendBtn = el('button', {
10396
+ display: 'none', alignItems: 'center', justifyContent: 'center',
10397
+ padding: '0', boxSizing: 'border-box',
10398
+ width: '28px', height: '28px', flexShrink: '0',
10399
+ border: 'none', borderLeft: '1px solid ' + P.hairline,
10400
+ borderRadius: '0',
10401
+ background: P.accent, color: C.ink,
10402
+ cursor: 'pointer',
10403
+ transition: 'filter 0.12s ease, opacity 0.12s ease',
10404
+ });
10405
+ pageChatSendBtn.id = PREFIX + '-page-chat-send';
10406
+ pageChatSendBtn.type = 'button';
10407
+ pageChatSendBtn.setAttribute('aria-label', 'Send steer message');
10408
+ pageChatSendBtn.innerHTML = ICON_CONFIGURE_SUBMIT;
10409
+ pageChatSendBtn.addEventListener('pointerdown', keepSteerPointerInside);
10410
+ pageChatSendBtn.addEventListener('mousedown', keepSteerPointerInside);
10411
+ pageChatSendBtn.addEventListener('mouseenter', () => {
10412
+ if (!pageChatSendBtn.disabled) pageChatSendBtn.style.filter = 'brightness(1.1)';
10413
+ });
10414
+ pageChatSendBtn.addEventListener('mouseleave', () => { pageChatSendBtn.style.filter = 'none'; });
10415
+ pageChatSendBtn.addEventListener('click', (e) => {
10416
+ e.stopPropagation();
10417
+ keepSteerPointerInside(e);
10418
+ if (steerLocked || pageChatSendBtn.disabled) return;
10419
+ submitSteerMessage();
10420
+ });
10421
+
9618
10422
  pageChatEl.appendChild(chatIcon);
9619
10423
  pageChatEl.appendChild(pageChatHint);
9620
10424
  pageChatEl.appendChild(pageChatInput);
9621
10425
  pageChatEl.appendChild(pageChatVoiceBtn);
10426
+ pageChatEl.appendChild(pageChatSendBtn);
9622
10427
 
9623
10428
  if (!uiGetById(PREFIX + '-page-chat-style')) {
9624
10429
  const s = document.createElement('style');
@@ -9641,14 +10446,14 @@ void main() {
9641
10446
 
9642
10447
  pageChatEl.addEventListener('pointerdown', (e) => {
9643
10448
  keepSteerPointerInside(e);
9644
- if (steerLocked || pageChatVoiceBtn.contains(e.target)) return;
10449
+ if (steerLocked || pageChatVoiceBtn.contains(e.target) || pageChatSendBtn.contains(e.target)) return;
9645
10450
  armPageChatForTyping({ expand: true, focus: false });
9646
10451
  });
9647
10452
  pageChatEl.addEventListener('mousedown', keepSteerPointerInside);
9648
10453
  pageChatEl.addEventListener('click', (e) => {
9649
10454
  keepSteerPointerInside(e);
9650
10455
  if (steerLocked) return;
9651
- if (pageChatVoiceBtn.contains(e.target)) return;
10456
+ if (pageChatVoiceBtn.contains(e.target) || pageChatSendBtn.contains(e.target)) return;
9652
10457
  armPageChatForTyping({ expand: true, focus: true });
9653
10458
  });
9654
10459
 
@@ -9669,6 +10474,7 @@ void main() {
9669
10474
 
9670
10475
  pageChatInput.addEventListener('input', () => {
9671
10476
  syncPageChatVisual();
10477
+ syncPageChatSendButton();
9672
10478
  });
9673
10479
 
9674
10480
  pageChatInput.addEventListener('focus', () => {
@@ -9739,6 +10545,7 @@ void main() {
9739
10545
 
9740
10546
  function syncAgentPollingUi(connected) {
9741
10547
  agentPollingConnected = !!connected;
10548
+ syncSteerQueueHint();
9742
10549
  if (!globalBarBrandEl) return;
9743
10550
  const P = barPaletteForTheme(globalBarEl?.dataset.theme || detectPageTheme());
9744
10551
  agentStatusMessage = agentStatusText();
@@ -11033,7 +11840,28 @@ void main() {
11033
11840
 
11034
11841
  // Unified render: merge parsed DESIGN.md frontmatter with sidecar v2
11035
11842
 
11843
+ /**
11844
+ * The empty state has to say which emptiness it is. `present:false` (no
11845
+ * DESIGN.md at all) is handled upstream in renderDesignBody; everything here
11846
+ * means the helper found a design system and this panel found nothing in it
11847
+ * worth drawing. Telling that user "no design system data" reads as "your
11848
+ * DESIGN.md is missing" and sends them to write a file they already have.
11849
+ */
11850
+ function designEmptyMessage() {
11851
+ if (designState.hasMd && !designState.hasSidecar) {
11852
+ return 'DESIGN.md found, no structured tokens to display. Run ' + IMPECCABLE_COMMAND + ' document to generate the .impeccable/design.json sidecar.';
11853
+ }
11854
+ if (designState.hasMd) {
11855
+ return 'DESIGN.md and its sidecar were found, but neither carries colors, type, radii, or components to display.';
11856
+ }
11857
+ return 'No design system data available.';
11858
+ }
11859
+
11036
11860
  function renderDesignVisual(body, parsed, sidecar) {
11861
+ // Count only what this function draws: renderDesignBody may already have
11862
+ // appended a stale-sidecar hint or the basic-view CTA, and those must not
11863
+ // pass for token content.
11864
+ const beforeCount = body.childElementCount;
11037
11865
  const frontmatter = parsed?.frontmatter || {};
11038
11866
  const extensions = sidecar?.extensions || {};
11039
11867
  const proseColors = parsed?.colors || null;
@@ -11061,8 +11889,8 @@ void main() {
11061
11889
  body.appendChild(renderOverviewCollapsible(narrative));
11062
11890
  }
11063
11891
 
11064
- if (body.childElementCount === 0) {
11065
- body.appendChild(msgDiv('empty', 'No design system data available.'));
11892
+ if (body.childElementCount === beforeCount) {
11893
+ body.appendChild(msgDiv('empty', designEmptyMessage()));
11066
11894
  }
11067
11895
  }
11068
11896
 
@@ -11147,7 +11975,9 @@ void main() {
11147
11975
  rules: [
11148
11976
  ...(md.colors?.rules || []).map((r) => ({ ...r, section: 'colors' })),
11149
11977
  ...(md.typography?.rules || []).map((r) => ({ ...r, section: 'typography' })),
11978
+ ...(md.layout?.rules || []).map((r) => ({ ...r, section: 'layout' })),
11150
11979
  ...(md.elevation?.rules || []).map((r) => ({ ...r, section: 'elevation' })),
11980
+ ...(md.shapes?.rules || []).map((r) => ({ ...r, section: 'shapes' })),
11151
11981
  ],
11152
11982
  dos: md.dosDonts?.dos || [],
11153
11983
  donts: md.dosDonts?.donts || [],