@bastani/atomic 0.9.10 → 0.9.11-alpha.10

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 (2339) hide show
  1. package/CHANGELOG.md +255 -0
  2. package/README.md +15 -10
  3. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  4. package/dist/builtin/intercom/package.json +4 -4
  5. package/dist/builtin/mcp/CHANGELOG.md +18 -0
  6. package/dist/builtin/mcp/README.md +3 -3
  7. package/dist/builtin/mcp/cli.js +0 -1
  8. package/dist/builtin/mcp/commands.ts +7 -3
  9. package/dist/builtin/mcp/config.ts +0 -2
  10. package/dist/builtin/mcp/package.json +6 -6
  11. package/dist/builtin/mcp/sampling-handler.ts +1 -5
  12. package/dist/builtin/mcp/types.ts +0 -1
  13. package/dist/builtin/subagents/CHANGELOG.md +40 -0
  14. package/dist/builtin/subagents/README.md +10 -1
  15. package/dist/builtin/subagents/agents/code-simplifier.md +48 -103
  16. package/dist/builtin/subagents/agents/codebase-analyzer.md +28 -131
  17. package/dist/builtin/subagents/agents/codebase-locator.md +25 -84
  18. package/dist/builtin/subagents/agents/codebase-online-researcher.md +61 -260
  19. package/dist/builtin/subagents/agents/codebase-pattern-finder.md +30 -208
  20. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +29 -151
  21. package/dist/builtin/subagents/agents/codebase-research-locator.md +22 -119
  22. package/dist/builtin/subagents/agents/debugger.md +29 -65
  23. package/dist/builtin/subagents/agents/worker.md +24 -31
  24. package/dist/builtin/subagents/package.json +5 -5
  25. package/dist/builtin/subagents/prompts/gather-context-and-clarify.md +18 -11
  26. package/dist/builtin/subagents/prompts/parallel-cleanup.md +34 -33
  27. package/dist/builtin/subagents/prompts/parallel-context-build.md +22 -36
  28. package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +20 -52
  29. package/dist/builtin/subagents/prompts/parallel-research.md +17 -41
  30. package/dist/builtin/subagents/prompts/parallel-review.md +23 -28
  31. package/dist/builtin/subagents/prompts/review-loop.md +18 -27
  32. package/dist/builtin/subagents/skills/subagent/SKILL.md +6 -6
  33. package/dist/builtin/subagents/src/agents/agent-discovery.ts +26 -15
  34. package/dist/builtin/subagents/src/agents/agent-loaders.ts +48 -34
  35. package/dist/builtin/subagents/src/agents/agent-management-helpers.ts +80 -31
  36. package/dist/builtin/subagents/src/agents/agent-management.ts +194 -55
  37. package/dist/builtin/subagents/src/agents/agent-overrides.ts +85 -32
  38. package/dist/builtin/subagents/src/agents/agent-paths.ts +4 -1
  39. package/dist/builtin/subagents/src/agents/agent-selection.ts +1 -1
  40. package/dist/builtin/subagents/src/agents/agent-serializer.ts +1 -4
  41. package/dist/builtin/subagents/src/agents/agents.ts +9 -9
  42. package/dist/builtin/subagents/src/agents/chain-serializer.ts +9 -5
  43. package/dist/builtin/subagents/src/agents/identity.ts +12 -3
  44. package/dist/builtin/subagents/src/agents/skills-paths.ts +64 -24
  45. package/dist/builtin/subagents/src/agents/skills.ts +29 -26
  46. package/dist/builtin/subagents/src/extension/doctor.ts +31 -20
  47. package/dist/builtin/subagents/src/extension/fanout-child.ts +27 -12
  48. package/dist/builtin/subagents/src/extension/index.ts +119 -43
  49. package/dist/builtin/subagents/src/extension/prompt-guidance.ts +4 -2
  50. package/dist/builtin/subagents/src/extension/schemas.ts +304 -177
  51. package/dist/builtin/subagents/src/extension/startup-maintenance.ts +137 -73
  52. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +49 -21
  53. package/dist/builtin/subagents/src/intercom/result-intercom.ts +76 -38
  54. package/dist/builtin/subagents/src/runs/background/async-event-journal.ts +12 -6
  55. package/dist/builtin/subagents/src/runs/background/async-execution-chain.ts +183 -57
  56. package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +15 -18
  57. package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +67 -26
  58. package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +8 -8
  59. package/dist/builtin/subagents/src/runs/background/async-execution.ts +1 -1
  60. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +86 -32
  61. package/dist/builtin/subagents/src/runs/background/async-resume.ts +93 -36
  62. package/dist/builtin/subagents/src/runs/background/async-status.ts +82 -27
  63. package/dist/builtin/subagents/src/runs/background/completion-claims.ts +12 -5
  64. package/dist/builtin/subagents/src/runs/background/completion-dedupe.ts +3 -1
  65. package/dist/builtin/subagents/src/runs/background/completion-notification.ts +3 -1
  66. package/dist/builtin/subagents/src/runs/background/notify.ts +38 -18
  67. package/dist/builtin/subagents/src/runs/background/parallel-groups.ts +28 -14
  68. package/dist/builtin/subagents/src/runs/background/result-delivery-processor.ts +147 -79
  69. package/dist/builtin/subagents/src/runs/background/result-file-claims.ts +20 -5
  70. package/dist/builtin/subagents/src/runs/background/result-quarantine.ts +7 -2
  71. package/dist/builtin/subagents/src/runs/background/result-status.ts +11 -5
  72. package/dist/builtin/subagents/src/runs/background/result-watcher-data.ts +18 -5
  73. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +104 -35
  74. package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +28 -7
  75. package/dist/builtin/subagents/src/runs/background/run-status.ts +120 -32
  76. package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +98 -39
  77. package/dist/builtin/subagents/src/runs/background/subagent-runner-dynamic.ts +185 -37
  78. package/dist/builtin/subagents/src/runs/background/subagent-runner-finalize.ts +23 -9
  79. package/dist/builtin/subagents/src/runs/background/subagent-runner-output.ts +6 -2
  80. package/dist/builtin/subagents/src/runs/background/subagent-runner-parallel-helpers.ts +34 -18
  81. package/dist/builtin/subagents/src/runs/background/subagent-runner-parallel.ts +175 -26
  82. package/dist/builtin/subagents/src/runs/background/subagent-runner-sequential.ts +68 -23
  83. package/dist/builtin/subagents/src/runs/background/subagent-runner-state.ts +200 -49
  84. package/dist/builtin/subagents/src/runs/background/subagent-runner-step.ts +82 -49
  85. package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +33 -11
  86. package/dist/builtin/subagents/src/runs/background/subagent-runner-types.ts +16 -6
  87. package/dist/builtin/subagents/src/runs/background/subagent-runner-utils.ts +2 -3
  88. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +11 -8
  89. package/dist/builtin/subagents/src/runs/foreground/chain-execution-details.ts +5 -2
  90. package/dist/builtin/subagents/src/runs/foreground/chain-execution-dynamic-step.ts +115 -31
  91. package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +71 -54
  92. package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-step.ts +104 -37
  93. package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +102 -61
  94. package/dist/builtin/subagents/src/runs/foreground/chain-execution-types.ts +24 -10
  95. package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +9 -6
  96. package/dist/builtin/subagents/src/runs/foreground/detached-cleanup-barrier.ts +22 -22
  97. package/dist/builtin/subagents/src/runs/foreground/execution-attempt-control.ts +45 -40
  98. package/dist/builtin/subagents/src/runs/foreground/execution-attempt-finalize.ts +6 -5
  99. package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +118 -43
  100. package/dist/builtin/subagents/src/runs/foreground/execution-detach-reservations.ts +17 -7
  101. package/dist/builtin/subagents/src/runs/foreground/execution-detach-route.ts +5 -3
  102. package/dist/builtin/subagents/src/runs/foreground/execution-intercom-detach.ts +39 -35
  103. package/dist/builtin/subagents/src/runs/foreground/execution-run-sync.ts +81 -42
  104. package/dist/builtin/subagents/src/runs/foreground/execution-structured-retries.ts +27 -21
  105. package/dist/builtin/subagents/src/runs/foreground/execution-updates.ts +2 -5
  106. package/dist/builtin/subagents/src/runs/foreground/execution-utils.ts +9 -4
  107. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +69 -25
  108. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-chain.ts +36 -19
  109. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +95 -62
  110. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +30 -9
  111. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +106 -96
  112. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +68 -36
  113. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +262 -83
  114. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +6 -1
  115. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +67 -41
  116. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +129 -44
  117. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +20 -3
  118. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-worktree.ts +8 -6
  119. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +65 -47
  120. package/dist/builtin/subagents/src/runs/shared/chain-outputs.ts +37 -12
  121. package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +120 -34
  122. package/dist/builtin/subagents/src/runs/shared/final-drain.ts +12 -7
  123. package/dist/builtin/subagents/src/runs/shared/intercom-group.ts +3 -2
  124. package/dist/builtin/subagents/src/runs/shared/long-running-guard.ts +8 -3
  125. package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +42 -20
  126. package/dist/builtin/subagents/src/runs/shared/model-candidate-filter.ts +8 -4
  127. package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +103 -38
  128. package/dist/builtin/subagents/src/runs/shared/nested-events-control.ts +30 -20
  129. package/dist/builtin/subagents/src/runs/shared/nested-events-core.ts +45 -20
  130. package/dist/builtin/subagents/src/runs/shared/nested-events-projection.ts +57 -23
  131. package/dist/builtin/subagents/src/runs/shared/nested-events-registry.ts +77 -29
  132. package/dist/builtin/subagents/src/runs/shared/nested-events-sanitize.ts +99 -42
  133. package/dist/builtin/subagents/src/runs/shared/nested-events.ts +32 -26
  134. package/dist/builtin/subagents/src/runs/shared/nested-path.ts +22 -17
  135. package/dist/builtin/subagents/src/runs/shared/nested-render.ts +31 -10
  136. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +10 -8
  137. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +59 -33
  138. package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +17 -13
  139. package/dist/builtin/subagents/src/runs/shared/run-history.ts +13 -6
  140. package/dist/builtin/subagents/src/runs/shared/single-output.ts +11 -6
  141. package/dist/builtin/subagents/src/runs/shared/spawn-env.ts +21 -0
  142. package/dist/builtin/subagents/src/runs/shared/structured-output.ts +33 -19
  143. package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +59 -48
  144. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +18 -11
  145. package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +41 -11
  146. package/dist/builtin/subagents/src/runs/shared/worktree-post-create.ts +46 -10
  147. package/dist/builtin/subagents/src/runs/shared/worktree-root.ts +21 -5
  148. package/dist/builtin/subagents/src/runs/shared/worktree.ts +89 -34
  149. package/dist/builtin/subagents/src/shared/artifacts.ts +137 -7
  150. package/dist/builtin/subagents/src/shared/event-jsonl-writer.ts +49 -13
  151. package/dist/builtin/subagents/src/shared/fast-mode.ts +13 -6
  152. package/dist/builtin/subagents/src/shared/fork-context.ts +7 -4
  153. package/dist/builtin/subagents/src/shared/formatters.ts +14 -9
  154. package/dist/builtin/subagents/src/shared/jsonl-writer.ts +2 -1
  155. package/dist/builtin/subagents/src/shared/model-info.ts +10 -3
  156. package/dist/builtin/subagents/src/shared/post-exit-stdio-guard.ts +7 -6
  157. package/dist/builtin/subagents/src/shared/session-tokens.ts +2 -1
  158. package/dist/builtin/subagents/src/shared/settings.ts +43 -43
  159. package/dist/builtin/subagents/src/shared/status-format.ts +10 -2
  160. package/dist/builtin/subagents/src/shared/types-async.ts +22 -19
  161. package/dist/builtin/subagents/src/shared/types-config.ts +1 -1
  162. package/dist/builtin/subagents/src/shared/types-depth.ts +23 -11
  163. package/dist/builtin/subagents/src/shared/types-results.ts +49 -6
  164. package/dist/builtin/subagents/src/shared/types-runtime.ts +16 -17
  165. package/dist/builtin/subagents/src/shared/types.ts +2 -2
  166. package/dist/builtin/subagents/src/shared/utils.ts +34 -20
  167. package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +44 -47
  168. package/dist/builtin/subagents/src/slash/saved-chain-mapping.ts +5 -7
  169. package/dist/builtin/subagents/src/slash/slash-bridge.ts +3 -3
  170. package/dist/builtin/subagents/src/slash/slash-commands.ts +78 -52
  171. package/dist/builtin/subagents/src/slash/slash-live-state.ts +59 -32
  172. package/dist/builtin/subagents/src/tui/render-chain-graph.ts +120 -32
  173. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +85 -22
  174. package/dist/builtin/subagents/src/tui/render-helpers.ts +16 -6
  175. package/dist/builtin/subagents/src/tui/render-layout.ts +8 -3
  176. package/dist/builtin/subagents/src/tui/render-result-compact.ts +125 -43
  177. package/dist/builtin/subagents/src/tui/render-result.ts +128 -85
  178. package/dist/builtin/subagents/src/tui/render-stable-output.ts +17 -13
  179. package/dist/builtin/subagents/src/tui/render-status-progress.ts +52 -20
  180. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +130 -28
  181. package/dist/builtin/subagents/src/tui/render-widget.ts +54 -27
  182. package/dist/builtin/subagents/src/tui/render.ts +10 -3
  183. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  184. package/dist/builtin/web-access/package.json +2 -2
  185. package/dist/builtin/workflows/CHANGELOG.md +127 -0
  186. package/dist/builtin/workflows/README.md +83 -70
  187. package/dist/builtin/workflows/ambient.d.ts +0 -5
  188. package/dist/builtin/workflows/builtin/adversarial-verification-prompts.ts +7 -4
  189. package/dist/builtin/workflows/builtin/adversarial-verification-runner.ts +1 -1
  190. package/dist/builtin/workflows/builtin/adversarial-verification.ts +2 -1
  191. package/dist/builtin/workflows/builtin/classify-and-act-prompts.ts +5 -2
  192. package/dist/builtin/workflows/builtin/classify-and-act-runner.ts +5 -7
  193. package/dist/builtin/workflows/builtin/classify-and-act.ts +3 -2
  194. package/dist/builtin/workflows/builtin/fan-out-and-synthesize-prompts.ts +6 -3
  195. package/dist/builtin/workflows/builtin/fan-out-and-synthesize-runner.ts +5 -5
  196. package/dist/builtin/workflows/builtin/fan-out-and-synthesize.ts +3 -2
  197. package/dist/builtin/workflows/builtin/generate-and-filter-prompts.ts +7 -4
  198. package/dist/builtin/workflows/builtin/generate-and-filter-runner.ts +6 -3
  199. package/dist/builtin/workflows/builtin/generate-and-filter.ts +3 -2
  200. package/dist/builtin/workflows/builtin/goal-artifacts.ts +1 -1
  201. package/dist/builtin/workflows/builtin/goal-models.ts +39 -39
  202. package/dist/builtin/workflows/builtin/goal-orchestrator-prompts.ts +94 -0
  203. package/dist/builtin/workflows/builtin/goal-prompts.ts +70 -285
  204. package/dist/builtin/workflows/builtin/goal-reducer.ts +1 -1
  205. package/dist/builtin/workflows/builtin/goal-runner.ts +72 -116
  206. package/dist/builtin/workflows/builtin/goal.ts +12 -11
  207. package/dist/builtin/workflows/builtin/index.d.ts +17 -112
  208. package/dist/builtin/workflows/builtin/index.ts +1 -2
  209. package/dist/builtin/workflows/builtin/loop-until-done-prompts.ts +20 -12
  210. package/dist/builtin/workflows/builtin/loop-until-done-runner.ts +3 -0
  211. package/dist/builtin/workflows/builtin/loop-until-done.ts +3 -2
  212. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +149 -88
  213. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +102 -78
  214. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +164 -56
  215. package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +16 -21
  216. package/dist/builtin/workflows/builtin/open-claude-design.ts +2 -1
  217. package/dist/builtin/workflows/builtin/ralph-core.ts +61 -57
  218. package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +41 -45
  219. package/dist/builtin/workflows/builtin/ralph-models.ts +37 -47
  220. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +37 -117
  221. package/dist/builtin/workflows/builtin/ralph-runner.ts +83 -108
  222. package/dist/builtin/workflows/builtin/ralph.ts +6 -5
  223. package/dist/builtin/workflows/builtin/shared-prompts.ts +117 -70
  224. package/dist/builtin/workflows/builtin/steering-context.ts +51 -0
  225. package/dist/builtin/workflows/builtin/tournament-prompts.ts +21 -12
  226. package/dist/builtin/workflows/builtin/tournament-runner.ts +3 -0
  227. package/dist/builtin/workflows/builtin/tournament.ts +3 -2
  228. package/dist/builtin/workflows/package.json +4 -4
  229. package/dist/builtin/workflows/skills/create-spec/SKILL.md +1 -1
  230. package/dist/builtin/workflows/skills/impeccable/SKILL.md +33 -129
  231. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_asset_producer.toml +11 -3
  232. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_documenter.toml +26 -0
  233. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_finish_reviewer.toml +35 -0
  234. package/dist/builtin/workflows/skills/impeccable/reference/android.md +1 -1
  235. package/dist/builtin/workflows/skills/impeccable/reference/animate.md +72 -189
  236. package/dist/builtin/workflows/skills/impeccable/reference/audit.md +10 -9
  237. package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +2 -2
  238. package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +19 -108
  239. package/dist/builtin/workflows/skills/impeccable/reference/clarify.md +59 -253
  240. package/dist/builtin/workflows/skills/impeccable/reference/colorize.md +51 -222
  241. package/dist/builtin/workflows/skills/impeccable/reference/craft-floor.md +45 -0
  242. package/dist/builtin/workflows/skills/impeccable/reference/craft.md +3 -121
  243. package/dist/builtin/workflows/skills/impeccable/reference/critique.md +28 -20
  244. package/dist/builtin/workflows/skills/impeccable/reference/degraded/asset-producer.md +97 -0
  245. package/dist/builtin/workflows/skills/impeccable/reference/degraded/documenter.md +23 -0
  246. package/dist/builtin/workflows/skills/impeccable/reference/degraded/finish-reviewer.md +32 -0
  247. package/dist/builtin/workflows/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
  248. package/dist/builtin/workflows/skills/impeccable/reference/delight.md +47 -279
  249. package/dist/builtin/workflows/skills/impeccable/reference/distill.md +2 -2
  250. package/dist/builtin/workflows/skills/impeccable/reference/doctor.md +53 -0
  251. package/dist/builtin/workflows/skills/impeccable/reference/document.md +60 -73
  252. package/dist/builtin/workflows/skills/impeccable/reference/harden.md +1 -12
  253. package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +23 -12
  254. package/dist/builtin/workflows/skills/impeccable/reference/init.md +64 -163
  255. package/dist/builtin/workflows/skills/impeccable/reference/ios.md +1 -1
  256. package/dist/builtin/workflows/skills/impeccable/reference/layout.md +52 -153
  257. package/dist/builtin/workflows/skills/impeccable/reference/live.md +47 -36
  258. package/dist/builtin/workflows/skills/impeccable/reference/new-work.md +105 -0
  259. package/dist/builtin/workflows/skills/impeccable/reference/{product.md → operate.md} +6 -5
  260. package/dist/builtin/workflows/skills/impeccable/reference/optimize.md +4 -4
  261. package/dist/builtin/workflows/skills/impeccable/reference/overdrive.md +1 -4
  262. package/dist/builtin/workflows/skills/impeccable/reference/polish.md +68 -212
  263. package/dist/builtin/workflows/skills/impeccable/reference/quieter.md +3 -3
  264. package/dist/builtin/workflows/skills/impeccable/reference/routing.md +18 -0
  265. package/dist/builtin/workflows/skills/impeccable/reference/shape.md +38 -144
  266. package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +48 -269
  267. package/dist/builtin/workflows/skills/impeccable/reference/visualize.md +38 -0
  268. package/dist/builtin/workflows/skills/impeccable/scripts/command-metadata.json +1 -1
  269. package/dist/builtin/workflows/skills/impeccable/scripts/concept-seed.mjs +584 -0
  270. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +117 -9
  271. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +486 -59
  272. package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +16 -45
  273. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +96 -10
  274. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +143 -26
  275. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +181 -12
  276. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +3187 -182
  277. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +102 -7
  278. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +282 -70
  279. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +183 -15
  280. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +22 -7
  281. package/dist/builtin/workflows/skills/impeccable/scripts/detector/findings.mjs +7 -1
  282. package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +16 -2
  283. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +155 -42
  284. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +2988 -155
  285. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/constants.mjs +11 -0
  286. package/dist/builtin/workflows/skills/impeccable/scripts/doctor.mjs +336 -0
  287. package/dist/builtin/workflows/skills/impeccable/scripts/generate-image.mjs +232 -0
  288. package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +92 -30
  289. package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +414 -120
  290. package/dist/builtin/workflows/skills/impeccable/scripts/hook.mjs +25 -8
  291. package/dist/builtin/workflows/skills/impeccable/scripts/lib/artifact-schema.mjs +93 -0
  292. package/dist/builtin/workflows/skills/impeccable/scripts/lib/composition-catalog.mjs +165 -0
  293. package/dist/builtin/workflows/skills/impeccable/scripts/lib/concept-catalog.mjs +329 -0
  294. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +20 -5
  295. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +16 -8
  296. package/dist/builtin/workflows/skills/impeccable/scripts/lib/provider.mjs +1 -0
  297. package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-deep.mjs +455 -0
  298. package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-notice.mjs +169 -0
  299. package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness.mjs +457 -0
  300. package/dist/builtin/workflows/skills/impeccable/scripts/lib/surface-briefs.mjs +151 -0
  301. package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-slug.mjs +33 -0
  302. package/dist/builtin/workflows/skills/impeccable/scripts/lib/template-extensions.mjs +146 -0
  303. package/dist/builtin/workflows/skills/impeccable/scripts/live/completion.mjs +10 -1
  304. package/dist/builtin/workflows/skills/impeccable/scripts/live/event-validation.mjs +15 -0
  305. package/dist/builtin/workflows/skills/impeccable/scripts/live/generation-preflight.mjs +149 -0
  306. package/dist/builtin/workflows/skills/impeccable/scripts/live/poll-lanes.mjs +14 -0
  307. package/dist/builtin/workflows/skills/impeccable/scripts/live/session-store.mjs +109 -32
  308. package/dist/builtin/workflows/skills/impeccable/scripts/live/source-lock.mjs +105 -0
  309. package/dist/builtin/workflows/skills/impeccable/scripts/live/source-search.mjs +105 -0
  310. package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +8 -6
  311. package/dist/builtin/workflows/skills/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
  312. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +212 -69
  313. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +5 -1
  314. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-session.js +7 -1
  315. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +565 -115
  316. package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +3 -0
  317. package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +198 -14
  318. package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +24 -6
  319. package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +11 -3
  320. package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +44 -14
  321. package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +405 -34
  322. package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +9 -5
  323. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +81 -67
  324. package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +7 -2
  325. package/dist/builtin/workflows/skills/impeccable/scripts/palette.mjs +76 -81
  326. package/dist/builtin/workflows/skills/impeccable/scripts/pin.mjs +4 -4
  327. package/dist/builtin/workflows/skills/impeccable/scripts/serve-question.mjs +890 -0
  328. package/dist/builtin/workflows/skills/impeccable/scripts/surface-brief.mjs +74 -0
  329. package/dist/builtin/workflows/skills/prompt-engineer/SKILL.md +57 -252
  330. package/dist/builtin/workflows/skills/prompt-engineer/references/advanced_patterns.md +70 -226
  331. package/dist/builtin/workflows/skills/prompt-engineer/references/core_prompting.md +64 -103
  332. package/dist/builtin/workflows/skills/prompt-engineer/references/quality_improvement.md +81 -155
  333. package/dist/builtin/workflows/src/authoring/typebox-defaults.d.ts +5 -5
  334. package/dist/builtin/workflows/src/authoring/typebox-defaults.ts +160 -197
  335. package/dist/builtin/workflows/src/authoring/workflow.ts +137 -132
  336. package/dist/builtin/workflows/src/authoring.d.ts +7 -3
  337. package/dist/builtin/workflows/src/durable/backend.ts +512 -385
  338. package/dist/builtin/workflows/src/durable/boundary-lifecycle.ts +172 -0
  339. package/dist/builtin/workflows/src/durable/boundary-topology.ts +475 -0
  340. package/dist/builtin/workflows/src/durable/child-invocation.ts +14 -0
  341. package/dist/builtin/workflows/src/durable/child-primitive.ts +115 -74
  342. package/dist/builtin/workflows/src/durable/completed-catalog-stage-groups.ts +290 -0
  343. package/dist/builtin/workflows/src/durable/completed-catalog.ts +419 -189
  344. package/dist/builtin/workflows/src/durable/completed-inspection.ts +118 -105
  345. package/dist/builtin/workflows/src/durable/completed-subtree.ts +35 -0
  346. package/dist/builtin/workflows/src/durable/dbos-backend.ts +484 -414
  347. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +172 -0
  348. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +183 -137
  349. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +415 -189
  350. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +128 -100
  351. package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +75 -68
  352. package/dist/builtin/workflows/src/durable/dbos-metadata.ts +138 -101
  353. package/dist/builtin/workflows/src/durable/dbos-prompt-reservations.ts +316 -259
  354. package/dist/builtin/workflows/src/durable/dbos-sdk-handle.ts +138 -117
  355. package/dist/builtin/workflows/src/durable/dbos-status-transition.ts +48 -43
  356. package/dist/builtin/workflows/src/durable/dbos-tombstone.ts +14 -14
  357. package/dist/builtin/workflows/src/durable/durable-hash.ts +9 -11
  358. package/dist/builtin/workflows/src/durable/factory.ts +70 -16
  359. package/dist/builtin/workflows/src/durable/format-version.ts +1 -1
  360. package/dist/builtin/workflows/src/durable/index.ts +69 -68
  361. package/dist/builtin/workflows/src/durable/local-command.ts +50 -37
  362. package/dist/builtin/workflows/src/durable/prompt-reservation-state.ts +156 -149
  363. package/dist/builtin/workflows/src/durable/prompt-reservations.ts +16 -16
  364. package/dist/builtin/workflows/src/durable/resume-catalog.ts +11 -11
  365. package/dist/builtin/workflows/src/durable/resume-eligibility.ts +27 -25
  366. package/dist/builtin/workflows/src/durable/resume-runtime.ts +261 -222
  367. package/dist/builtin/workflows/src/durable/retention-policy.ts +31 -30
  368. package/dist/builtin/workflows/src/durable/run-timing.ts +42 -41
  369. package/dist/builtin/workflows/src/durable/scoped-backend.ts +261 -193
  370. package/dist/builtin/workflows/src/durable/stage-primitive.ts +466 -370
  371. package/dist/builtin/workflows/src/durable/stage-topology-validation.ts +247 -0
  372. package/dist/builtin/workflows/src/durable/stage-topology.ts +53 -0
  373. package/dist/builtin/workflows/src/durable/tool-failure-checkpoint.ts +49 -0
  374. package/dist/builtin/workflows/src/durable/tool-outcome.ts +130 -0
  375. package/dist/builtin/workflows/src/durable/tool-primitive.ts +642 -137
  376. package/dist/builtin/workflows/src/durable/types.ts +243 -132
  377. package/dist/builtin/workflows/src/durable/ui-primitive.ts +243 -164
  378. package/dist/builtin/workflows/src/durable/workflow-child-result.ts +61 -0
  379. package/dist/builtin/workflows/src/durable/workflow-status-transition.ts +16 -15
  380. package/dist/builtin/workflows/src/engine/graph-inference.ts +76 -76
  381. package/dist/builtin/workflows/src/engine/options.ts +35 -35
  382. package/dist/builtin/workflows/src/engine/primitives/chain.ts +27 -22
  383. package/dist/builtin/workflows/src/engine/primitives/exit.ts +1 -1
  384. package/dist/builtin/workflows/src/engine/primitives/parallel.ts +64 -52
  385. package/dist/builtin/workflows/src/engine/primitives/task.ts +110 -95
  386. package/dist/builtin/workflows/src/engine/primitives/ui.ts +46 -50
  387. package/dist/builtin/workflows/src/engine/primitives/workflow.ts +194 -151
  388. package/dist/builtin/workflows/src/engine/replay.ts +6 -6
  389. package/dist/builtin/workflows/src/engine/run-durable-admission.ts +50 -0
  390. package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +64 -37
  391. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +30 -24
  392. package/dist/builtin/workflows/src/engine/run-durable-topology.ts +288 -0
  393. package/dist/builtin/workflows/src/engine/run-returned-status.ts +60 -57
  394. package/dist/builtin/workflows/src/engine/run-terminal-event.ts +39 -0
  395. package/dist/builtin/workflows/src/engine/run-terminal-failure.ts +53 -0
  396. package/dist/builtin/workflows/src/engine/run-tool-admission-boundary.ts +99 -0
  397. package/dist/builtin/workflows/src/engine/run-tool-control-registry.ts +179 -0
  398. package/dist/builtin/workflows/src/engine/run-tool-execution-tracker.ts +164 -0
  399. package/dist/builtin/workflows/src/engine/run-tool-node-lifecycle.ts +152 -0
  400. package/dist/builtin/workflows/src/engine/run.ts +680 -477
  401. package/dist/builtin/workflows/src/engine/runtime.ts +160 -140
  402. package/dist/builtin/workflows/src/engine/workflow-activity.ts +7 -3
  403. package/dist/builtin/workflows/src/engine/workflow-tool-abort.ts +92 -0
  404. package/dist/builtin/workflows/src/extension/atomic-stage-session.ts +123 -120
  405. package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +108 -112
  406. package/dist/builtin/workflows/src/extension/companions.ts +116 -116
  407. package/dist/builtin/workflows/src/extension/completed-stage-intercom-ask.ts +123 -111
  408. package/dist/builtin/workflows/src/extension/config-file-loader.ts +153 -148
  409. package/dist/builtin/workflows/src/extension/config-loader.ts +264 -283
  410. package/dist/builtin/workflows/src/extension/discovery-loaders.ts +81 -84
  411. package/dist/builtin/workflows/src/extension/discovery.ts +267 -272
  412. package/dist/builtin/workflows/src/extension/dispatcher.ts +214 -217
  413. package/dist/builtin/workflows/src/extension/extension-factory.ts +103 -93
  414. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +135 -107
  415. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +414 -409
  416. package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +270 -261
  417. package/dist/builtin/workflows/src/extension/index.bundle.mjs +74625 -0
  418. package/dist/builtin/workflows/src/extension/index.ts +37 -37
  419. package/dist/builtin/workflows/src/extension/lifecycle-notification-delivery.ts +64 -64
  420. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +386 -369
  421. package/dist/builtin/workflows/src/extension/mcp.ts +35 -35
  422. package/dist/builtin/workflows/src/extension/postmortem-deps.ts +31 -35
  423. package/dist/builtin/workflows/src/extension/public-types.ts +162 -185
  424. package/dist/builtin/workflows/src/extension/render-call.ts +85 -84
  425. package/dist/builtin/workflows/src/extension/render-component.ts +9 -11
  426. package/dist/builtin/workflows/src/extension/render-result.ts +453 -348
  427. package/dist/builtin/workflows/src/extension/renderers.ts +35 -35
  428. package/dist/builtin/workflows/src/extension/runtime-active-block-claim.ts +37 -37
  429. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +146 -138
  430. package/dist/builtin/workflows/src/extension/runtime.ts +346 -258
  431. package/dist/builtin/workflows/src/extension/status-writer.ts +95 -95
  432. package/dist/builtin/workflows/src/extension/ui-surface.ts +224 -218
  433. package/dist/builtin/workflows/src/extension/wiring.ts +307 -281
  434. package/dist/builtin/workflows/src/extension/workflow-command-completions.ts +109 -108
  435. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +257 -224
  436. package/dist/builtin/workflows/src/extension/workflow-command-surfaces.ts +84 -83
  437. package/dist/builtin/workflows/src/extension/workflow-command-utils.ts +126 -132
  438. package/dist/builtin/workflows/src/extension/workflow-durable-resume-command.ts +213 -199
  439. package/dist/builtin/workflows/src/extension/workflow-model-catalog.ts +24 -0
  440. package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +110 -95
  441. package/dist/builtin/workflows/src/extension/workflow-policy.ts +7 -9
  442. package/dist/builtin/workflows/src/extension/workflow-ports.ts +30 -34
  443. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +41 -17
  444. package/dist/builtin/workflows/src/extension/workflow-reload-coordinator.ts +46 -46
  445. package/dist/builtin/workflows/src/extension/workflow-reload-report.ts +28 -25
  446. package/dist/builtin/workflows/src/extension/workflow-resume-picker-rows.ts +48 -45
  447. package/dist/builtin/workflows/src/extension/workflow-resume-shadow.ts +28 -31
  448. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +539 -453
  449. package/dist/builtin/workflows/src/extension/workflow-schema.ts +149 -91
  450. package/dist/builtin/workflows/src/extension/workflow-stage-results.ts +182 -194
  451. package/dist/builtin/workflows/src/extension/workflow-status-summary.ts +154 -116
  452. package/dist/builtin/workflows/src/extension/workflow-targets.ts +159 -125
  453. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +204 -216
  454. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +520 -294
  455. package/dist/builtin/workflows/src/extension/workflow-tool-inspection.ts +146 -150
  456. package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +43 -46
  457. package/dist/builtin/workflows/src/extension/workflow-tool-send.ts +354 -206
  458. package/dist/builtin/workflows/src/extension/workflow-tool.ts +123 -117
  459. package/dist/builtin/workflows/src/intercom/intercom-bridge.ts +17 -20
  460. package/dist/builtin/workflows/src/intercom/intercom-routing.ts +62 -62
  461. package/dist/builtin/workflows/src/intercom/result-intercom.ts +123 -123
  462. package/dist/builtin/workflows/src/runs/background/cancellation-registry.ts +72 -59
  463. package/dist/builtin/workflows/src/runs/background/durable-resume-transition.ts +45 -45
  464. package/dist/builtin/workflows/src/runs/background/job-tracker.ts +69 -34
  465. package/dist/builtin/workflows/src/runs/background/quit-tool-node.ts +71 -0
  466. package/dist/builtin/workflows/src/runs/background/quit.ts +359 -138
  467. package/dist/builtin/workflows/src/runs/background/resume-acknowledgements.ts +70 -69
  468. package/dist/builtin/workflows/src/runs/background/run-inspect.ts +85 -74
  469. package/dist/builtin/workflows/src/runs/background/runner.ts +122 -121
  470. package/dist/builtin/workflows/src/runs/background/startup-admission.ts +56 -60
  471. package/dist/builtin/workflows/src/runs/background/status.ts +324 -284
  472. package/dist/builtin/workflows/src/runs/background/workflow-lifecycle-aggregate.ts +24 -26
  473. package/dist/builtin/workflows/src/runs/foreground/executor-abort.ts +311 -309
  474. package/dist/builtin/workflows/src/runs/foreground/executor-child-boundary.ts +229 -199
  475. package/dist/builtin/workflows/src/runs/foreground/executor-child-helpers.ts +56 -51
  476. package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +168 -152
  477. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +255 -217
  478. package/dist/builtin/workflows/src/runs/foreground/executor-exit-manager.ts +178 -173
  479. package/dist/builtin/workflows/src/runs/foreground/executor-hil.ts +273 -269
  480. package/dist/builtin/workflows/src/runs/foreground/executor-inputs.ts +59 -56
  481. package/dist/builtin/workflows/src/runs/foreground/executor-lifecycle.ts +375 -351
  482. package/dist/builtin/workflows/src/runs/foreground/executor-outputs.ts +123 -127
  483. package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +360 -295
  484. package/dist/builtin/workflows/src/runs/foreground/executor-queued-user-message.ts +47 -47
  485. package/dist/builtin/workflows/src/runs/foreground/executor-run-finalizers.ts +125 -104
  486. package/dist/builtin/workflows/src/runs/foreground/executor-scheduler.ts +189 -189
  487. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +294 -242
  488. package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +141 -127
  489. package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +219 -179
  490. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +406 -354
  491. package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +143 -126
  492. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +50 -47
  493. package/dist/builtin/workflows/src/runs/foreground/executor-task-context.ts +1 -1
  494. package/dist/builtin/workflows/src/runs/foreground/executor-task-prompts.ts +148 -153
  495. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +157 -110
  496. package/dist/builtin/workflows/src/runs/foreground/executor.ts +10 -10
  497. package/dist/builtin/workflows/src/runs/foreground/postmortem-stage-chat.ts +176 -100
  498. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +363 -257
  499. package/dist/builtin/workflows/src/runs/foreground/stage-delivery-activity.ts +86 -0
  500. package/dist/builtin/workflows/src/runs/foreground/stage-queued-user-messages.ts +95 -0
  501. package/dist/builtin/workflows/src/runs/foreground/stage-runner-candidate.ts +8 -8
  502. package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +233 -221
  503. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +630 -494
  504. package/dist/builtin/workflows/src/runs/foreground/stage-runner-message-admission.ts +182 -181
  505. package/dist/builtin/workflows/src/runs/foreground/stage-runner-messages.ts +80 -80
  506. package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +78 -74
  507. package/dist/builtin/workflows/src/runs/foreground/stage-runner-output.ts +102 -104
  508. package/dist/builtin/workflows/src/runs/foreground/stage-runner-pause.ts +200 -0
  509. package/dist/builtin/workflows/src/runs/foreground/stage-runner-replacement.ts +31 -32
  510. package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +115 -98
  511. package/dist/builtin/workflows/src/runs/foreground/stage-runner-session-options.ts +29 -42
  512. package/dist/builtin/workflows/src/runs/foreground/stage-runner-session.ts +44 -52
  513. package/dist/builtin/workflows/src/runs/foreground/stage-runner-structured-output.ts +63 -58
  514. package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +161 -124
  515. package/dist/builtin/workflows/src/runs/foreground/stage-runner-unresolved-overflow.ts +27 -23
  516. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +10 -10
  517. package/dist/builtin/workflows/src/runs/foreground/stage-tool-execution-buffer.ts +33 -31
  518. package/dist/builtin/workflows/src/runs/shared/concurrency.ts +53 -53
  519. package/dist/builtin/workflows/src/runs/shared/graph-inference.ts +1 -1
  520. package/dist/builtin/workflows/src/runs/shared/model-fallback-candidates.ts +257 -385
  521. package/dist/builtin/workflows/src/runs/shared/model-fallback-failures.ts +378 -363
  522. package/dist/builtin/workflows/src/runs/shared/prompt-callsite.ts +55 -55
  523. package/dist/builtin/workflows/src/runs/shared/validate-inputs.ts +75 -84
  524. package/dist/builtin/workflows/src/runs/shared/worktree-cache-lifecycle.ts +1 -3
  525. package/dist/builtin/workflows/src/runs/shared/worktree-cwd.ts +96 -83
  526. package/dist/builtin/workflows/src/runs/shared/worktree-diff.ts +9 -2
  527. package/dist/builtin/workflows/src/runs/shared/worktree-git-runner.ts +21 -6
  528. package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +60 -30
  529. package/dist/builtin/workflows/src/runs/shared/worktree-post-create.ts +12 -5
  530. package/dist/builtin/workflows/src/runs/shared/worktree-setup.ts +51 -30
  531. package/dist/builtin/workflows/src/runs/shared/worktree.ts +4 -4
  532. package/dist/builtin/workflows/src/sdk-surface.ts +38 -19
  533. package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +17 -16
  534. package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +299 -280
  535. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +76 -10
  536. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +311 -219
  537. package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +255 -156
  538. package/dist/builtin/workflows/src/shared/intercom-group.ts +33 -26
  539. package/dist/builtin/workflows/src/shared/persistence-compaction-policy.ts +30 -30
  540. package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +435 -379
  541. package/dist/builtin/workflows/src/shared/persistence-restore.ts +187 -181
  542. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +211 -207
  543. package/dist/builtin/workflows/src/shared/prompt-callsite-context.ts +3 -3
  544. package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +87 -94
  545. package/dist/builtin/workflows/src/shared/resume-continuation.ts +1 -1
  546. package/dist/builtin/workflows/src/shared/returned-run-status.ts +91 -77
  547. package/dist/builtin/workflows/src/shared/run-visibility.ts +2 -2
  548. package/dist/builtin/workflows/src/shared/schema-introspection.ts +50 -60
  549. package/dist/builtin/workflows/src/shared/serializable.ts +72 -86
  550. package/dist/builtin/workflows/src/shared/session-transcript.ts +44 -39
  551. package/dist/builtin/workflows/src/shared/stage-prompt.ts +183 -202
  552. package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +270 -274
  553. package/dist/builtin/workflows/src/shared/store-factory.ts +9 -7
  554. package/dist/builtin/workflows/src/shared/store-internal.ts +172 -168
  555. package/dist/builtin/workflows/src/shared/store-prompt-methods.ts +196 -200
  556. package/dist/builtin/workflows/src/shared/store-public-types.ts +208 -196
  557. package/dist/builtin/workflows/src/shared/store-run-methods.ts +238 -222
  558. package/dist/builtin/workflows/src/shared/store-stage-methods.ts +284 -283
  559. package/dist/builtin/workflows/src/shared/store-tool-node-methods.ts +53 -0
  560. package/dist/builtin/workflows/src/shared/store-types.ts +269 -235
  561. package/dist/builtin/workflows/src/shared/store.ts +7 -7
  562. package/dist/builtin/workflows/src/shared/timing.ts +36 -34
  563. package/dist/builtin/workflows/src/shared/types.ts +257 -217
  564. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +11 -4
  565. package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +81 -58
  566. package/dist/builtin/workflows/src/shared/workflow-failures-classifier.ts +247 -221
  567. package/dist/builtin/workflows/src/shared/workflow-failures-contract.ts +45 -46
  568. package/dist/builtin/workflows/src/shared/workflow-failures-decisions.ts +330 -351
  569. package/dist/builtin/workflows/src/shared/workflow-failures-signals.ts +212 -157
  570. package/dist/builtin/workflows/src/shared/workflow-failures.ts +10 -10
  571. package/dist/builtin/workflows/src/shared/workflow-run-ownership.ts +49 -0
  572. package/dist/builtin/workflows/src/tui/chat-surface-message.ts +157 -184
  573. package/dist/builtin/workflows/src/tui/chat-surface.ts +332 -355
  574. package/dist/builtin/workflows/src/tui/color-utils.ts +21 -30
  575. package/dist/builtin/workflows/src/tui/connectors.ts +57 -61
  576. package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +168 -192
  577. package/dist/builtin/workflows/src/tui/edge.ts +10 -9
  578. package/dist/builtin/workflows/src/tui/graph-canvas.ts +131 -134
  579. package/dist/builtin/workflows/src/tui/graph-theme.ts +139 -148
  580. package/dist/builtin/workflows/src/tui/graph-view-constants.ts +6 -6
  581. package/dist/builtin/workflows/src/tui/graph-view-graph-render.ts +146 -179
  582. package/dist/builtin/workflows/src/tui/graph-view-input.ts +295 -329
  583. package/dist/builtin/workflows/src/tui/graph-view-render-helpers.ts +291 -320
  584. package/dist/builtin/workflows/src/tui/graph-view-render.ts +171 -190
  585. package/dist/builtin/workflows/src/tui/graph-view-state.ts +319 -287
  586. package/dist/builtin/workflows/src/tui/graph-view-types.ts +65 -58
  587. package/dist/builtin/workflows/src/tui/header.ts +140 -156
  588. package/dist/builtin/workflows/src/tui/host-input-form.ts +47 -45
  589. package/dist/builtin/workflows/src/tui/inline-form-card.ts +277 -266
  590. package/dist/builtin/workflows/src/tui/inline-form-editor-text.ts +61 -62
  591. package/dist/builtin/workflows/src/tui/inline-form-editor.ts +407 -412
  592. package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +203 -214
  593. package/dist/builtin/workflows/src/tui/inline-form-store.ts +24 -24
  594. package/dist/builtin/workflows/src/tui/inputs-overlay.ts +116 -129
  595. package/dist/builtin/workflows/src/tui/inputs-picker-editing.ts +111 -115
  596. package/dist/builtin/workflows/src/tui/inputs-picker-input.ts +255 -269
  597. package/dist/builtin/workflows/src/tui/inputs-picker-render.ts +248 -236
  598. package/dist/builtin/workflows/src/tui/inputs-picker-types.ts +111 -140
  599. package/dist/builtin/workflows/src/tui/inputs-picker.ts +12 -12
  600. package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +49 -57
  601. package/dist/builtin/workflows/src/tui/layout.ts +124 -133
  602. package/dist/builtin/workflows/src/tui/mouse-input.ts +45 -47
  603. package/dist/builtin/workflows/src/tui/node-card.ts +230 -218
  604. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +381 -398
  605. package/dist/builtin/workflows/src/tui/overlay-terminal-modes.ts +20 -19
  606. package/dist/builtin/workflows/src/tui/prompt-card-input.ts +183 -194
  607. package/dist/builtin/workflows/src/tui/prompt-card-render.ts +243 -302
  608. package/dist/builtin/workflows/src/tui/prompt-card-select.ts +76 -98
  609. package/dist/builtin/workflows/src/tui/prompt-card-state.ts +40 -40
  610. package/dist/builtin/workflows/src/tui/prompt-card-text.ts +76 -83
  611. package/dist/builtin/workflows/src/tui/prompt-card.ts +3 -3
  612. package/dist/builtin/workflows/src/tui/renderers.ts +6 -6
  613. package/dist/builtin/workflows/src/tui/run-detail.ts +300 -256
  614. package/dist/builtin/workflows/src/tui/session-list.ts +19 -18
  615. package/dist/builtin/workflows/src/tui/session-overlays.ts +82 -87
  616. package/dist/builtin/workflows/src/tui/session-picker.ts +258 -275
  617. package/dist/builtin/workflows/src/tui/stage-chat-composer-drafts.ts +11 -11
  618. package/dist/builtin/workflows/src/tui/stage-chat-layout.ts +55 -62
  619. package/dist/builtin/workflows/src/tui/stage-chat-view-archive-history.ts +220 -263
  620. package/dist/builtin/workflows/src/tui/stage-chat-view-custom-ui.ts +91 -102
  621. package/dist/builtin/workflows/src/tui/stage-chat-view-delivery-activity.ts +113 -0
  622. package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +154 -208
  623. package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +174 -208
  624. package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +53 -23
  625. package/dist/builtin/workflows/src/tui/stage-chat-view-pending-tools.ts +2 -4
  626. package/dist/builtin/workflows/src/tui/stage-chat-view-render-helpers.ts +85 -88
  627. package/dist/builtin/workflows/src/tui/stage-chat-view-render-settings.ts +39 -17
  628. package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +435 -409
  629. package/dist/builtin/workflows/src/tui/stage-chat-view-status.ts +24 -25
  630. package/dist/builtin/workflows/src/tui/stage-chat-view-transcript.ts +181 -197
  631. package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +125 -128
  632. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +210 -205
  633. package/dist/builtin/workflows/src/tui/status-helpers.ts +65 -69
  634. package/dist/builtin/workflows/src/tui/status-list.ts +316 -294
  635. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +213 -214
  636. package/dist/builtin/workflows/src/tui/submit-pane.ts +114 -130
  637. package/dist/builtin/workflows/src/tui/switcher.ts +120 -128
  638. package/dist/builtin/workflows/src/tui/text-helpers.ts +136 -145
  639. package/dist/builtin/workflows/src/tui/toast.ts +68 -67
  640. package/dist/builtin/workflows/src/tui/widget.ts +265 -285
  641. package/dist/builtin/workflows/src/tui/workflow-attach-pane-handle.ts +12 -17
  642. package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +81 -91
  643. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +468 -479
  644. package/dist/builtin/workflows/src/tui/workflow-list.ts +130 -145
  645. package/dist/builtin/workflows/src/tui/workflow-notice-card.ts +127 -144
  646. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +217 -217
  647. package/dist/builtin/workflows/src/tui/workflow-status.ts +7 -0
  648. package/dist/builtin/workflows/src/workflows/identity.ts +12 -12
  649. package/dist/builtin/workflows/src/workflows/registry.ts +66 -68
  650. package/dist/bun/cli.js.map +1 -1
  651. package/dist/bun/internal-intercom-broker.d.ts.map +1 -1
  652. package/dist/bun/internal-intercom-broker.js +1 -1
  653. package/dist/bun/internal-intercom-broker.js.map +1 -1
  654. package/dist/bun/register-bedrock.js +1 -1
  655. package/dist/bun/register-bedrock.js.map +1 -1
  656. package/dist/cli/args.d.ts +8 -1
  657. package/dist/cli/args.d.ts.map +1 -1
  658. package/dist/cli/args.js +28 -22
  659. package/dist/cli/args.js.map +1 -1
  660. package/dist/cli/config-selector.d.ts +4 -2
  661. package/dist/cli/config-selector.d.ts.map +1 -1
  662. package/dist/cli/config-selector.js +2 -2
  663. package/dist/cli/config-selector.js.map +1 -1
  664. package/dist/cli/credential-print.d.ts +170 -0
  665. package/dist/cli/credential-print.d.ts.map +1 -0
  666. package/dist/cli/credential-print.js +513 -0
  667. package/dist/cli/credential-print.js.map +1 -0
  668. package/dist/cli/list-models.d.ts +2 -2
  669. package/dist/cli/list-models.d.ts.map +1 -1
  670. package/dist/cli/list-models.js +6 -7
  671. package/dist/cli/list-models.js.map +1 -1
  672. package/dist/cli/project-trust.d.ts +1 -1
  673. package/dist/cli/project-trust.d.ts.map +1 -1
  674. package/dist/cli/project-trust.js.map +1 -1
  675. package/dist/cli/session-picker.d.ts.map +1 -1
  676. package/dist/cli/session-picker.js +2 -1
  677. package/dist/cli/session-picker.js.map +1 -1
  678. package/dist/cli/startup-ui.d.ts +3 -0
  679. package/dist/cli/startup-ui.d.ts.map +1 -1
  680. package/dist/cli/startup-ui.js +58 -2
  681. package/dist/cli/startup-ui.js.map +1 -1
  682. package/dist/cli.js +2 -0
  683. package/dist/cli.js.map +1 -1
  684. package/dist/config-command-parser.d.ts +9 -0
  685. package/dist/config-command-parser.d.ts.map +1 -0
  686. package/dist/config-command-parser.js +22 -0
  687. package/dist/config-command-parser.js.map +1 -0
  688. package/dist/config-self-update.d.ts +6 -2
  689. package/dist/config-self-update.d.ts.map +1 -1
  690. package/dist/config-self-update.js +23 -13
  691. package/dist/config-self-update.js.map +1 -1
  692. package/dist/config.d.ts +5 -3
  693. package/dist/config.d.ts.map +1 -1
  694. package/dist/config.js +36 -14
  695. package/dist/config.js.map +1 -1
  696. package/dist/core/agent-session-accessors.d.ts.map +1 -1
  697. package/dist/core/agent-session-accessors.js +125 -24
  698. package/dist/core/agent-session-accessors.js.map +1 -1
  699. package/dist/core/agent-session-auto-compaction.d.ts +4 -3
  700. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  701. package/dist/core/agent-session-auto-compaction.js +59 -64
  702. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  703. package/dist/core/agent-session-bash.d.ts +10 -7
  704. package/dist/core/agent-session-bash.d.ts.map +1 -1
  705. package/dist/core/agent-session-bash.js +34 -13
  706. package/dist/core/agent-session-bash.js.map +1 -1
  707. package/dist/core/agent-session-compaction.d.ts +11 -1
  708. package/dist/core/agent-session-compaction.d.ts.map +1 -1
  709. package/dist/core/agent-session-compaction.js +138 -27
  710. package/dist/core/agent-session-compaction.js.map +1 -1
  711. package/dist/core/agent-session-custom-message-commit.d.ts +14 -0
  712. package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -0
  713. package/dist/core/agent-session-custom-message-commit.js +145 -0
  714. package/dist/core/agent-session-custom-message-commit.js.map +1 -0
  715. package/dist/core/agent-session-delivery-forwarding.d.ts +9 -0
  716. package/dist/core/agent-session-delivery-forwarding.d.ts.map +1 -0
  717. package/dist/core/agent-session-delivery-forwarding.js +36 -0
  718. package/dist/core/agent-session-delivery-forwarding.js.map +1 -0
  719. package/dist/core/agent-session-events.d.ts +1 -1
  720. package/dist/core/agent-session-events.d.ts.map +1 -1
  721. package/dist/core/agent-session-events.js +58 -19
  722. package/dist/core/agent-session-events.js.map +1 -1
  723. package/dist/core/agent-session-export.d.ts +1 -1
  724. package/dist/core/agent-session-export.d.ts.map +1 -1
  725. package/dist/core/agent-session-export.js +32 -25
  726. package/dist/core/agent-session-export.js.map +1 -1
  727. package/dist/core/agent-session-extension-bindings.d.ts +2 -6
  728. package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
  729. package/dist/core/agent-session-extension-bindings.js +38 -18
  730. package/dist/core/agent-session-extension-bindings.js.map +1 -1
  731. package/dist/core/agent-session-message-queue.d.ts +7 -4
  732. package/dist/core/agent-session-message-queue.d.ts.map +1 -1
  733. package/dist/core/agent-session-message-queue.js +101 -98
  734. package/dist/core/agent-session-message-queue.js.map +1 -1
  735. package/dist/core/agent-session-methods.d.ts +52 -45
  736. package/dist/core/agent-session-methods.d.ts.map +1 -1
  737. package/dist/core/agent-session-methods.js.map +1 -1
  738. package/dist/core/agent-session-models.d.ts +3 -39
  739. package/dist/core/agent-session-models.d.ts.map +1 -1
  740. package/dist/core/agent-session-models.js +22 -160
  741. package/dist/core/agent-session-models.js.map +1 -1
  742. package/dist/core/agent-session-persistent-custom-messages.d.ts +47 -0
  743. package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -0
  744. package/dist/core/agent-session-persistent-custom-messages.js +269 -0
  745. package/dist/core/agent-session-persistent-custom-messages.js.map +1 -0
  746. package/dist/core/agent-session-post-tool-compaction.d.ts +1 -1
  747. package/dist/core/agent-session-post-tool-compaction.d.ts.map +1 -1
  748. package/dist/core/agent-session-post-tool-compaction.js +34 -11
  749. package/dist/core/agent-session-post-tool-compaction.js.map +1 -1
  750. package/dist/core/agent-session-prompt.d.ts +2 -0
  751. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  752. package/dist/core/agent-session-prompt.js +52 -25
  753. package/dist/core/agent-session-prompt.js.map +1 -1
  754. package/dist/core/agent-session-queue-pause.d.ts +10 -0
  755. package/dist/core/agent-session-queue-pause.d.ts.map +1 -0
  756. package/dist/core/agent-session-queue-pause.js +87 -0
  757. package/dist/core/agent-session-queue-pause.js.map +1 -0
  758. package/dist/core/agent-session-retry.d.ts +0 -11
  759. package/dist/core/agent-session-retry.d.ts.map +1 -1
  760. package/dist/core/agent-session-retry.js +44 -63
  761. package/dist/core/agent-session-retry.js.map +1 -1
  762. package/dist/core/agent-session-runtime-auth.d.ts +6 -0
  763. package/dist/core/agent-session-runtime-auth.d.ts.map +1 -0
  764. package/dist/core/agent-session-runtime-auth.js +18 -0
  765. package/dist/core/agent-session-runtime-auth.js.map +1 -0
  766. package/dist/core/agent-session-runtime.d.ts +36 -1
  767. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  768. package/dist/core/agent-session-runtime.js +62 -6
  769. package/dist/core/agent-session-runtime.js.map +1 -1
  770. package/dist/core/agent-session-services.d.ts +4 -9
  771. package/dist/core/agent-session-services.d.ts.map +1 -1
  772. package/dist/core/agent-session-services.js +25 -25
  773. package/dist/core/agent-session-services.js.map +1 -1
  774. package/dist/core/agent-session-state.d.ts +1 -1
  775. package/dist/core/agent-session-state.d.ts.map +1 -1
  776. package/dist/core/agent-session-state.js +3 -0
  777. package/dist/core/agent-session-state.js.map +1 -1
  778. package/dist/core/agent-session-tool-hooks.d.ts.map +1 -1
  779. package/dist/core/agent-session-tool-hooks.js +8 -7
  780. package/dist/core/agent-session-tool-hooks.js.map +1 -1
  781. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  782. package/dist/core/agent-session-tool-registry.js +6 -20
  783. package/dist/core/agent-session-tool-registry.js.map +1 -1
  784. package/dist/core/agent-session-transfer.d.ts +4 -0
  785. package/dist/core/agent-session-transfer.d.ts.map +1 -0
  786. package/dist/core/agent-session-transfer.js +67 -0
  787. package/dist/core/agent-session-transfer.js.map +1 -0
  788. package/dist/core/agent-session-tree.d.ts +1 -1
  789. package/dist/core/agent-session-tree.d.ts.map +1 -1
  790. package/dist/core/agent-session-tree.js +13 -3
  791. package/dist/core/agent-session-tree.js.map +1 -1
  792. package/dist/core/agent-session-types.d.ts +28 -14
  793. package/dist/core/agent-session-types.d.ts.map +1 -1
  794. package/dist/core/agent-session-types.js +4 -4
  795. package/dist/core/agent-session-types.js.map +1 -1
  796. package/dist/core/agent-session.d.ts +27 -13
  797. package/dist/core/agent-session.d.ts.map +1 -1
  798. package/dist/core/agent-session.js +17 -12
  799. package/dist/core/agent-session.js.map +1 -1
  800. package/dist/core/anthropic-thinking-guard.d.ts.map +1 -1
  801. package/dist/core/anthropic-thinking-guard.js +4 -2
  802. package/dist/core/anthropic-thinking-guard.js.map +1 -1
  803. package/dist/core/async/format.d.ts +1 -1
  804. package/dist/core/async/format.d.ts.map +1 -1
  805. package/dist/core/async/format.js +3 -1
  806. package/dist/core/async/format.js.map +1 -1
  807. package/dist/core/async/job-manager.d.ts.map +1 -1
  808. package/dist/core/async/job-manager.js +20 -5
  809. package/dist/core/async/job-manager.js.map +1 -1
  810. package/dist/core/async/session-manager.d.ts.map +1 -1
  811. package/dist/core/async/session-manager.js.map +1 -1
  812. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  813. package/dist/core/atomic-guide-command.js +34 -35
  814. package/dist/core/atomic-guide-command.js.map +1 -1
  815. package/dist/core/auth-guidance.d.ts.map +1 -1
  816. package/dist/core/auth-guidance.js.map +1 -1
  817. package/dist/core/auth-storage-backends.d.ts +5 -0
  818. package/dist/core/auth-storage-backends.d.ts.map +1 -1
  819. package/dist/core/auth-storage-backends.js +77 -7
  820. package/dist/core/auth-storage-backends.js.map +1 -1
  821. package/dist/core/auth-storage.d.ts +13 -136
  822. package/dist/core/auth-storage.d.ts.map +1 -1
  823. package/dist/core/auth-storage.js +68 -358
  824. package/dist/core/auth-storage.js.map +1 -1
  825. package/dist/core/bash-executor.d.ts +5 -3
  826. package/dist/core/bash-executor.d.ts.map +1 -1
  827. package/dist/core/bash-executor.js +5 -4
  828. package/dist/core/bash-executor.js.map +1 -1
  829. package/dist/core/builtin-packages.d.ts.map +1 -1
  830. package/dist/core/builtin-packages.js +1 -10
  831. package/dist/core/builtin-packages.js.map +1 -1
  832. package/dist/core/cache-stats.d.ts +25 -0
  833. package/dist/core/cache-stats.d.ts.map +1 -0
  834. package/dist/core/cache-stats.js +70 -0
  835. package/dist/core/cache-stats.js.map +1 -0
  836. package/dist/core/callback-activity.d.ts.map +1 -1
  837. package/dist/core/callback-activity.js.map +1 -1
  838. package/dist/core/codex-errors.d.ts +3 -0
  839. package/dist/core/codex-errors.d.ts.map +1 -0
  840. package/dist/core/codex-errors.js +12 -0
  841. package/dist/core/codex-errors.js.map +1 -0
  842. package/dist/core/codex-fast-mode.d.ts.map +1 -1
  843. package/dist/core/codex-fast-mode.js.map +1 -1
  844. package/dist/core/compaction/branch-summarization.d.ts +15 -5
  845. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  846. package/dist/core/compaction/branch-summarization.js +23 -12
  847. package/dist/core/compaction/branch-summarization.js.map +1 -1
  848. package/dist/core/compaction/compaction-boundary.d.ts +18 -1
  849. package/dist/core/compaction/compaction-boundary.d.ts.map +1 -1
  850. package/dist/core/compaction/compaction-boundary.js +12 -8
  851. package/dist/core/compaction/compaction-boundary.js.map +1 -1
  852. package/dist/core/compaction/compaction-runner.d.ts +63 -6
  853. package/dist/core/compaction/compaction-runner.d.ts.map +1 -1
  854. package/dist/core/compaction/compaction-runner.js +244 -17
  855. package/dist/core/compaction/compaction-runner.js.map +1 -1
  856. package/dist/core/compaction/compaction-types.d.ts +53 -2
  857. package/dist/core/compaction/compaction-types.d.ts.map +1 -1
  858. package/dist/core/compaction/compaction-types.js.map +1 -1
  859. package/dist/core/compaction/compaction.d.ts.map +1 -1
  860. package/dist/core/compaction/compaction.js +5 -1
  861. package/dist/core/compaction/compaction.js.map +1 -1
  862. package/dist/core/compaction/deleted-ranges.d.ts.map +1 -1
  863. package/dist/core/compaction/deleted-ranges.js.map +1 -1
  864. package/dist/core/compaction/fallback-planner.d.ts +64 -0
  865. package/dist/core/compaction/fallback-planner.d.ts.map +1 -0
  866. package/dist/core/compaction/fallback-planner.js +75 -0
  867. package/dist/core/compaction/fallback-planner.js.map +1 -0
  868. package/dist/core/compaction/index.d.ts +7 -4
  869. package/dist/core/compaction/index.d.ts.map +1 -1
  870. package/dist/core/compaction/index.js +7 -4
  871. package/dist/core/compaction/index.js.map +1 -1
  872. package/dist/core/compaction/planner-outcome.d.ts +86 -0
  873. package/dist/core/compaction/planner-outcome.d.ts.map +1 -0
  874. package/dist/core/compaction/planner-outcome.js +122 -0
  875. package/dist/core/compaction/planner-outcome.js.map +1 -0
  876. package/dist/core/compaction/range-planner-diagnostics.d.ts +58 -6
  877. package/dist/core/compaction/range-planner-diagnostics.d.ts.map +1 -1
  878. package/dist/core/compaction/range-planner-diagnostics.js +71 -31
  879. package/dist/core/compaction/range-planner-diagnostics.js.map +1 -1
  880. package/dist/core/compaction/range-planner.d.ts +39 -8
  881. package/dist/core/compaction/range-planner.d.ts.map +1 -1
  882. package/dist/core/compaction/range-planner.js +140 -51
  883. package/dist/core/compaction/range-planner.js.map +1 -1
  884. package/dist/core/compaction/region-trimming.d.ts +34 -0
  885. package/dist/core/compaction/region-trimming.d.ts.map +1 -0
  886. package/dist/core/compaction/region-trimming.js +73 -0
  887. package/dist/core/compaction/region-trimming.js.map +1 -0
  888. package/dist/core/compaction/transcript-serialization.d.ts +8 -1
  889. package/dist/core/compaction/transcript-serialization.d.ts.map +1 -1
  890. package/dist/core/compaction/transcript-serialization.js +78 -27
  891. package/dist/core/compaction/transcript-serialization.js.map +1 -1
  892. package/dist/core/context-tool-pairing.d.ts +19 -0
  893. package/dist/core/context-tool-pairing.d.ts.map +1 -0
  894. package/dist/core/context-tool-pairing.js +50 -0
  895. package/dist/core/context-tool-pairing.js.map +1 -0
  896. package/dist/core/copilot-env-routing.d.ts +36 -0
  897. package/dist/core/copilot-env-routing.d.ts.map +1 -0
  898. package/dist/core/copilot-env-routing.js +94 -0
  899. package/dist/core/copilot-env-routing.js.map +1 -0
  900. package/dist/core/diagnostics.d.ts +8 -0
  901. package/dist/core/diagnostics.d.ts.map +1 -1
  902. package/dist/core/diagnostics.js.map +1 -1
  903. package/dist/core/exec.d.ts.map +1 -1
  904. package/dist/core/exec.js +5 -0
  905. package/dist/core/exec.js.map +1 -1
  906. package/dist/core/experimental.d.ts.map +1 -1
  907. package/dist/core/experimental.js +2 -1
  908. package/dist/core/experimental.js.map +1 -1
  909. package/dist/core/extensions/agent-events.d.ts +10 -0
  910. package/dist/core/extensions/agent-events.d.ts.map +1 -1
  911. package/dist/core/extensions/agent-events.js.map +1 -1
  912. package/dist/core/extensions/api-types.d.ts +9 -3
  913. package/dist/core/extensions/api-types.d.ts.map +1 -1
  914. package/dist/core/extensions/api-types.js.map +1 -1
  915. package/dist/core/extensions/context-types.d.ts +10 -1
  916. package/dist/core/extensions/context-types.d.ts.map +1 -1
  917. package/dist/core/extensions/context-types.js.map +1 -1
  918. package/dist/core/extensions/event-types.d.ts +2 -2
  919. package/dist/core/extensions/event-types.d.ts.map +1 -1
  920. package/dist/core/extensions/event-types.js.map +1 -1
  921. package/dist/core/extensions/index.d.ts +7 -5
  922. package/dist/core/extensions/index.d.ts.map +1 -1
  923. package/dist/core/extensions/index.js +2 -2
  924. package/dist/core/extensions/index.js.map +1 -1
  925. package/dist/core/extensions/loader-api.d.ts +1 -1
  926. package/dist/core/extensions/loader-api.d.ts.map +1 -1
  927. package/dist/core/extensions/loader-api.js +63 -29
  928. package/dist/core/extensions/loader-api.js.map +1 -1
  929. package/dist/core/extensions/loader-core.d.ts.map +1 -1
  930. package/dist/core/extensions/loader-core.js +3 -2
  931. package/dist/core/extensions/loader-core.js.map +1 -1
  932. package/dist/core/extensions/loader-discovery.d.ts.map +1 -1
  933. package/dist/core/extensions/loader-discovery.js.map +1 -1
  934. package/dist/core/extensions/loader-resources.d.ts.map +1 -1
  935. package/dist/core/extensions/loader-resources.js.map +1 -1
  936. package/dist/core/extensions/loader-runtime.d.ts +2 -4
  937. package/dist/core/extensions/loader-runtime.d.ts.map +1 -1
  938. package/dist/core/extensions/loader-runtime.js +214 -13
  939. package/dist/core/extensions/loader-runtime.js.map +1 -1
  940. package/dist/core/extensions/loader-virtual-modules.d.ts +17 -0
  941. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  942. package/dist/core/extensions/loader-virtual-modules.js +288 -19
  943. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  944. package/dist/core/extensions/loader.d.ts.map +1 -1
  945. package/dist/core/extensions/loader.js.map +1 -1
  946. package/dist/core/extensions/message-types.d.ts +12 -4
  947. package/dist/core/extensions/message-types.d.ts.map +1 -1
  948. package/dist/core/extensions/message-types.js.map +1 -1
  949. package/dist/core/extensions/provider-types.d.ts +16 -5
  950. package/dist/core/extensions/provider-types.d.ts.map +1 -1
  951. package/dist/core/extensions/provider-types.js.map +1 -1
  952. package/dist/core/extensions/reactive-widget.d.ts.map +1 -1
  953. package/dist/core/extensions/reactive-widget.js.map +1 -1
  954. package/dist/core/extensions/runner-context.d.ts +29 -1
  955. package/dist/core/extensions/runner-context.d.ts.map +1 -1
  956. package/dist/core/extensions/runner-context.js +56 -0
  957. package/dist/core/extensions/runner-context.js.map +1 -1
  958. package/dist/core/extensions/runner-events.d.ts.map +1 -1
  959. package/dist/core/extensions/runner-events.js.map +1 -1
  960. package/dist/core/extensions/runner-registries.d.ts +2 -1
  961. package/dist/core/extensions/runner-registries.d.ts.map +1 -1
  962. package/dist/core/extensions/runner-registries.js +8 -0
  963. package/dist/core/extensions/runner-registries.js.map +1 -1
  964. package/dist/core/extensions/runner.d.ts +7 -2
  965. package/dist/core/extensions/runner.d.ts.map +1 -1
  966. package/dist/core/extensions/runner.js +65 -23
  967. package/dist/core/extensions/runner.js.map +1 -1
  968. package/dist/core/extensions/runtime-types.d.ts +35 -2
  969. package/dist/core/extensions/runtime-types.d.ts.map +1 -1
  970. package/dist/core/extensions/runtime-types.js.map +1 -1
  971. package/dist/core/extensions/session-events.d.ts.map +1 -1
  972. package/dist/core/extensions/session-events.js.map +1 -1
  973. package/dist/core/extensions/tool-types.d.ts +7 -3
  974. package/dist/core/extensions/tool-types.d.ts.map +1 -1
  975. package/dist/core/extensions/tool-types.js.map +1 -1
  976. package/dist/core/extensions/types.d.ts +15 -2
  977. package/dist/core/extensions/types.d.ts.map +1 -1
  978. package/dist/core/extensions/types.js +1 -1
  979. package/dist/core/extensions/types.js.map +1 -1
  980. package/dist/core/extensions/ui-types.d.ts +34 -6
  981. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  982. package/dist/core/extensions/ui-types.js.map +1 -1
  983. package/dist/core/fallback-models.d.ts +37 -0
  984. package/dist/core/fallback-models.d.ts.map +1 -0
  985. package/dist/core/fallback-models.js +57 -0
  986. package/dist/core/fallback-models.js.map +1 -0
  987. package/dist/core/flattened-tool-arguments.d.ts +4 -7
  988. package/dist/core/flattened-tool-arguments.d.ts.map +1 -1
  989. package/dist/core/flattened-tool-arguments.js +4 -7
  990. package/dist/core/flattened-tool-arguments.js.map +1 -1
  991. package/dist/core/footer-data-provider.d.ts +10 -0
  992. package/dist/core/footer-data-provider.d.ts.map +1 -1
  993. package/dist/core/footer-data-provider.js +5 -3
  994. package/dist/core/footer-data-provider.js.map +1 -1
  995. package/dist/core/http-dispatcher.d.ts +1 -0
  996. package/dist/core/http-dispatcher.d.ts.map +1 -1
  997. package/dist/core/http-dispatcher.js +42 -8
  998. package/dist/core/http-dispatcher.js.map +1 -1
  999. package/dist/core/keybindings.d.ts +7 -2
  1000. package/dist/core/keybindings.d.ts.map +1 -1
  1001. package/dist/core/keybindings.js +3 -1
  1002. package/dist/core/keybindings.js.map +1 -1
  1003. package/dist/core/messages.d.ts +24 -4
  1004. package/dist/core/messages.d.ts.map +1 -1
  1005. package/dist/core/messages.js +64 -13
  1006. package/dist/core/messages.js.map +1 -1
  1007. package/dist/core/model-capabilities.d.ts +18 -0
  1008. package/dist/core/model-capabilities.d.ts.map +1 -0
  1009. package/dist/core/model-capabilities.js +22 -0
  1010. package/dist/core/model-capabilities.js.map +1 -0
  1011. package/dist/core/model-config.d.ts +542 -0
  1012. package/dist/core/model-config.d.ts.map +1 -0
  1013. package/dist/core/{model-registry-schemas.js → model-config.js} +92 -32
  1014. package/dist/core/model-config.js.map +1 -0
  1015. package/dist/core/model-refresh-timeout.d.ts +9 -0
  1016. package/dist/core/model-refresh-timeout.d.ts.map +1 -0
  1017. package/dist/core/model-refresh-timeout.js +9 -0
  1018. package/dist/core/model-refresh-timeout.js.map +1 -0
  1019. package/dist/core/model-registry.d.ts +26 -94
  1020. package/dist/core/model-registry.d.ts.map +1 -1
  1021. package/dist/core/model-registry.js +65 -382
  1022. package/dist/core/model-registry.js.map +1 -1
  1023. package/dist/core/model-resolver-cli.d.ts +2 -2
  1024. package/dist/core/model-resolver-cli.d.ts.map +1 -1
  1025. package/dist/core/model-resolver-cli.js +2 -2
  1026. package/dist/core/model-resolver-cli.js.map +1 -1
  1027. package/dist/core/model-resolver-defaults.d.ts.map +1 -1
  1028. package/dist/core/model-resolver-defaults.js +5 -1
  1029. package/dist/core/model-resolver-defaults.js.map +1 -1
  1030. package/dist/core/model-resolver-initial.d.ts +4 -4
  1031. package/dist/core/model-resolver-initial.d.ts.map +1 -1
  1032. package/dist/core/model-resolver-initial.js +26 -17
  1033. package/dist/core/model-resolver-initial.js.map +1 -1
  1034. package/dist/core/model-resolver-patterns.d.ts.map +1 -1
  1035. package/dist/core/model-resolver-patterns.js +0 -2
  1036. package/dist/core/model-resolver-patterns.js.map +1 -1
  1037. package/dist/core/model-resolver-scope.d.ts +5 -3
  1038. package/dist/core/model-resolver-scope.d.ts.map +1 -1
  1039. package/dist/core/model-resolver-scope.js +32 -8
  1040. package/dist/core/model-resolver-scope.js.map +1 -1
  1041. package/dist/core/model-resolver-types.d.ts +2 -0
  1042. package/dist/core/model-resolver-types.d.ts.map +1 -1
  1043. package/dist/core/model-resolver-types.js.map +1 -1
  1044. package/dist/core/model-resolver.d.ts +3 -3
  1045. package/dist/core/model-resolver.d.ts.map +1 -1
  1046. package/dist/core/model-resolver.js +1 -1
  1047. package/dist/core/model-resolver.js.map +1 -1
  1048. package/dist/core/model-runtime-auth.d.ts +7 -0
  1049. package/dist/core/model-runtime-auth.d.ts.map +1 -0
  1050. package/dist/core/model-runtime-auth.js +17 -0
  1051. package/dist/core/model-runtime-auth.js.map +1 -0
  1052. package/dist/core/model-runtime-providers.d.ts +6 -0
  1053. package/dist/core/model-runtime-providers.d.ts.map +1 -0
  1054. package/dist/core/model-runtime-providers.js +22 -0
  1055. package/dist/core/model-runtime-providers.js.map +1 -0
  1056. package/dist/core/model-runtime-restoration.d.ts +6 -0
  1057. package/dist/core/model-runtime-restoration.d.ts.map +1 -0
  1058. package/dist/core/model-runtime-restoration.js +21 -0
  1059. package/dist/core/model-runtime-restoration.js.map +1 -0
  1060. package/dist/core/model-runtime-snapshot.d.ts +19 -0
  1061. package/dist/core/model-runtime-snapshot.d.ts.map +1 -0
  1062. package/dist/core/model-runtime-snapshot.js +111 -0
  1063. package/dist/core/model-runtime-snapshot.js.map +1 -0
  1064. package/dist/core/model-runtime-streaming.d.ts +17 -0
  1065. package/dist/core/model-runtime-streaming.d.ts.map +1 -0
  1066. package/dist/core/model-runtime-streaming.js +66 -0
  1067. package/dist/core/model-runtime-streaming.js.map +1 -0
  1068. package/dist/core/model-runtime-types.d.ts +21 -0
  1069. package/dist/core/model-runtime-types.d.ts.map +1 -0
  1070. package/dist/core/model-runtime-types.js +2 -0
  1071. package/dist/core/model-runtime-types.js.map +1 -0
  1072. package/dist/core/model-runtime.d.ts +79 -0
  1073. package/dist/core/model-runtime.d.ts.map +1 -0
  1074. package/dist/core/model-runtime.js +417 -0
  1075. package/dist/core/model-runtime.js.map +1 -0
  1076. package/dist/core/models-store.d.ts +2 -10
  1077. package/dist/core/models-store.d.ts.map +1 -1
  1078. package/dist/core/models-store.js +2 -29
  1079. package/dist/core/models-store.js.map +1 -1
  1080. package/dist/core/oauth-login.d.ts +24 -0
  1081. package/dist/core/oauth-login.d.ts.map +1 -0
  1082. package/dist/core/oauth-login.js +92 -0
  1083. package/dist/core/oauth-login.js.map +1 -0
  1084. package/dist/core/oauth-provider-metadata.d.ts +5 -0
  1085. package/dist/core/oauth-provider-metadata.d.ts.map +1 -0
  1086. package/dist/core/oauth-provider-metadata.js +33 -0
  1087. package/dist/core/oauth-provider-metadata.js.map +1 -0
  1088. package/dist/core/openai-responses-payload-sanitizer.js.map +1 -1
  1089. package/dist/core/output-guard.d.ts +44 -0
  1090. package/dist/core/output-guard.d.ts.map +1 -1
  1091. package/dist/core/output-guard.js +63 -0
  1092. package/dist/core/output-guard.js.map +1 -1
  1093. package/dist/core/package-manager-auto-resources.d.ts.map +1 -1
  1094. package/dist/core/package-manager-auto-resources.js +18 -5
  1095. package/dist/core/package-manager-auto-resources.js.map +1 -1
  1096. package/dist/core/package-manager-git.d.ts.map +1 -1
  1097. package/dist/core/package-manager-git.js +27 -11
  1098. package/dist/core/package-manager-git.js.map +1 -1
  1099. package/dist/core/package-manager-npm.d.ts.map +1 -1
  1100. package/dist/core/package-manager-npm.js +4 -2
  1101. package/dist/core/package-manager-npm.js.map +1 -1
  1102. package/dist/core/package-manager-operations.d.ts.map +1 -1
  1103. package/dist/core/package-manager-operations.js +2 -2
  1104. package/dist/core/package-manager-operations.js.map +1 -1
  1105. package/dist/core/package-manager-paths.d.ts.map +1 -1
  1106. package/dist/core/package-manager-paths.js.map +1 -1
  1107. package/dist/core/package-manager-resolver.d.ts.map +1 -1
  1108. package/dist/core/package-manager-resolver.js +71 -26
  1109. package/dist/core/package-manager-resolver.js.map +1 -1
  1110. package/dist/core/package-manager-resource-collector.d.ts.map +1 -1
  1111. package/dist/core/package-manager-resource-collector.js +13 -7
  1112. package/dist/core/package-manager-resource-collector.js.map +1 -1
  1113. package/dist/core/package-manager-resource-files.d.ts.map +1 -1
  1114. package/dist/core/package-manager-resource-files.js +4 -3
  1115. package/dist/core/package-manager-resource-files.js.map +1 -1
  1116. package/dist/core/package-manager-resource-patterns.d.ts +1 -0
  1117. package/dist/core/package-manager-resource-patterns.d.ts.map +1 -1
  1118. package/dist/core/package-manager-resource-patterns.js +15 -0
  1119. package/dist/core/package-manager-resource-patterns.js.map +1 -1
  1120. package/dist/core/package-manager-settings.d.ts.map +1 -1
  1121. package/dist/core/package-manager-settings.js +3 -2
  1122. package/dist/core/package-manager-settings.js.map +1 -1
  1123. package/dist/core/package-manager-source.d.ts.map +1 -1
  1124. package/dist/core/package-manager-source.js +16 -9
  1125. package/dist/core/package-manager-source.js.map +1 -1
  1126. package/dist/core/package-manager-types.d.ts +3 -0
  1127. package/dist/core/package-manager-types.d.ts.map +1 -1
  1128. package/dist/core/package-manager-types.js.map +1 -1
  1129. package/dist/core/package-manager.d.ts +1 -1
  1130. package/dist/core/package-manager.d.ts.map +1 -1
  1131. package/dist/core/package-manager.js +2 -3
  1132. package/dist/core/package-manager.js.map +1 -1
  1133. package/dist/core/prompt-templates-async.d.ts.map +1 -1
  1134. package/dist/core/prompt-templates-async.js +15 -7
  1135. package/dist/core/prompt-templates-async.js.map +1 -1
  1136. package/dist/core/prompt-templates.d.ts +1 -0
  1137. package/dist/core/prompt-templates.d.ts.map +1 -1
  1138. package/dist/core/prompt-templates.js +4 -4
  1139. package/dist/core/prompt-templates.js.map +1 -1
  1140. package/dist/core/provider-composer-internal.d.ts +53 -0
  1141. package/dist/core/provider-composer-internal.d.ts.map +1 -0
  1142. package/dist/core/provider-composer-internal.js +273 -0
  1143. package/dist/core/provider-composer-internal.js.map +1 -0
  1144. package/dist/core/provider-composer.d.ts +16 -0
  1145. package/dist/core/provider-composer.d.ts.map +1 -0
  1146. package/dist/core/provider-composer.js +108 -0
  1147. package/dist/core/provider-composer.js.map +1 -0
  1148. package/dist/core/provider-context-usage.d.ts.map +1 -1
  1149. package/dist/core/provider-context-usage.js +2 -1
  1150. package/dist/core/provider-context-usage.js.map +1 -1
  1151. package/dist/core/provider-display-names.d.ts.map +1 -1
  1152. package/dist/core/provider-display-names.js +4 -0
  1153. package/dist/core/provider-display-names.js.map +1 -1
  1154. package/dist/core/remote-catalog-provider.d.ts +1 -1
  1155. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  1156. package/dist/core/remote-catalog-provider.js +82 -105
  1157. package/dist/core/remote-catalog-provider.js.map +1 -1
  1158. package/dist/core/resolve-config-value.d.ts +8 -7
  1159. package/dist/core/resolve-config-value.d.ts.map +1 -1
  1160. package/dist/core/resolve-config-value.js +26 -23
  1161. package/dist/core/resolve-config-value.js.map +1 -1
  1162. package/dist/core/resource-loader-assets.d.ts.map +1 -1
  1163. package/dist/core/resource-loader-assets.js +58 -8
  1164. package/dist/core/resource-loader-assets.js.map +1 -1
  1165. package/dist/core/resource-loader-context-files.d.ts +6 -0
  1166. package/dist/core/resource-loader-context-files.d.ts.map +1 -1
  1167. package/dist/core/resource-loader-context-files.js +85 -4
  1168. package/dist/core/resource-loader-context-files.js.map +1 -1
  1169. package/dist/core/resource-loader-core.d.ts +13 -3
  1170. package/dist/core/resource-loader-core.d.ts.map +1 -1
  1171. package/dist/core/resource-loader-core.js +39 -19
  1172. package/dist/core/resource-loader-core.js.map +1 -1
  1173. package/dist/core/resource-loader-discovery.d.ts.map +1 -1
  1174. package/dist/core/resource-loader-discovery.js.map +1 -1
  1175. package/dist/core/resource-loader-extensions.d.ts +1 -0
  1176. package/dist/core/resource-loader-extensions.d.ts.map +1 -1
  1177. package/dist/core/resource-loader-extensions.js +140 -12
  1178. package/dist/core/resource-loader-extensions.js.map +1 -1
  1179. package/dist/core/resource-loader-helpers.d.ts.map +1 -1
  1180. package/dist/core/resource-loader-helpers.js +1 -0
  1181. package/dist/core/resource-loader-helpers.js.map +1 -1
  1182. package/dist/core/resource-loader-internals.d.ts +14 -7
  1183. package/dist/core/resource-loader-internals.d.ts.map +1 -1
  1184. package/dist/core/resource-loader-internals.js.map +1 -1
  1185. package/dist/core/resource-loader-package-resources.d.ts.map +1 -1
  1186. package/dist/core/resource-loader-package-resources.js +15 -2
  1187. package/dist/core/resource-loader-package-resources.js.map +1 -1
  1188. package/dist/core/resource-loader-paths.js.map +1 -1
  1189. package/dist/core/resource-loader-reload.d.ts.map +1 -1
  1190. package/dist/core/resource-loader-reload.js +29 -13
  1191. package/dist/core/resource-loader-reload.js.map +1 -1
  1192. package/dist/core/resource-loader-source-info.d.ts +1 -1
  1193. package/dist/core/resource-loader-source-info.d.ts.map +1 -1
  1194. package/dist/core/resource-loader-source-info.js +7 -4
  1195. package/dist/core/resource-loader-source-info.js.map +1 -1
  1196. package/dist/core/resource-loader-types.d.ts +12 -6
  1197. package/dist/core/resource-loader-types.d.ts.map +1 -1
  1198. package/dist/core/resource-loader-types.js.map +1 -1
  1199. package/dist/core/resource-loader.d.ts +1 -1
  1200. package/dist/core/resource-loader.d.ts.map +1 -1
  1201. package/dist/core/resource-loader.js +1 -1
  1202. package/dist/core/resource-loader.js.map +1 -1
  1203. package/dist/core/runtime-credentials.d.ts +16 -0
  1204. package/dist/core/runtime-credentials.d.ts.map +1 -0
  1205. package/dist/core/runtime-credentials.js +42 -0
  1206. package/dist/core/runtime-credentials.js.map +1 -0
  1207. package/dist/core/sdk-exports.d.ts +2 -2
  1208. package/dist/core/sdk-exports.d.ts.map +1 -1
  1209. package/dist/core/sdk-exports.js +2 -2
  1210. package/dist/core/sdk-exports.js.map +1 -1
  1211. package/dist/core/sdk-types.d.ts +16 -18
  1212. package/dist/core/sdk-types.d.ts.map +1 -1
  1213. package/dist/core/sdk-types.js.map +1 -1
  1214. package/dist/core/sdk.d.ts +2 -1
  1215. package/dist/core/sdk.d.ts.map +1 -1
  1216. package/dist/core/sdk.js +61 -132
  1217. package/dist/core/sdk.js.map +1 -1
  1218. package/dist/core/session-manager-archive.d.ts.map +1 -1
  1219. package/dist/core/session-manager-archive.js +5 -2
  1220. package/dist/core/session-manager-archive.js.map +1 -1
  1221. package/dist/core/session-manager-classification.js +3 -3
  1222. package/dist/core/session-manager-classification.js.map +1 -1
  1223. package/dist/core/session-manager-core.d.ts +5 -4
  1224. package/dist/core/session-manager-core.d.ts.map +1 -1
  1225. package/dist/core/session-manager-core.js +29 -33
  1226. package/dist/core/session-manager-core.js.map +1 -1
  1227. package/dist/core/session-manager-entries.d.ts +5 -8
  1228. package/dist/core/session-manager-entries.d.ts.map +1 -1
  1229. package/dist/core/session-manager-entries.js +4 -9
  1230. package/dist/core/session-manager-entries.js.map +1 -1
  1231. package/dist/core/session-manager-history.d.ts +8 -3
  1232. package/dist/core/session-manager-history.d.ts.map +1 -1
  1233. package/dist/core/session-manager-history.js +90 -27
  1234. package/dist/core/session-manager-history.js.map +1 -1
  1235. package/dist/core/session-manager-list.d.ts.map +1 -1
  1236. package/dist/core/session-manager-list.js +1 -1
  1237. package/dist/core/session-manager-list.js.map +1 -1
  1238. package/dist/core/session-manager-migrations.js.map +1 -1
  1239. package/dist/core/session-manager-storage.d.ts +6 -0
  1240. package/dist/core/session-manager-storage.d.ts.map +1 -1
  1241. package/dist/core/session-manager-storage.js +38 -5
  1242. package/dist/core/session-manager-storage.js.map +1 -1
  1243. package/dist/core/session-manager-types.d.ts +10 -8
  1244. package/dist/core/session-manager-types.d.ts.map +1 -1
  1245. package/dist/core/session-manager-types.js.map +1 -1
  1246. package/dist/core/session-manager-validation.d.ts.map +1 -1
  1247. package/dist/core/session-manager-validation.js +3 -16
  1248. package/dist/core/session-manager-validation.js.map +1 -1
  1249. package/dist/core/session-manager.d.ts +4 -4
  1250. package/dist/core/session-manager.d.ts.map +1 -1
  1251. package/dist/core/session-manager.js +2 -2
  1252. package/dist/core/session-manager.js.map +1 -1
  1253. package/dist/core/settings-manager-basic-accessors.d.ts +7 -4
  1254. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  1255. package/dist/core/settings-manager-basic-accessors.js +35 -50
  1256. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  1257. package/dist/core/settings-manager-core.d.ts +2 -4
  1258. package/dist/core/settings-manager-core.d.ts.map +1 -1
  1259. package/dist/core/settings-manager-core.js +5 -51
  1260. package/dist/core/settings-manager-core.js.map +1 -1
  1261. package/dist/core/settings-manager-resource-accessors.d.ts +3 -0
  1262. package/dist/core/settings-manager-resource-accessors.d.ts.map +1 -1
  1263. package/dist/core/settings-manager-resource-accessors.js +16 -0
  1264. package/dist/core/settings-manager-resource-accessors.js.map +1 -1
  1265. package/dist/core/settings-manager-ui-accessors.js +2 -1
  1266. package/dist/core/settings-manager-ui-accessors.js.map +1 -1
  1267. package/dist/core/settings-manager.d.ts +1 -1
  1268. package/dist/core/settings-manager.d.ts.map +1 -1
  1269. package/dist/core/settings-manager.js.map +1 -1
  1270. package/dist/core/settings-storage.d.ts +3 -1
  1271. package/dist/core/settings-storage.d.ts.map +1 -1
  1272. package/dist/core/settings-storage.js +23 -1
  1273. package/dist/core/settings-storage.js.map +1 -1
  1274. package/dist/core/settings-types.d.ts +10 -6
  1275. package/dist/core/settings-types.d.ts.map +1 -1
  1276. package/dist/core/settings-types.js.map +1 -1
  1277. package/dist/core/skills-async.d.ts.map +1 -1
  1278. package/dist/core/skills-async.js +18 -8
  1279. package/dist/core/skills-async.js.map +1 -1
  1280. package/dist/core/slash-commands.d.ts +1 -0
  1281. package/dist/core/slash-commands.d.ts.map +1 -1
  1282. package/dist/core/slash-commands.js +127 -24
  1283. package/dist/core/slash-commands.js.map +1 -1
  1284. package/dist/core/source-info.d.ts +3 -1
  1285. package/dist/core/source-info.d.ts.map +1 -1
  1286. package/dist/core/source-info.js +2 -0
  1287. package/dist/core/source-info.js.map +1 -1
  1288. package/dist/core/summarization-retry.d.ts +11 -0
  1289. package/dist/core/summarization-retry.d.ts.map +1 -0
  1290. package/dist/core/summarization-retry.js +21 -0
  1291. package/dist/core/summarization-retry.js.map +1 -0
  1292. package/dist/core/system-prompt.d.ts.map +1 -1
  1293. package/dist/core/system-prompt.js +15 -18
  1294. package/dist/core/system-prompt.js.map +1 -1
  1295. package/dist/core/thinking-blocks.d.ts.map +1 -1
  1296. package/dist/core/thinking-blocks.js +2 -2
  1297. package/dist/core/thinking-blocks.js.map +1 -1
  1298. package/dist/core/tools/artifact-protocol.d.ts.map +1 -1
  1299. package/dist/core/tools/artifact-protocol.js.map +1 -1
  1300. package/dist/core/tools/artifacts.d.ts.map +1 -1
  1301. package/dist/core/tools/artifacts.js.map +1 -1
  1302. package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
  1303. package/dist/core/tools/ask-user-question/ask-user-question.js +1 -1
  1304. package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
  1305. package/dist/core/tools/ask-user-question/state/build-questionnaire.d.ts +1 -1
  1306. package/dist/core/tools/ask-user-question/state/build-questionnaire.d.ts.map +1 -1
  1307. package/dist/core/tools/ask-user-question/state/build-questionnaire.js +1 -1
  1308. package/dist/core/tools/ask-user-question/state/build-questionnaire.js.map +1 -1
  1309. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
  1310. package/dist/core/tools/ask-user-question/state/questionnaire-session.js +2 -4
  1311. package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
  1312. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
  1313. package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
  1314. package/dist/core/tools/ask-user-question/view/body-residual-spacer.d.ts.map +1 -1
  1315. package/dist/core/tools/ask-user-question/view/body-residual-spacer.js.map +1 -1
  1316. package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts.map +1 -1
  1317. package/dist/core/tools/ask-user-question/view/components/multi-select-view.js.map +1 -1
  1318. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.d.ts +1 -1
  1319. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.d.ts.map +1 -1
  1320. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.js.map +1 -1
  1321. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +1 -1
  1322. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
  1323. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js +1 -3
  1324. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
  1325. package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts.map +1 -1
  1326. package/dist/core/tools/ask-user-question/view/components/submit-picker.js.map +1 -1
  1327. package/dist/core/tools/ask-user-question/view/components/tab-bar.d.ts.map +1 -1
  1328. package/dist/core/tools/ask-user-question/view/components/tab-bar.js.map +1 -1
  1329. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +1 -1
  1330. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
  1331. package/dist/core/tools/ask-user-question/view/dialog-builder.js +1 -1
  1332. package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
  1333. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
  1334. package/dist/core/tools/ask-user-question/view/props-adapter.js +2 -4
  1335. package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
  1336. package/dist/core/tools/ask-user-question/view/tab-content-strategy.d.ts +1 -1
  1337. package/dist/core/tools/ask-user-question/view/tab-content-strategy.d.ts.map +1 -1
  1338. package/dist/core/tools/ask-user-question/view/tab-content-strategy.js +2 -9
  1339. package/dist/core/tools/ask-user-question/view/tab-content-strategy.js.map +1 -1
  1340. package/dist/core/tools/bash-async-execution.d.ts.map +1 -1
  1341. package/dist/core/tools/bash-async-execution.js +11 -4
  1342. package/dist/core/tools/bash-async-execution.js.map +1 -1
  1343. package/dist/core/tools/bash-async-jobs.d.ts.map +1 -1
  1344. package/dist/core/tools/bash-async-jobs.js +14 -2
  1345. package/dist/core/tools/bash-async-jobs.js.map +1 -1
  1346. package/dist/core/tools/bash-async-output.d.ts.map +1 -1
  1347. package/dist/core/tools/bash-async-output.js +6 -2
  1348. package/dist/core/tools/bash-async-output.js.map +1 -1
  1349. package/dist/core/tools/bash-interceptor.d.ts.map +1 -1
  1350. package/dist/core/tools/bash-interceptor.js +20 -4
  1351. package/dist/core/tools/bash-interceptor.js.map +1 -1
  1352. package/dist/core/tools/bash-leading-cd.d.ts.map +1 -1
  1353. package/dist/core/tools/bash-leading-cd.js +9 -3
  1354. package/dist/core/tools/bash-leading-cd.js.map +1 -1
  1355. package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
  1356. package/dist/core/tools/bash-pty-native.js.map +1 -1
  1357. package/dist/core/tools/bash-session-environment.d.ts +13 -0
  1358. package/dist/core/tools/bash-session-environment.d.ts.map +1 -0
  1359. package/dist/core/tools/bash-session-environment.js +47 -0
  1360. package/dist/core/tools/bash-session-environment.js.map +1 -0
  1361. package/dist/core/tools/bash.d.ts +4 -1
  1362. package/dist/core/tools/bash.d.ts.map +1 -1
  1363. package/dist/core/tools/bash.js +134 -30
  1364. package/dist/core/tools/bash.js.map +1 -1
  1365. package/dist/core/tools/block-resolver.d.ts.map +1 -1
  1366. package/dist/core/tools/block-resolver.js.map +1 -1
  1367. package/dist/core/tools/conflict-registry.d.ts.map +1 -1
  1368. package/dist/core/tools/conflict-registry.js.map +1 -1
  1369. package/dist/core/tools/directory-tree.d.ts.map +1 -1
  1370. package/dist/core/tools/directory-tree.js +20 -4
  1371. package/dist/core/tools/directory-tree.js.map +1 -1
  1372. package/dist/core/tools/edit.d.ts.map +1 -1
  1373. package/dist/core/tools/edit.js +37 -13
  1374. package/dist/core/tools/edit.js.map +1 -1
  1375. package/dist/core/tools/fetch-url.d.ts.map +1 -1
  1376. package/dist/core/tools/fetch-url.js +127 -41
  1377. package/dist/core/tools/fetch-url.js.map +1 -1
  1378. package/dist/core/tools/find.d.ts.map +1 -1
  1379. package/dist/core/tools/find.js +176 -46
  1380. package/dist/core/tools/find.js.map +1 -1
  1381. package/dist/core/tools/grep.d.ts.map +1 -1
  1382. package/dist/core/tools/grep.js +43 -10
  1383. package/dist/core/tools/grep.js.map +1 -1
  1384. package/dist/core/tools/hashline-engine/apply.d.ts.map +1 -1
  1385. package/dist/core/tools/hashline-engine/apply.js +6 -6
  1386. package/dist/core/tools/hashline-engine/apply.js.map +1 -1
  1387. package/dist/core/tools/hashline-engine/block.js +1 -1
  1388. package/dist/core/tools/hashline-engine/block.js.map +1 -1
  1389. package/dist/core/tools/hashline-engine/format.d.ts.map +1 -1
  1390. package/dist/core/tools/hashline-engine/format.js +1 -1
  1391. package/dist/core/tools/hashline-engine/format.js.map +1 -1
  1392. package/dist/core/tools/hashline-engine/input.d.ts.map +1 -1
  1393. package/dist/core/tools/hashline-engine/input.js +7 -7
  1394. package/dist/core/tools/hashline-engine/input.js.map +1 -1
  1395. package/dist/core/tools/hashline-engine/parser.js +1 -1
  1396. package/dist/core/tools/hashline-engine/parser.js.map +1 -1
  1397. package/dist/core/tools/hashline-engine/patcher.js +5 -5
  1398. package/dist/core/tools/hashline-engine/patcher.js.map +1 -1
  1399. package/dist/core/tools/hashline-engine/prefixes.js +4 -4
  1400. package/dist/core/tools/hashline-engine/prefixes.js.map +1 -1
  1401. package/dist/core/tools/hashline-engine/recovery.d.ts.map +1 -1
  1402. package/dist/core/tools/hashline-engine/recovery.js +4 -2
  1403. package/dist/core/tools/hashline-engine/recovery.js.map +1 -1
  1404. package/dist/core/tools/hashline-engine/snapshots.js +5 -5
  1405. package/dist/core/tools/hashline-engine/snapshots.js.map +1 -1
  1406. package/dist/core/tools/hashline.d.ts.map +1 -1
  1407. package/dist/core/tools/hashline.js +24 -13
  1408. package/dist/core/tools/hashline.js.map +1 -1
  1409. package/dist/core/tools/index.d.ts +6 -6
  1410. package/dist/core/tools/index.d.ts.map +1 -1
  1411. package/dist/core/tools/index.js +6 -6
  1412. package/dist/core/tools/index.js.map +1 -1
  1413. package/dist/core/tools/ls.d.ts.map +1 -1
  1414. package/dist/core/tools/ls.js +2 -1
  1415. package/dist/core/tools/ls.js.map +1 -1
  1416. package/dist/core/tools/notebook.d.ts.map +1 -1
  1417. package/dist/core/tools/notebook.js +13 -3
  1418. package/dist/core/tools/notebook.js.map +1 -1
  1419. package/dist/core/tools/read-document-extract.d.ts.map +1 -1
  1420. package/dist/core/tools/read-document-extract.js +30 -10
  1421. package/dist/core/tools/read-document-extract.js.map +1 -1
  1422. package/dist/core/tools/read-selectors.d.ts.map +1 -1
  1423. package/dist/core/tools/read-selectors.js +59 -15
  1424. package/dist/core/tools/read-selectors.js.map +1 -1
  1425. package/dist/core/tools/read-url.d.ts.map +1 -1
  1426. package/dist/core/tools/read-url.js +41 -7
  1427. package/dist/core/tools/read-url.js.map +1 -1
  1428. package/dist/core/tools/read.d.ts.map +1 -1
  1429. package/dist/core/tools/read.js +307 -72
  1430. package/dist/core/tools/read.js.map +1 -1
  1431. package/dist/core/tools/resource-selectors.d.ts +4 -0
  1432. package/dist/core/tools/resource-selectors.d.ts.map +1 -1
  1433. package/dist/core/tools/resource-selectors.js +324 -84
  1434. package/dist/core/tools/resource-selectors.js.map +1 -1
  1435. package/dist/core/tools/search-details.d.ts.map +1 -1
  1436. package/dist/core/tools/search-details.js +13 -1
  1437. package/dist/core/tools/search-details.js.map +1 -1
  1438. package/dist/core/tools/search-line-ranges.d.ts.map +1 -1
  1439. package/dist/core/tools/search-line-ranges.js +5 -2
  1440. package/dist/core/tools/search-line-ranges.js.map +1 -1
  1441. package/dist/core/tools/search-native.d.ts.map +1 -1
  1442. package/dist/core/tools/search-native.js.map +1 -1
  1443. package/dist/core/tools/search.d.ts.map +1 -1
  1444. package/dist/core/tools/search.js +244 -90
  1445. package/dist/core/tools/search.js.map +1 -1
  1446. package/dist/core/tools/structured-output.d.ts +1 -1
  1447. package/dist/core/tools/structured-output.d.ts.map +1 -1
  1448. package/dist/core/tools/structured-output.js.map +1 -1
  1449. package/dist/core/tools/todos-execute.d.ts.map +1 -1
  1450. package/dist/core/tools/todos-execute.js +4 -4
  1451. package/dist/core/tools/todos-execute.js.map +1 -1
  1452. package/dist/core/tools/todos-locks.d.ts.map +1 -1
  1453. package/dist/core/tools/todos-locks.js.map +1 -1
  1454. package/dist/core/tools/todos-mutations.js +1 -1
  1455. package/dist/core/tools/todos-mutations.js.map +1 -1
  1456. package/dist/core/tools/todos-paths.js +2 -2
  1457. package/dist/core/tools/todos-paths.js.map +1 -1
  1458. package/dist/core/tools/todos-render.d.ts.map +1 -1
  1459. package/dist/core/tools/todos-render.js +2 -2
  1460. package/dist/core/tools/todos-render.js.map +1 -1
  1461. package/dist/core/tools/todos-storage.d.ts.map +1 -1
  1462. package/dist/core/tools/todos-storage.js +1 -2
  1463. package/dist/core/tools/todos-storage.js.map +1 -1
  1464. package/dist/core/tools/todos-types.d.ts.map +1 -1
  1465. package/dist/core/tools/todos-types.js +1 -11
  1466. package/dist/core/tools/todos-types.js.map +1 -1
  1467. package/dist/core/tools/tool-definition-wrapper.d.ts +3 -0
  1468. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  1469. package/dist/core/tools/tool-definition-wrapper.js +9 -5
  1470. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  1471. package/dist/core/tools/url-ip-guards.d.ts.map +1 -1
  1472. package/dist/core/tools/url-ip-guards.js +27 -17
  1473. package/dist/core/tools/url-ip-guards.js.map +1 -1
  1474. package/dist/core/tools/write.d.ts.map +1 -1
  1475. package/dist/core/tools/write.js +88 -29
  1476. package/dist/core/tools/write.js.map +1 -1
  1477. package/dist/core/usage-totals.d.ts +18 -0
  1478. package/dist/core/usage-totals.d.ts.map +1 -0
  1479. package/dist/core/usage-totals.js +43 -0
  1480. package/dist/core/usage-totals.js.map +1 -0
  1481. package/dist/core/workflow-stage-admission.d.ts.map +1 -1
  1482. package/dist/core/workflow-stage-admission.js +8 -2
  1483. package/dist/core/workflow-stage-admission.js.map +1 -1
  1484. package/dist/extensions/index.d.ts +3 -0
  1485. package/dist/extensions/index.d.ts.map +1 -0
  1486. package/dist/extensions/index.js +5 -0
  1487. package/dist/extensions/index.js.map +1 -0
  1488. package/dist/extensions/llama/client.d.ts +61 -0
  1489. package/dist/extensions/llama/client.d.ts.map +1 -0
  1490. package/dist/extensions/llama/client.js +300 -0
  1491. package/dist/extensions/llama/client.js.map +1 -0
  1492. package/dist/extensions/llama/huggingface-ui.d.ts +33 -0
  1493. package/dist/extensions/llama/huggingface-ui.d.ts.map +1 -0
  1494. package/dist/extensions/llama/huggingface-ui.js +166 -0
  1495. package/dist/extensions/llama/huggingface-ui.js.map +1 -0
  1496. package/dist/extensions/llama/huggingface.d.ts +23 -0
  1497. package/dist/extensions/llama/huggingface.d.ts.map +1 -0
  1498. package/dist/extensions/llama/huggingface.js +139 -0
  1499. package/dist/extensions/llama/huggingface.js.map +1 -0
  1500. package/dist/extensions/llama/index.d.ts +3 -0
  1501. package/dist/extensions/llama/index.d.ts.map +1 -0
  1502. package/dist/extensions/llama/index.js +208 -0
  1503. package/dist/extensions/llama/index.js.map +1 -0
  1504. package/dist/extensions/llama/provider.d.ts +10 -0
  1505. package/dist/extensions/llama/provider.d.ts.map +1 -0
  1506. package/dist/extensions/llama/provider.js +107 -0
  1507. package/dist/extensions/llama/provider.js.map +1 -0
  1508. package/dist/extensions/llama/ui.d.ts +42 -0
  1509. package/dist/extensions/llama/ui.d.ts.map +1 -0
  1510. package/dist/extensions/llama/ui.js +237 -0
  1511. package/dist/extensions/llama/ui.js.map +1 -0
  1512. package/dist/index-extensions.d.ts +2 -2
  1513. package/dist/index-extensions.d.ts.map +1 -1
  1514. package/dist/index-extensions.js +1 -1
  1515. package/dist/index-extensions.js.map +1 -1
  1516. package/dist/index.d.ts +22 -23
  1517. package/dist/index.d.ts.map +1 -1
  1518. package/dist/index.js +26 -27
  1519. package/dist/index.js.map +1 -1
  1520. package/dist/main-app-mode.d.ts +1 -0
  1521. package/dist/main-app-mode.d.ts.map +1 -1
  1522. package/dist/main-app-mode.js +3 -0
  1523. package/dist/main-app-mode.js.map +1 -1
  1524. package/dist/main-deferred-startup.d.ts +1 -1
  1525. package/dist/main-deferred-startup.d.ts.map +1 -1
  1526. package/dist/main-deferred-startup.js +16 -15
  1527. package/dist/main-deferred-startup.js.map +1 -1
  1528. package/dist/main-early-input.js.map +1 -1
  1529. package/dist/main-first-time-setup.d.ts +6 -0
  1530. package/dist/main-first-time-setup.d.ts.map +1 -0
  1531. package/dist/main-first-time-setup.js +10 -0
  1532. package/dist/main-first-time-setup.js.map +1 -0
  1533. package/dist/main-runtime-api-key.d.ts +6 -0
  1534. package/dist/main-runtime-api-key.d.ts.map +1 -0
  1535. package/dist/main-runtime-api-key.js +6 -0
  1536. package/dist/main-runtime-api-key.js.map +1 -0
  1537. package/dist/main-session-options.d.ts +2 -2
  1538. package/dist/main-session-options.d.ts.map +1 -1
  1539. package/dist/main-session-options.js +3 -7
  1540. package/dist/main-session-options.js.map +1 -1
  1541. package/dist/main-session.d.ts.map +1 -1
  1542. package/dist/main-session.js +5 -4
  1543. package/dist/main-session.js.map +1 -1
  1544. package/dist/main-types.d.ts +2 -2
  1545. package/dist/main-types.d.ts.map +1 -1
  1546. package/dist/main-types.js.map +1 -1
  1547. package/dist/main.d.ts +1 -1
  1548. package/dist/main.d.ts.map +1 -1
  1549. package/dist/main.js +173 -69
  1550. package/dist/main.js.map +1 -1
  1551. package/dist/migrations-config-values.d.ts.map +1 -1
  1552. package/dist/migrations-config-values.js +3 -1
  1553. package/dist/migrations-config-values.js.map +1 -1
  1554. package/dist/migrations.d.ts.map +1 -1
  1555. package/dist/migrations.js.map +1 -1
  1556. package/dist/modes/index.d.ts +1 -1
  1557. package/dist/modes/index.d.ts.map +1 -1
  1558. package/dist/modes/index.js.map +1 -1
  1559. package/dist/modes/interactive/chat-input-actions.d.ts +1 -1
  1560. package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -1
  1561. package/dist/modes/interactive/chat-input-actions.js +30 -103
  1562. package/dist/modes/interactive/chat-input-actions.js.map +1 -1
  1563. package/dist/modes/interactive/components/atomic-banner.d.ts +9 -8
  1564. package/dist/modes/interactive/components/atomic-banner.d.ts.map +1 -1
  1565. package/dist/modes/interactive/components/atomic-banner.js +68 -22
  1566. package/dist/modes/interactive/components/atomic-banner.js.map +1 -1
  1567. package/dist/modes/interactive/components/atomic-working-status.d.ts +52 -0
  1568. package/dist/modes/interactive/components/atomic-working-status.d.ts.map +1 -0
  1569. package/dist/modes/interactive/components/atomic-working-status.js +215 -0
  1570. package/dist/modes/interactive/components/atomic-working-status.js.map +1 -0
  1571. package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -1
  1572. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  1573. package/dist/modes/interactive/components/chat-message-renderer.js +7 -6
  1574. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  1575. package/dist/modes/interactive/components/chat-session-host-actions.d.ts +3 -2
  1576. package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
  1577. package/dist/modes/interactive/components/chat-session-host-actions.js +93 -17
  1578. package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
  1579. package/dist/modes/interactive/components/chat-session-host-editor.d.ts +4 -4
  1580. package/dist/modes/interactive/components/chat-session-host-editor.d.ts.map +1 -1
  1581. package/dist/modes/interactive/components/chat-session-host-editor.js +12 -7
  1582. package/dist/modes/interactive/components/chat-session-host-editor.js.map +1 -1
  1583. package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
  1584. package/dist/modes/interactive/components/chat-session-host-events.js +84 -15
  1585. package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
  1586. package/dist/modes/interactive/components/chat-session-host-rendering.d.ts +1 -1
  1587. package/dist/modes/interactive/components/chat-session-host-rendering.d.ts.map +1 -1
  1588. package/dist/modes/interactive/components/chat-session-host-rendering.js +49 -14
  1589. package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
  1590. package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +36 -2
  1591. package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
  1592. package/dist/modes/interactive/components/chat-session-host-runtime.js +129 -25
  1593. package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
  1594. package/dist/modes/interactive/components/chat-session-host-state.d.ts +11 -2
  1595. package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
  1596. package/dist/modes/interactive/components/chat-session-host-state.js +5 -0
  1597. package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
  1598. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts.map +1 -1
  1599. package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js.map +1 -1
  1600. package/dist/modes/interactive/components/chat-session-host-types.d.ts +19 -2
  1601. package/dist/modes/interactive/components/chat-session-host-types.d.ts.map +1 -1
  1602. package/dist/modes/interactive/components/chat-session-host-types.js.map +1 -1
  1603. package/dist/modes/interactive/components/chat-session-host-utils.d.ts +1 -1
  1604. package/dist/modes/interactive/components/chat-session-host-utils.d.ts.map +1 -1
  1605. package/dist/modes/interactive/components/chat-session-host-utils.js +3 -8
  1606. package/dist/modes/interactive/components/chat-session-host-utils.js.map +1 -1
  1607. package/dist/modes/interactive/components/chat-session-host.d.ts +17 -4
  1608. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  1609. package/dist/modes/interactive/components/chat-session-host.js +26 -3
  1610. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  1611. package/dist/modes/interactive/components/chat-transcript.d.ts +1 -1
  1612. package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
  1613. package/dist/modes/interactive/components/chat-transcript.js +5 -12
  1614. package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
  1615. package/dist/modes/interactive/components/compaction-boundary-message.d.ts.map +1 -1
  1616. package/dist/modes/interactive/components/compaction-boundary-message.js +20 -8
  1617. package/dist/modes/interactive/components/compaction-boundary-message.js.map +1 -1
  1618. package/dist/modes/interactive/components/config-selector-list.d.ts +6 -6
  1619. package/dist/modes/interactive/components/config-selector-list.d.ts.map +1 -1
  1620. package/dist/modes/interactive/components/config-selector-list.js +66 -125
  1621. package/dist/modes/interactive/components/config-selector-list.js.map +1 -1
  1622. package/dist/modes/interactive/components/config-selector-project-scope.d.ts +4 -0
  1623. package/dist/modes/interactive/components/config-selector-project-scope.d.ts.map +1 -0
  1624. package/dist/modes/interactive/components/config-selector-project-scope.js +59 -0
  1625. package/dist/modes/interactive/components/config-selector-project-scope.js.map +1 -0
  1626. package/dist/modes/interactive/components/config-selector.d.ts +8 -5
  1627. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  1628. package/dist/modes/interactive/components/config-selector.js +46 -13
  1629. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  1630. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  1631. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  1632. package/dist/modes/interactive/components/custom-editor.d.ts +11 -0
  1633. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  1634. package/dist/modes/interactive/components/custom-editor.js +21 -5
  1635. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  1636. package/dist/modes/interactive/components/custom-entry.d.ts +16 -0
  1637. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
  1638. package/dist/modes/interactive/components/custom-entry.js +45 -0
  1639. package/dist/modes/interactive/components/custom-entry.js.map +1 -0
  1640. package/dist/modes/interactive/components/custom-message.d.ts +3 -1
  1641. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  1642. package/dist/modes/interactive/components/custom-message.js +10 -5
  1643. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  1644. package/dist/modes/interactive/components/diff.js +2 -2
  1645. package/dist/modes/interactive/components/diff.js.map +1 -1
  1646. package/dist/modes/interactive/components/extension-editor.d.ts +3 -2
  1647. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  1648. package/dist/modes/interactive/components/extension-editor.js +14 -43
  1649. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  1650. package/dist/modes/interactive/components/first-time-setup.d.ts +24 -0
  1651. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -0
  1652. package/dist/modes/interactive/components/first-time-setup.js +80 -0
  1653. package/dist/modes/interactive/components/first-time-setup.js.map +1 -0
  1654. package/dist/modes/interactive/components/footer.d.ts +7 -1
  1655. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  1656. package/dist/modes/interactive/components/footer.js +68 -39
  1657. package/dist/modes/interactive/components/footer.js.map +1 -1
  1658. package/dist/modes/interactive/components/host-input-form-mount.d.ts.map +1 -1
  1659. package/dist/modes/interactive/components/host-input-form-mount.js +4 -2
  1660. package/dist/modes/interactive/components/host-input-form-mount.js.map +1 -1
  1661. package/dist/modes/interactive/components/host-input-form.d.ts +2 -0
  1662. package/dist/modes/interactive/components/host-input-form.d.ts.map +1 -1
  1663. package/dist/modes/interactive/components/host-input-form.js +61 -24
  1664. package/dist/modes/interactive/components/host-input-form.js.map +1 -1
  1665. package/dist/modes/interactive/components/host-session-picker.d.ts.map +1 -1
  1666. package/dist/modes/interactive/components/host-session-picker.js +8 -3
  1667. package/dist/modes/interactive/components/host-session-picker.js.map +1 -1
  1668. package/dist/modes/interactive/components/idle-status.d.ts +8 -0
  1669. package/dist/modes/interactive/components/idle-status.d.ts.map +1 -0
  1670. package/dist/modes/interactive/components/idle-status.js +13 -0
  1671. package/dist/modes/interactive/components/idle-status.js.map +1 -0
  1672. package/dist/modes/interactive/components/index.d.ts +8 -5
  1673. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  1674. package/dist/modes/interactive/components/index.js +5 -2
  1675. package/dist/modes/interactive/components/index.js.map +1 -1
  1676. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  1677. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  1678. package/dist/modes/interactive/components/login-dialog.d.ts +5 -5
  1679. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  1680. package/dist/modes/interactive/components/login-dialog.js +15 -9
  1681. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  1682. package/dist/modes/interactive/components/model-selector.d.ts +4 -3
  1683. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  1684. package/dist/modes/interactive/components/model-selector.js +70 -72
  1685. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  1686. package/dist/modes/interactive/components/oauth-selector.d.ts +4 -3
  1687. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  1688. package/dist/modes/interactive/components/oauth-selector.js +19 -18
  1689. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  1690. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  1691. package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
  1692. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  1693. package/dist/modes/interactive/components/session-selector-list.d.ts +1 -1
  1694. package/dist/modes/interactive/components/session-selector-list.d.ts.map +1 -1
  1695. package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
  1696. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  1697. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  1698. package/dist/modes/interactive/components/settings-selector-handlers.d.ts.map +1 -1
  1699. package/dist/modes/interactive/components/settings-selector-handlers.js +6 -0
  1700. package/dist/modes/interactive/components/settings-selector-handlers.js.map +1 -1
  1701. package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
  1702. package/dist/modes/interactive/components/settings-selector-items.js +14 -0
  1703. package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
  1704. package/dist/modes/interactive/components/settings-selector-submenus.d.ts.map +1 -1
  1705. package/dist/modes/interactive/components/settings-selector-submenus.js.map +1 -1
  1706. package/dist/modes/interactive/components/settings-selector-types.d.ts +4 -0
  1707. package/dist/modes/interactive/components/settings-selector-types.d.ts.map +1 -1
  1708. package/dist/modes/interactive/components/settings-selector-types.js.map +1 -1
  1709. package/dist/modes/interactive/components/startup-identity.d.ts +22 -0
  1710. package/dist/modes/interactive/components/startup-identity.d.ts.map +1 -0
  1711. package/dist/modes/interactive/components/startup-identity.js +58 -0
  1712. package/dist/modes/interactive/components/startup-identity.js.map +1 -0
  1713. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  1714. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  1715. package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
  1716. package/dist/modes/interactive/components/tree-selector-content.js +0 -6
  1717. package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
  1718. package/dist/modes/interactive/components/tree-selector-model.d.ts.map +1 -1
  1719. package/dist/modes/interactive/components/tree-selector-model.js +1 -10
  1720. package/dist/modes/interactive/components/tree-selector-model.js.map +1 -1
  1721. package/dist/modes/interactive/components/tree-selector-viewport.d.ts.map +1 -1
  1722. package/dist/modes/interactive/components/tree-selector-viewport.js.map +1 -1
  1723. package/dist/modes/interactive/components/working-status.d.ts +5 -11
  1724. package/dist/modes/interactive/components/working-status.d.ts.map +1 -1
  1725. package/dist/modes/interactive/components/working-status.js +14 -15
  1726. package/dist/modes/interactive/components/working-status.js.map +1 -1
  1727. package/dist/modes/interactive/external-editor.d.ts +23 -0
  1728. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  1729. package/dist/modes/interactive/external-editor.js +120 -0
  1730. package/dist/modes/interactive/external-editor.js.map +1 -0
  1731. package/dist/modes/interactive/interactive-agent-events.js +73 -28
  1732. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  1733. package/dist/modes/interactive/interactive-auth-login.js +36 -28
  1734. package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
  1735. package/dist/modes/interactive/interactive-auth-routing.d.ts +4 -1
  1736. package/dist/modes/interactive/interactive-auth-routing.d.ts.map +1 -1
  1737. package/dist/modes/interactive/interactive-auth-routing.js +92 -65
  1738. package/dist/modes/interactive/interactive-auth-routing.js.map +1 -1
  1739. package/dist/modes/interactive/interactive-autocomplete.js +24 -14
  1740. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  1741. package/dist/modes/interactive/interactive-bash-compact.d.ts +2 -1
  1742. package/dist/modes/interactive/interactive-bash-compact.d.ts.map +1 -1
  1743. package/dist/modes/interactive/interactive-bash-compact.js +26 -8
  1744. package/dist/modes/interactive/interactive-bash-compact.js.map +1 -1
  1745. package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
  1746. package/dist/modes/interactive/interactive-deferred-startup.js +61 -27
  1747. package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
  1748. package/dist/modes/interactive/interactive-editor-actions.js +9 -13
  1749. package/dist/modes/interactive/interactive-editor-actions.js.map +1 -1
  1750. package/dist/modes/interactive/interactive-extension-context.js +4 -4
  1751. package/dist/modes/interactive/interactive-extension-context.js.map +1 -1
  1752. package/dist/modes/interactive/interactive-extension-custom-ui.js +15 -8
  1753. package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
  1754. package/dist/modes/interactive/interactive-extension-dialogs.js +51 -25
  1755. package/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
  1756. package/dist/modes/interactive/interactive-extension-runtime.js +15 -6
  1757. package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
  1758. package/dist/modes/interactive/interactive-extension-widgets.js +1 -1
  1759. package/dist/modes/interactive/interactive-extension-widgets.js.map +1 -1
  1760. package/dist/modes/interactive/interactive-global-clear.d.ts +55 -3
  1761. package/dist/modes/interactive/interactive-global-clear.d.ts.map +1 -1
  1762. package/dist/modes/interactive/interactive-global-clear.js +61 -3
  1763. package/dist/modes/interactive/interactive-global-clear.js.map +1 -1
  1764. package/dist/modes/interactive/interactive-hotkeys-debug.js +2 -3
  1765. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  1766. package/dist/modes/interactive/interactive-initial-session-binding.d.ts +4 -0
  1767. package/dist/modes/interactive/interactive-initial-session-binding.d.ts.map +1 -0
  1768. package/dist/modes/interactive/interactive-initial-session-binding.js +23 -0
  1769. package/dist/modes/interactive/interactive-initial-session-binding.js.map +1 -0
  1770. package/dist/modes/interactive/interactive-input-handling.d.ts +2 -1
  1771. package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
  1772. package/dist/modes/interactive/interactive-input-handling.js +82 -69
  1773. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  1774. package/dist/modes/interactive/interactive-key-identity.d.ts +22 -0
  1775. package/dist/modes/interactive/interactive-key-identity.d.ts.map +1 -0
  1776. package/dist/modes/interactive/interactive-key-identity.js +29 -0
  1777. package/dist/modes/interactive/interactive-key-identity.js.map +1 -0
  1778. package/dist/modes/interactive/interactive-mode-base.d.ts +18 -13
  1779. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  1780. package/dist/modes/interactive/interactive-mode-base.js +26 -18
  1781. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  1782. package/dist/modes/interactive/interactive-mode-deps.d.ts +20 -22
  1783. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  1784. package/dist/modes/interactive/interactive-mode-deps.js +16 -18
  1785. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  1786. package/dist/modes/interactive/interactive-mode-helpers.d.ts +3 -2
  1787. package/dist/modes/interactive/interactive-mode-helpers.d.ts.map +1 -1
  1788. package/dist/modes/interactive/interactive-mode-helpers.js +8 -18
  1789. package/dist/modes/interactive/interactive-mode-helpers.js.map +1 -1
  1790. package/dist/modes/interactive/interactive-mode-surface.d.ts +28 -17
  1791. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  1792. package/dist/modes/interactive/interactive-mode-surface.js +1 -0
  1793. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  1794. package/dist/modes/interactive/interactive-mode-types.d.ts +2 -2
  1795. package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
  1796. package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
  1797. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  1798. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  1799. package/dist/modes/interactive/interactive-mode.js +1 -0
  1800. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  1801. package/dist/modes/interactive/interactive-model-catalog-startup.d.ts +12 -0
  1802. package/dist/modes/interactive/interactive-model-catalog-startup.d.ts.map +1 -0
  1803. package/dist/modes/interactive/interactive-model-catalog-startup.js +22 -0
  1804. package/dist/modes/interactive/interactive-model-catalog-startup.js.map +1 -0
  1805. package/dist/modes/interactive/interactive-model-routing.js +41 -111
  1806. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  1807. package/dist/modes/interactive/interactive-onboarding.js +7 -7
  1808. package/dist/modes/interactive/interactive-onboarding.js.map +1 -1
  1809. package/dist/modes/interactive/interactive-pause.d.ts +4 -0
  1810. package/dist/modes/interactive/interactive-pause.d.ts.map +1 -0
  1811. package/dist/modes/interactive/interactive-pause.js +8 -0
  1812. package/dist/modes/interactive/interactive-pause.js.map +1 -0
  1813. package/dist/modes/interactive/interactive-process-lifecycle.js +43 -12
  1814. package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
  1815. package/dist/modes/interactive/interactive-prompt-restore.d.ts +58 -0
  1816. package/dist/modes/interactive/interactive-prompt-restore.d.ts.map +1 -0
  1817. package/dist/modes/interactive/interactive-prompt-restore.js +87 -0
  1818. package/dist/modes/interactive/interactive-prompt-restore.js.map +1 -0
  1819. package/dist/modes/interactive/interactive-prompt-turn.d.ts +2 -0
  1820. package/dist/modes/interactive/interactive-prompt-turn.d.ts.map +1 -0
  1821. package/dist/modes/interactive/interactive-prompt-turn.js +56 -0
  1822. package/dist/modes/interactive/interactive-prompt-turn.js.map +1 -0
  1823. package/dist/modes/interactive/interactive-queueing.js +6 -9
  1824. package/dist/modes/interactive/interactive-queueing.js.map +1 -1
  1825. package/dist/modes/interactive/interactive-render-chat.js +128 -48
  1826. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  1827. package/dist/modes/interactive/interactive-resource-disclosure.js +1 -3
  1828. package/dist/modes/interactive/interactive-resource-disclosure.js.map +1 -1
  1829. package/dist/modes/interactive/interactive-resource-paths.js +17 -21
  1830. package/dist/modes/interactive/interactive-resource-paths.js.map +1 -1
  1831. package/dist/modes/interactive/interactive-resource-rendering.js +110 -12
  1832. package/dist/modes/interactive/interactive-resource-rendering.js.map +1 -1
  1833. package/dist/modes/interactive/interactive-selectors.js +13 -5
  1834. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  1835. package/dist/modes/interactive/interactive-session-routing.js +19 -4
  1836. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  1837. package/dist/modes/interactive/interactive-session-runtime.js +12 -1
  1838. package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
  1839. package/dist/modes/interactive/interactive-slash-commands.js +23 -4
  1840. package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
  1841. package/dist/modes/interactive/interactive-startup-chat-container.d.ts +20 -0
  1842. package/dist/modes/interactive/interactive-startup-chat-container.d.ts.map +1 -0
  1843. package/dist/modes/interactive/interactive-startup-chat-container.js +26 -0
  1844. package/dist/modes/interactive/interactive-startup-chat-container.js.map +1 -0
  1845. package/dist/modes/interactive/interactive-startup.d.ts +1 -1
  1846. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  1847. package/dist/modes/interactive/interactive-startup.js +37 -37
  1848. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  1849. package/dist/modes/interactive/interactive-submission.d.ts +24 -0
  1850. package/dist/modes/interactive/interactive-submission.d.ts.map +1 -0
  1851. package/dist/modes/interactive/interactive-submission.js +10 -0
  1852. package/dist/modes/interactive/interactive-submission.js.map +1 -0
  1853. package/dist/modes/interactive/interactive-summarization-retry-events.d.ts +8 -0
  1854. package/dist/modes/interactive/interactive-summarization-retry-events.d.ts.map +1 -0
  1855. package/dist/modes/interactive/interactive-summarization-retry-events.js +48 -0
  1856. package/dist/modes/interactive/interactive-summarization-retry-events.js.map +1 -0
  1857. package/dist/modes/interactive/login-provider-options.d.ts +21 -0
  1858. package/dist/modes/interactive/login-provider-options.d.ts.map +1 -0
  1859. package/dist/modes/interactive/login-provider-options.js +50 -0
  1860. package/dist/modes/interactive/login-provider-options.js.map +1 -0
  1861. package/dist/modes/interactive/theme/catppuccin-frappe.json +1 -1
  1862. package/dist/modes/interactive/theme/catppuccin-latte.json +1 -1
  1863. package/dist/modes/interactive/theme/catppuccin-macchiato.json +1 -1
  1864. package/dist/modes/interactive/theme/catppuccin-mocha.json +9 -1
  1865. package/dist/modes/interactive/theme/dark.json +1 -1
  1866. package/dist/modes/interactive/theme/global-theme.d.ts +1 -1
  1867. package/dist/modes/interactive/theme/global-theme.d.ts.map +1 -1
  1868. package/dist/modes/interactive/theme/global-theme.js.map +1 -1
  1869. package/dist/modes/interactive/theme/light.json +1 -1
  1870. package/dist/modes/interactive/theme/theme-class.d.ts +4 -0
  1871. package/dist/modes/interactive/theme/theme-class.d.ts.map +1 -1
  1872. package/dist/modes/interactive/theme/theme-class.js +7 -0
  1873. package/dist/modes/interactive/theme/theme-class.js.map +1 -1
  1874. package/dist/modes/interactive/theme/theme-loading.d.ts.map +1 -1
  1875. package/dist/modes/interactive/theme/theme-loading.js +5 -1
  1876. package/dist/modes/interactive/theme/theme-loading.js.map +1 -1
  1877. package/dist/modes/interactive/theme/theme-schema.d.ts +32 -0
  1878. package/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
  1879. package/dist/modes/interactive/theme/theme-schema.js +8 -0
  1880. package/dist/modes/interactive/theme/theme-schema.js.map +1 -1
  1881. package/dist/modes/interactive/theme/theme-schema.json +15 -2
  1882. package/dist/modes/interactive/theme/theme.d.ts +2 -2
  1883. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  1884. package/dist/modes/interactive/theme/theme.js +2 -2
  1885. package/dist/modes/interactive/theme/theme.js.map +1 -1
  1886. package/dist/modes/interactive/whimsical-messages.d.ts +1 -0
  1887. package/dist/modes/interactive/whimsical-messages.d.ts.map +1 -1
  1888. package/dist/modes/interactive/whimsical-messages.js +2 -2
  1889. package/dist/modes/interactive/whimsical-messages.js.map +1 -1
  1890. package/dist/modes/interactive-engine/activity-watchdog.d.ts +16 -6
  1891. package/dist/modes/interactive-engine/activity-watchdog.d.ts.map +1 -1
  1892. package/dist/modes/interactive-engine/activity-watchdog.js +12 -6
  1893. package/dist/modes/interactive-engine/activity-watchdog.js.map +1 -1
  1894. package/dist/modes/interactive-engine/create-isolated-runtime.d.ts.map +1 -1
  1895. package/dist/modes/interactive-engine/create-isolated-runtime.js +8 -5
  1896. package/dist/modes/interactive-engine/create-isolated-runtime.js.map +1 -1
  1897. package/dist/modes/interactive-engine/engine-args.d.ts.map +1 -1
  1898. package/dist/modes/interactive-engine/engine-args.js +0 -1
  1899. package/dist/modes/interactive-engine/engine-args.js.map +1 -1
  1900. package/dist/modes/interactive-engine/engine-child-liveness.d.ts.map +1 -1
  1901. package/dist/modes/interactive-engine/engine-child-liveness.js +6 -4
  1902. package/dist/modes/interactive-engine/engine-child-liveness.js.map +1 -1
  1903. package/dist/modes/interactive-engine/engine-custom-ui.d.ts +3 -2
  1904. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  1905. package/dist/modes/interactive-engine/engine-custom-ui.js +37 -28
  1906. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  1907. package/dist/modes/interactive-engine/engine-diagnostic-view.d.ts +16 -0
  1908. package/dist/modes/interactive-engine/engine-diagnostic-view.d.ts.map +1 -0
  1909. package/dist/modes/interactive-engine/engine-diagnostic-view.js +19 -0
  1910. package/dist/modes/interactive-engine/engine-diagnostic-view.js.map +1 -0
  1911. package/dist/modes/interactive-engine/engine-dialog-host.d.ts +33 -0
  1912. package/dist/modes/interactive-engine/engine-dialog-host.d.ts.map +1 -0
  1913. package/dist/modes/interactive-engine/engine-dialog-host.js +129 -0
  1914. package/dist/modes/interactive-engine/engine-dialog-host.js.map +1 -0
  1915. package/dist/modes/interactive-engine/engine-generation.d.ts +29 -0
  1916. package/dist/modes/interactive-engine/engine-generation.d.ts.map +1 -0
  1917. package/dist/modes/interactive-engine/engine-generation.js +11 -0
  1918. package/dist/modes/interactive-engine/engine-generation.js.map +1 -0
  1919. package/dist/modes/interactive-engine/engine-health.d.ts +107 -0
  1920. package/dist/modes/interactive-engine/engine-health.d.ts.map +1 -0
  1921. package/dist/modes/interactive-engine/engine-health.js +250 -0
  1922. package/dist/modes/interactive-engine/engine-health.js.map +1 -0
  1923. package/dist/modes/interactive-engine/engine-input-form.d.ts +1 -1
  1924. package/dist/modes/interactive-engine/engine-input-form.d.ts.map +1 -1
  1925. package/dist/modes/interactive-engine/engine-input-form.js +21 -4
  1926. package/dist/modes/interactive-engine/engine-input-form.js.map +1 -1
  1927. package/dist/modes/interactive-engine/engine-monitor.d.ts +8 -1
  1928. package/dist/modes/interactive-engine/engine-monitor.d.ts.map +1 -1
  1929. package/dist/modes/interactive-engine/engine-monitor.js +19 -16
  1930. package/dist/modes/interactive-engine/engine-monitor.js.map +1 -1
  1931. package/dist/modes/interactive-engine/engine-render-service.d.ts.map +1 -1
  1932. package/dist/modes/interactive-engine/engine-render-service.js +19 -24
  1933. package/dist/modes/interactive-engine/engine-render-service.js.map +1 -1
  1934. package/dist/modes/interactive-engine/engine-session-picker.d.ts.map +1 -1
  1935. package/dist/modes/interactive-engine/engine-session-picker.js +4 -2
  1936. package/dist/modes/interactive-engine/engine-session-picker.js.map +1 -1
  1937. package/dist/modes/interactive-engine/extension-ui-bridge.d.ts +18 -3
  1938. package/dist/modes/interactive-engine/extension-ui-bridge.d.ts.map +1 -1
  1939. package/dist/modes/interactive-engine/extension-ui-bridge.js +41 -45
  1940. package/dist/modes/interactive-engine/extension-ui-bridge.js.map +1 -1
  1941. package/dist/modes/interactive-engine/input-form-host.d.ts +1 -0
  1942. package/dist/modes/interactive-engine/input-form-host.d.ts.map +1 -1
  1943. package/dist/modes/interactive-engine/input-form-host.js +18 -3
  1944. package/dist/modes/interactive-engine/input-form-host.js.map +1 -1
  1945. package/dist/modes/interactive-engine/isolated-auth.d.ts +10 -0
  1946. package/dist/modes/interactive-engine/isolated-auth.d.ts.map +1 -0
  1947. package/dist/modes/interactive-engine/isolated-auth.js +15 -0
  1948. package/dist/modes/interactive-engine/isolated-auth.js.map +1 -0
  1949. package/dist/modes/interactive-engine/isolated-runtime.d.ts +44 -11
  1950. package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
  1951. package/dist/modes/interactive-engine/isolated-runtime.js +173 -106
  1952. package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
  1953. package/dist/modes/interactive-engine/protocol.d.ts +26 -2
  1954. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  1955. package/dist/modes/interactive-engine/protocol.js +192 -63
  1956. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  1957. package/dist/modes/interactive-engine/remote-component.d.ts +38 -0
  1958. package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
  1959. package/dist/modes/interactive-engine/remote-component.js +111 -22
  1960. package/dist/modes/interactive-engine/remote-component.js.map +1 -1
  1961. package/dist/modes/interactive-engine/remote-input-ownership.d.ts +48 -0
  1962. package/dist/modes/interactive-engine/remote-input-ownership.d.ts.map +1 -0
  1963. package/dist/modes/interactive-engine/remote-input-ownership.js +42 -0
  1964. package/dist/modes/interactive-engine/remote-input-ownership.js.map +1 -0
  1965. package/dist/modes/interactive-engine/remote-model-catalog.d.ts +17 -0
  1966. package/dist/modes/interactive-engine/remote-model-catalog.d.ts.map +1 -0
  1967. package/dist/modes/interactive-engine/remote-model-catalog.js +80 -0
  1968. package/dist/modes/interactive-engine/remote-model-catalog.js.map +1 -0
  1969. package/dist/modes/interactive-engine/remote-queue-pause.d.ts +13 -0
  1970. package/dist/modes/interactive-engine/remote-queue-pause.d.ts.map +1 -0
  1971. package/dist/modes/interactive-engine/remote-queue-pause.js +38 -0
  1972. package/dist/modes/interactive-engine/remote-queue-pause.js.map +1 -0
  1973. package/dist/modes/interactive-engine/remote-renderer.d.ts +3 -1
  1974. package/dist/modes/interactive-engine/remote-renderer.d.ts.map +1 -1
  1975. package/dist/modes/interactive-engine/remote-renderer.js +41 -10
  1976. package/dist/modes/interactive-engine/remote-renderer.js.map +1 -1
  1977. package/dist/modes/interactive-engine/session-picker-host.d.ts +1 -0
  1978. package/dist/modes/interactive-engine/session-picker-host.d.ts.map +1 -1
  1979. package/dist/modes/interactive-engine/session-picker-host.js +4 -0
  1980. package/dist/modes/interactive-engine/session-picker-host.js.map +1 -1
  1981. package/dist/modes/print-mode.d.ts.map +1 -1
  1982. package/dist/modes/print-mode.js +5 -0
  1983. package/dist/modes/print-mode.js.map +1 -1
  1984. package/dist/modes/rpc/jsonl.d.ts +9 -7
  1985. package/dist/modes/rpc/jsonl.d.ts.map +1 -1
  1986. package/dist/modes/rpc/jsonl.js +23 -24
  1987. package/dist/modes/rpc/jsonl.js.map +1 -1
  1988. package/dist/modes/rpc/queued-writer.d.ts +33 -0
  1989. package/dist/modes/rpc/queued-writer.d.ts.map +1 -0
  1990. package/dist/modes/rpc/queued-writer.js +104 -0
  1991. package/dist/modes/rpc/queued-writer.js.map +1 -0
  1992. package/dist/modes/rpc/rpc-bash-request-owners.d.ts +24 -0
  1993. package/dist/modes/rpc/rpc-bash-request-owners.d.ts.map +1 -0
  1994. package/dist/modes/rpc/rpc-bash-request-owners.js +45 -0
  1995. package/dist/modes/rpc/rpc-bash-request-owners.js.map +1 -0
  1996. package/dist/modes/rpc/rpc-client-api.d.ts +17 -4
  1997. package/dist/modes/rpc/rpc-client-api.d.ts.map +1 -1
  1998. package/dist/modes/rpc/rpc-client-api.js +92 -25
  1999. package/dist/modes/rpc/rpc-client-api.js.map +1 -1
  2000. package/dist/modes/rpc/rpc-client-process.d.ts +11 -4
  2001. package/dist/modes/rpc/rpc-client-process.d.ts.map +1 -1
  2002. package/dist/modes/rpc/rpc-client-process.js +77 -29
  2003. package/dist/modes/rpc/rpc-client-process.js.map +1 -1
  2004. package/dist/modes/rpc/rpc-client-waits.d.ts +17 -1
  2005. package/dist/modes/rpc/rpc-client-waits.d.ts.map +1 -1
  2006. package/dist/modes/rpc/rpc-client-waits.js +25 -0
  2007. package/dist/modes/rpc/rpc-client-waits.js.map +1 -1
  2008. package/dist/modes/rpc/rpc-client.d.ts +68 -9
  2009. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  2010. package/dist/modes/rpc/rpc-client.js +205 -158
  2011. package/dist/modes/rpc/rpc-client.js.map +1 -1
  2012. package/dist/modes/rpc/rpc-command-handler.d.ts +10 -3
  2013. package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
  2014. package/dist/modes/rpc/rpc-command-handler.js +165 -43
  2015. package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
  2016. package/dist/modes/rpc/rpc-command-timeouts.d.ts +30 -0
  2017. package/dist/modes/rpc/rpc-command-timeouts.d.ts.map +1 -0
  2018. package/dist/modes/rpc/rpc-command-timeouts.js +52 -0
  2019. package/dist/modes/rpc/rpc-command-timeouts.js.map +1 -0
  2020. package/dist/modes/rpc/rpc-event-buffer.d.ts.map +1 -1
  2021. package/dist/modes/rpc/rpc-event-buffer.js +3 -1
  2022. package/dist/modes/rpc/rpc-event-buffer.js.map +1 -1
  2023. package/dist/modes/rpc/rpc-extension-ui.d.ts +4 -2
  2024. package/dist/modes/rpc/rpc-extension-ui.d.ts.map +1 -1
  2025. package/dist/modes/rpc/rpc-extension-ui.js +50 -17
  2026. package/dist/modes/rpc/rpc-extension-ui.js.map +1 -1
  2027. package/dist/modes/rpc/rpc-generation-buffer.d.ts +21 -0
  2028. package/dist/modes/rpc/rpc-generation-buffer.d.ts.map +1 -0
  2029. package/dist/modes/rpc/rpc-generation-buffer.js +35 -0
  2030. package/dist/modes/rpc/rpc-generation-buffer.js.map +1 -0
  2031. package/dist/modes/rpc/rpc-input-scheduler.d.ts +3 -3
  2032. package/dist/modes/rpc/rpc-input-scheduler.d.ts.map +1 -1
  2033. package/dist/modes/rpc/rpc-input-scheduler.js +19 -8
  2034. package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
  2035. package/dist/modes/rpc/rpc-input.d.ts +7 -1
  2036. package/dist/modes/rpc/rpc-input.d.ts.map +1 -1
  2037. package/dist/modes/rpc/rpc-input.js +14 -4
  2038. package/dist/modes/rpc/rpc-input.js.map +1 -1
  2039. package/dist/modes/rpc/rpc-keybindings-reload.d.ts.map +1 -1
  2040. package/dist/modes/rpc/rpc-keybindings-reload.js.map +1 -1
  2041. package/dist/modes/rpc/rpc-mode.d.ts +1 -1
  2042. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  2043. package/dist/modes/rpc/rpc-mode.js +23 -23
  2044. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  2045. package/dist/modes/rpc/rpc-model-fallback-prompt.d.ts +4 -0
  2046. package/dist/modes/rpc/rpc-model-fallback-prompt.d.ts.map +1 -0
  2047. package/dist/modes/rpc/rpc-model-fallback-prompt.js +11 -0
  2048. package/dist/modes/rpc/rpc-model-fallback-prompt.js.map +1 -0
  2049. package/dist/modes/rpc/rpc-oauth-client.d.ts +21 -0
  2050. package/dist/modes/rpc/rpc-oauth-client.d.ts.map +1 -0
  2051. package/dist/modes/rpc/rpc-oauth-client.js +81 -0
  2052. package/dist/modes/rpc/rpc-oauth-client.js.map +1 -0
  2053. package/dist/modes/rpc/rpc-oauth-interaction.d.ts +9 -0
  2054. package/dist/modes/rpc/rpc-oauth-interaction.d.ts.map +1 -0
  2055. package/dist/modes/rpc/rpc-oauth-interaction.js +49 -0
  2056. package/dist/modes/rpc/rpc-oauth-interaction.js.map +1 -0
  2057. package/dist/modes/rpc/rpc-output-buffer.d.ts +2 -1
  2058. package/dist/modes/rpc/rpc-output-buffer.d.ts.map +1 -1
  2059. package/dist/modes/rpc/rpc-output-buffer.js +7 -40
  2060. package/dist/modes/rpc/rpc-output-buffer.js.map +1 -1
  2061. package/dist/modes/rpc/rpc-pending-requests.d.ts +37 -0
  2062. package/dist/modes/rpc/rpc-pending-requests.d.ts.map +1 -0
  2063. package/dist/modes/rpc/rpc-pending-requests.js +57 -0
  2064. package/dist/modes/rpc/rpc-pending-requests.js.map +1 -0
  2065. package/dist/modes/rpc/rpc-provider-auth.d.ts +22 -0
  2066. package/dist/modes/rpc/rpc-provider-auth.d.ts.map +1 -0
  2067. package/dist/modes/rpc/rpc-provider-auth.js +117 -0
  2068. package/dist/modes/rpc/rpc-provider-auth.js.map +1 -0
  2069. package/dist/modes/rpc/rpc-responses.d.ts +0 -1
  2070. package/dist/modes/rpc/rpc-responses.d.ts.map +1 -1
  2071. package/dist/modes/rpc/rpc-responses.js +0 -14
  2072. package/dist/modes/rpc/rpc-responses.js.map +1 -1
  2073. package/dist/modes/rpc/rpc-session-binding.d.ts +2 -1
  2074. package/dist/modes/rpc/rpc-session-binding.d.ts.map +1 -1
  2075. package/dist/modes/rpc/rpc-session-binding.js +71 -43
  2076. package/dist/modes/rpc/rpc-session-binding.js.map +1 -1
  2077. package/dist/modes/rpc/rpc-terminal-drain.d.ts +52 -0
  2078. package/dist/modes/rpc/rpc-terminal-drain.d.ts.map +1 -0
  2079. package/dist/modes/rpc/rpc-terminal-drain.js +85 -0
  2080. package/dist/modes/rpc/rpc-terminal-drain.js.map +1 -0
  2081. package/dist/modes/rpc/rpc-transport-error.d.ts +49 -0
  2082. package/dist/modes/rpc/rpc-transport-error.d.ts.map +1 -0
  2083. package/dist/modes/rpc/rpc-transport-error.js +78 -0
  2084. package/dist/modes/rpc/rpc-transport-error.js.map +1 -0
  2085. package/dist/modes/rpc/rpc-types.d.ts +188 -25
  2086. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  2087. package/dist/modes/rpc/rpc-types.js.map +1 -1
  2088. package/dist/package-manager-cli-parser.js.map +1 -1
  2089. package/dist/package-manager-cli.d.ts +3 -3
  2090. package/dist/package-manager-cli.d.ts.map +1 -1
  2091. package/dist/package-manager-cli.js +66 -53
  2092. package/dist/package-manager-cli.js.map +1 -1
  2093. package/dist/rpc-entry.js +6 -2
  2094. package/dist/rpc-entry.js.map +1 -1
  2095. package/dist/self-update-plan.d.ts +12 -0
  2096. package/dist/self-update-plan.d.ts.map +1 -0
  2097. package/dist/self-update-plan.js +42 -0
  2098. package/dist/self-update-plan.js.map +1 -0
  2099. package/dist/utils/child-process.d.ts.map +1 -1
  2100. package/dist/utils/child-process.js.map +1 -1
  2101. package/dist/utils/clipboard-image.js +1 -1
  2102. package/dist/utils/clipboard-image.js.map +1 -1
  2103. package/dist/utils/clipboard-native.d.ts +1 -0
  2104. package/dist/utils/clipboard-native.d.ts.map +1 -1
  2105. package/dist/utils/clipboard-native.js.map +1 -1
  2106. package/dist/utils/clipboard.d.ts +3 -0
  2107. package/dist/utils/clipboard.d.ts.map +1 -1
  2108. package/dist/utils/clipboard.js +31 -8
  2109. package/dist/utils/clipboard.js.map +1 -1
  2110. package/dist/utils/compile-cache.d.ts +8 -0
  2111. package/dist/utils/compile-cache.d.ts.map +1 -0
  2112. package/dist/utils/compile-cache.js +31 -0
  2113. package/dist/utils/compile-cache.js.map +1 -0
  2114. package/dist/utils/fs-watch.d.ts.map +1 -1
  2115. package/dist/utils/fs-watch.js +1 -3
  2116. package/dist/utils/fs-watch.js.map +1 -1
  2117. package/dist/utils/git.js +1 -1
  2118. package/dist/utils/git.js.map +1 -1
  2119. package/dist/utils/interactive-engine-bootstrap.d.ts +67 -0
  2120. package/dist/utils/interactive-engine-bootstrap.d.ts.map +1 -0
  2121. package/dist/utils/interactive-engine-bootstrap.js +117 -0
  2122. package/dist/utils/interactive-engine-bootstrap.js.map +1 -0
  2123. package/dist/utils/interactive-engine-env.d.ts +41 -0
  2124. package/dist/utils/interactive-engine-env.d.ts.map +1 -0
  2125. package/dist/utils/interactive-engine-env.js +67 -0
  2126. package/dist/utils/interactive-engine-env.js.map +1 -0
  2127. package/dist/utils/markit.d.ts.map +1 -1
  2128. package/dist/utils/markit.js +15 -5
  2129. package/dist/utils/markit.js.map +1 -1
  2130. package/dist/utils/shell.js +2 -2
  2131. package/dist/utils/shell.js.map +1 -1
  2132. package/dist/utils/split-launcher.d.ts.map +1 -1
  2133. package/dist/utils/split-launcher.js +1 -2
  2134. package/dist/utils/split-launcher.js.map +1 -1
  2135. package/dist/utils/tools-manager.d.ts.map +1 -1
  2136. package/dist/utils/tools-manager.js +8 -34
  2137. package/dist/utils/tools-manager.js.map +1 -1
  2138. package/dist/utils/version-check.d.ts +1 -0
  2139. package/dist/utils/version-check.d.ts.map +1 -1
  2140. package/dist/utils/version-check.js +7 -5
  2141. package/dist/utils/version-check.js.map +1 -1
  2142. package/docs/changelog.mdx +24 -2
  2143. package/docs/compaction.md +101 -18
  2144. package/docs/custom-provider.md +40 -8
  2145. package/docs/development.md +14 -15
  2146. package/docs/docs.json +12 -2
  2147. package/docs/environment-variables.md +37 -0
  2148. package/docs/extensions.md +166 -18
  2149. package/docs/index.md +1 -0
  2150. package/docs/intercom.md +1 -1
  2151. package/docs/json.md +11 -5
  2152. package/docs/keybindings.md +19 -2
  2153. package/docs/llama-cpp.md +72 -0
  2154. package/docs/models/artificial-analysis-index.md +82 -0
  2155. package/docs/models/model-selection.md +63 -0
  2156. package/docs/models/pareto-efficiency.md +64 -0
  2157. package/docs/models.md +41 -50
  2158. package/docs/packages.md +4 -2
  2159. package/docs/prompt-templates.md +1 -0
  2160. package/docs/providers.md +89 -23
  2161. package/docs/quickstart.md +21 -17
  2162. package/docs/rpc.md +97 -68
  2163. package/docs/sdk.md +68 -71
  2164. package/docs/security.md +15 -0
  2165. package/docs/session-format.md +6 -15
  2166. package/docs/sessions.md +1 -1
  2167. package/docs/settings.md +29 -18
  2168. package/docs/skills.md +8 -0
  2169. package/docs/subagents.md +24 -8
  2170. package/docs/themes.md +10 -1
  2171. package/docs/tui.md +14 -7
  2172. package/docs/usage.md +71 -2
  2173. package/docs/windows.md +1 -1
  2174. package/docs/workflows.md +1253 -534
  2175. package/examples/extensions/README.md +2 -2
  2176. package/examples/extensions/border-status-editor.ts +1 -6
  2177. package/examples/extensions/custom-compaction.ts +7 -2
  2178. package/examples/extensions/custom-footer.ts +1 -1
  2179. package/examples/extensions/custom-provider-anthropic/index.ts +14 -2
  2180. package/examples/extensions/custom-provider-gitlab-duo/index.ts +1 -1
  2181. package/examples/extensions/gondolin/index.ts +1 -1
  2182. package/examples/extensions/handoff.ts +9 -2
  2183. package/examples/extensions/hello.ts +1 -1
  2184. package/examples/extensions/message-renderer.ts +2 -2
  2185. package/examples/extensions/minimal-mode.ts +6 -6
  2186. package/examples/extensions/overlay-qa-focus-components.ts +0 -1
  2187. package/examples/extensions/overlay-qa-streaming-input-components.ts +2 -2
  2188. package/examples/extensions/overlay-qa-tests.ts +16 -14
  2189. package/examples/extensions/overlay-qa-toggle-passive-components.ts +3 -5
  2190. package/examples/extensions/overlay-test.ts +3 -6
  2191. package/examples/extensions/plan-mode/index.ts +1 -1
  2192. package/examples/extensions/preset.ts +1 -1
  2193. package/examples/extensions/qna.ts +1 -1
  2194. package/examples/extensions/sandbox/package-lock.json +3 -3
  2195. package/examples/extensions/structured-output.ts +9 -9
  2196. package/examples/extensions/subagent/display.ts +115 -127
  2197. package/examples/extensions/subagent/index.ts +310 -349
  2198. package/examples/extensions/subagent/render.ts +244 -345
  2199. package/examples/extensions/subagent/runner.ts +223 -230
  2200. package/examples/extensions/subagent/schemas.ts +39 -46
  2201. package/examples/extensions/subagent/types.ts +22 -22
  2202. package/examples/extensions/summarize.ts +4 -1
  2203. package/examples/extensions/tic-tac-toe.ts +4 -4
  2204. package/examples/extensions/todo.ts +1 -1
  2205. package/examples/extensions/tool-override.ts +1 -1
  2206. package/examples/extensions/trigger-compact.ts +4 -1
  2207. package/examples/extensions/working-indicator.ts +3 -2
  2208. package/examples/rpc-extension-ui-components.ts +1 -2
  2209. package/examples/sdk/02-custom-model.ts +8 -10
  2210. package/examples/sdk/03-custom-prompt.ts +1 -6
  2211. package/examples/sdk/06-extensions.ts +1 -6
  2212. package/examples/sdk/07-context-files.ts +1 -6
  2213. package/examples/sdk/09-api-keys-and-oauth.ts +17 -23
  2214. package/examples/sdk/12-full-control.ts +10 -12
  2215. package/examples/sdk/README.md +17 -21
  2216. package/npm-shrinkwrap.json +305 -314
  2217. package/package.json +17 -15
  2218. package/dist/builtin/cursor/CHANGELOG.md +0 -275
  2219. package/dist/builtin/cursor/LICENSE +0 -26
  2220. package/dist/builtin/cursor/README.md +0 -24
  2221. package/dist/builtin/cursor/index.ts +0 -9
  2222. package/dist/builtin/cursor/package.json +0 -47
  2223. package/dist/builtin/cursor/src/auth.ts +0 -352
  2224. package/dist/builtin/cursor/src/catalog-cache.ts +0 -155
  2225. package/dist/builtin/cursor/src/config.ts +0 -123
  2226. package/dist/builtin/cursor/src/conversation-state.ts +0 -135
  2227. package/dist/builtin/cursor/src/cursor-models-raw.json +0 -412
  2228. package/dist/builtin/cursor/src/model-mapper.ts +0 -369
  2229. package/dist/builtin/cursor/src/model-reference.ts +0 -282
  2230. package/dist/builtin/cursor/src/models.ts +0 -54
  2231. package/dist/builtin/cursor/src/native-loader.ts +0 -71
  2232. package/dist/builtin/cursor/src/proto/README.md +0 -34
  2233. package/dist/builtin/cursor/src/proto/agent_pb.ts +0 -15294
  2234. package/dist/builtin/cursor/src/proto/protobuf-codec-base64.ts +0 -22
  2235. package/dist/builtin/cursor/src/proto/protobuf-codec-json.ts +0 -44
  2236. package/dist/builtin/cursor/src/proto/protobuf-codec-request.ts +0 -311
  2237. package/dist/builtin/cursor/src/proto/protobuf-codec-wire.ts +0 -248
  2238. package/dist/builtin/cursor/src/proto/protobuf-codec.ts +0 -200
  2239. package/dist/builtin/cursor/src/provider.ts +0 -301
  2240. package/dist/builtin/cursor/src/stream.ts +0 -494
  2241. package/dist/builtin/cursor/src/transport-errors.ts +0 -74
  2242. package/dist/builtin/cursor/src/transport-frame.ts +0 -56
  2243. package/dist/builtin/cursor/src/transport-http2.ts +0 -122
  2244. package/dist/builtin/cursor/src/transport-native-client.ts +0 -161
  2245. package/dist/builtin/cursor/src/transport-run-stream.ts +0 -188
  2246. package/dist/builtin/cursor/src/transport-timeouts.ts +0 -87
  2247. package/dist/builtin/cursor/src/transport-types.ts +0 -143
  2248. package/dist/builtin/cursor/src/transport.ts +0 -26
  2249. package/dist/builtin/workflows/builtin/deep-research-codebase-runner.ts +0 -492
  2250. package/dist/builtin/workflows/builtin/deep-research-codebase-utils.ts +0 -383
  2251. package/dist/builtin/workflows/builtin/deep-research-codebase.d.ts +0 -35
  2252. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +0 -47
  2253. package/dist/builtin/workflows/skills/impeccable/reference/brand.md +0 -108
  2254. package/dist/builtin/workflows/skills/impeccable/reference/codex.md +0 -105
  2255. package/dist/builtin/workflows/skills/impeccable/reference/interaction-design.md +0 -189
  2256. package/dist/builtin/workflows/skills/impeccable/scripts/hook-before-edit.mjs +0 -516
  2257. package/dist/core/context-window.d.ts +0 -54
  2258. package/dist/core/context-window.d.ts.map +0 -1
  2259. package/dist/core/context-window.js +0 -110
  2260. package/dist/core/context-window.js.map +0 -1
  2261. package/dist/core/copilot-anthropic-sse-repair.d.ts +0 -23
  2262. package/dist/core/copilot-anthropic-sse-repair.d.ts.map +0 -1
  2263. package/dist/core/copilot-anthropic-sse-repair.js +0 -340
  2264. package/dist/core/copilot-anthropic-sse-repair.js.map +0 -1
  2265. package/dist/core/copilot-errors.d.ts +0 -9
  2266. package/dist/core/copilot-errors.d.ts.map +0 -1
  2267. package/dist/core/copilot-errors.js +0 -32
  2268. package/dist/core/copilot-errors.js.map +0 -1
  2269. package/dist/core/copilot-gemini-payload-sanitizer.d.ts +0 -72
  2270. package/dist/core/copilot-gemini-payload-sanitizer.d.ts.map +0 -1
  2271. package/dist/core/copilot-gemini-payload-sanitizer.js +0 -296
  2272. package/dist/core/copilot-gemini-payload-sanitizer.js.map +0 -1
  2273. package/dist/core/copilot-gemini-reasoning.d.ts +0 -126
  2274. package/dist/core/copilot-gemini-reasoning.d.ts.map +0 -1
  2275. package/dist/core/copilot-gemini-reasoning.js +0 -265
  2276. package/dist/core/copilot-gemini-reasoning.js.map +0 -1
  2277. package/dist/core/copilot-gemini-tool-arguments.d.ts +0 -42
  2278. package/dist/core/copilot-gemini-tool-arguments.d.ts.map +0 -1
  2279. package/dist/core/copilot-gemini-tool-arguments.js +0 -159
  2280. package/dist/core/copilot-gemini-tool-arguments.js.map +0 -1
  2281. package/dist/core/copilot-hosts.d.ts +0 -12
  2282. package/dist/core/copilot-hosts.d.ts.map +0 -1
  2283. package/dist/core/copilot-hosts.js +0 -33
  2284. package/dist/core/copilot-hosts.js.map +0 -1
  2285. package/dist/core/copilot-model-catalog.d.ts +0 -114
  2286. package/dist/core/copilot-model-catalog.d.ts.map +0 -1
  2287. package/dist/core/copilot-model-catalog.js +0 -392
  2288. package/dist/core/copilot-model-catalog.js.map +0 -1
  2289. package/dist/core/copilot-model-static-fallbacks.d.ts +0 -43
  2290. package/dist/core/copilot-model-static-fallbacks.d.ts.map +0 -1
  2291. package/dist/core/copilot-model-static-fallbacks.js +0 -50
  2292. package/dist/core/copilot-model-static-fallbacks.js.map +0 -1
  2293. package/dist/core/copilot-model-synthesis.d.ts +0 -10
  2294. package/dist/core/copilot-model-synthesis.d.ts.map +0 -1
  2295. package/dist/core/copilot-model-synthesis.js +0 -91
  2296. package/dist/core/copilot-model-synthesis.js.map +0 -1
  2297. package/dist/core/model-registry-auth.d.ts +0 -8
  2298. package/dist/core/model-registry-auth.d.ts.map +0 -1
  2299. package/dist/core/model-registry-auth.js +0 -83
  2300. package/dist/core/model-registry-auth.js.map +0 -1
  2301. package/dist/core/model-registry-builtins.d.ts +0 -10
  2302. package/dist/core/model-registry-builtins.d.ts.map +0 -1
  2303. package/dist/core/model-registry-builtins.js +0 -171
  2304. package/dist/core/model-registry-builtins.js.map +0 -1
  2305. package/dist/core/model-registry-custom-loader.d.ts +0 -3
  2306. package/dist/core/model-registry-custom-loader.d.ts.map +0 -1
  2307. package/dist/core/model-registry-custom-loader.js +0 -254
  2308. package/dist/core/model-registry-custom-loader.js.map +0 -1
  2309. package/dist/core/model-registry-dynamic.d.ts +0 -7
  2310. package/dist/core/model-registry-dynamic.d.ts.map +0 -1
  2311. package/dist/core/model-registry-dynamic.js +0 -201
  2312. package/dist/core/model-registry-dynamic.js.map +0 -1
  2313. package/dist/core/model-registry-loader.d.ts +0 -5
  2314. package/dist/core/model-registry-loader.d.ts.map +0 -1
  2315. package/dist/core/model-registry-loader.js +0 -28
  2316. package/dist/core/model-registry-loader.js.map +0 -1
  2317. package/dist/core/model-registry-schemas.d.ts +0 -1273
  2318. package/dist/core/model-registry-schemas.d.ts.map +0 -1
  2319. package/dist/core/model-registry-schemas.js.map +0 -1
  2320. package/dist/core/model-registry-types.d.ts +0 -82
  2321. package/dist/core/model-registry-types.d.ts.map +0 -1
  2322. package/dist/core/model-registry-types.js +0 -2
  2323. package/dist/core/model-registry-types.js.map +0 -1
  2324. package/dist/core/oauth-compat.d.ts +0 -17
  2325. package/dist/core/oauth-compat.d.ts.map +0 -1
  2326. package/dist/core/oauth-compat.js +0 -2
  2327. package/dist/core/oauth-compat.js.map +0 -1
  2328. package/dist/core/oauth-provider-bridge.d.ts +0 -40
  2329. package/dist/core/oauth-provider-bridge.d.ts.map +0 -1
  2330. package/dist/core/oauth-provider-bridge.js +0 -169
  2331. package/dist/core/oauth-provider-bridge.js.map +0 -1
  2332. package/dist/modes/interactive/components/context-window-selector.d.ts +0 -53
  2333. package/dist/modes/interactive/components/context-window-selector.d.ts.map +0 -1
  2334. package/dist/modes/interactive/components/context-window-selector.js +0 -136
  2335. package/dist/modes/interactive/components/context-window-selector.js.map +0 -1
  2336. package/dist/modes/rpc/bounded-writer.d.ts +0 -27
  2337. package/dist/modes/rpc/bounded-writer.d.ts.map +0 -1
  2338. package/dist/modes/rpc/bounded-writer.js +0 -103
  2339. package/dist/modes/rpc/bounded-writer.js.map +0 -1
@@ -82,6 +82,15 @@ const GENERIC_FONTS = new Set([
82
82
  const WCAG_LARGE_TEXT_PX = 18 * (96 / 72);
83
83
  const WCAG_LARGE_BOLD_TEXT_PX = 14 * (96 / 72);
84
84
 
85
+ // Em-dash overuse (advisory) thresholds, shared by the regex/static-HTML
86
+ // analyzer and the browser DOM check so both fire on the same saturation
87
+ // pattern. Two gates must hold: an absolute floor of EM_DASH_FLOOR dashes, and
88
+ // a density of at least one dash per EM_DASH_CHARS_PER_DASH characters of body
89
+ // text. A long article that uses a few em-dashes is left alone; a short,
90
+ // dash-per-clause page is not.
91
+ const EM_DASH_FLOOR = 8;
92
+ const EM_DASH_CHARS_PER_DASH = 500;
93
+
85
94
  // Serif faces that show up in italic-display heroes. The rule also fires when
86
95
  // the primary face is unknown but the stack ends in the generic `serif` token,
87
96
  // which catches custom/private faces with a serif fallback.
@@ -134,9 +143,9 @@ const ANTIPATTERNS = [
134
143
  id: 'single-font',
135
144
  category: 'slop',
136
145
  scopes: ['type'],
137
- name: 'Single font for everything',
146
+ name: 'Single font without hierarchy',
138
147
  description:
139
- 'Only one font family is used for the entire page. Pair a distinctive display font with a refined body font to create typographic hierarchy.',
148
+ 'Only one font family is used for the entire page. A single family can work when weight and size contrast carry the hierarchy; otherwise pair a distinctive display font with a refined body font.',
140
149
  skillSection: 'Typography',
141
150
  skillGuideline: 'only one font family for the entire page',
142
151
  },
@@ -206,15 +215,69 @@ const ANTIPATTERNS = [
206
215
  skillSection: 'Motion',
207
216
  skillGuideline: 'bounce or elastic easing',
208
217
  },
218
+ {
219
+ id: 'pulsing-dot',
220
+ category: 'slop',
221
+ name: 'Pulsing status dot',
222
+ description:
223
+ 'Small pulsing status dots simulate liveness decoratively. Reserve pulse animation for indicators tied to genuinely live, changing data; a static indicator with clear labeling is honest and calmer.',
224
+ skillSection: 'Motion',
225
+ skillGuideline: 'decorative pulsing status dot',
226
+ },
227
+ {
228
+ id: 'blinking-cursor',
229
+ category: 'slop',
230
+ severity: 'advisory',
231
+ name: 'Decorative blinking cursor',
232
+ description:
233
+ 'A blinking text cursor animated into a hero or landing section simulates typing where no input exists. It borrows the dev-tool aesthetic as decoration. Real editable fields draw their own caret; anywhere else, let the composition hold attention without a fake prompt.',
234
+ skillSection: 'Motion',
235
+ },
236
+ {
237
+ id: 'shape-assembled-illustration',
238
+ category: 'slop',
239
+ severity: 'advisory',
240
+ name: 'Shape-assembled illustration',
241
+ description:
242
+ 'A large inline SVG that builds a pictorial scene from a pile of primitive shapes reads as placeholder clip art, not illustration. Icons, logos, and data graphics are fine at their scale; a hero-sized visual deserves real artwork, a photograph, or a deliberately drawn graphic.',
243
+ skillSection: 'Imagery',
244
+ },
209
245
  {
210
246
  id: 'dark-glow',
211
247
  category: 'slop',
212
- name: 'Dark mode with glowing accents',
248
+ name: 'Glowing shadow accents',
249
+ description:
250
+ 'Colored glow shadows — a zero-offset chromatic halo (box- or text-shadow) on any background, or any colored blurred shadow on a dark background — are the default "cool" look of AI-generated UIs. Use neutral elevation shadows and subtle, purposeful lighting instead.',
251
+ skillSection: 'Color & Contrast',
252
+ skillGuideline: 'dark mode with glowing accents',
253
+ },
254
+ {
255
+ id: 'radial-halo',
256
+ category: 'slop',
257
+ name: 'Radial-gradient background halo',
258
+ description:
259
+ 'A chromatic radial-gradient wash — saturated at the center, fading to transparent — used as a decorative background glow on a dark page. Same tell as glowing shadows, drawn with a gradient instead of a shadow. Ground the surface with a solid or subtly shifted background instead.',
260
+ skillSection: 'Color & Contrast',
261
+ skillGuideline: 'dark mode with glowing accents',
262
+ },
263
+ {
264
+ id: 'radial-spotlight-glow',
265
+ category: 'slop',
266
+ name: 'Decorative radial spotlight glow',
213
267
  description:
214
- 'Dark backgrounds with colored box-shadow glows are the default "cool" look of AI-generated UIs. Use subtle, purposeful lighting instead or skip the dark theme entirely.',
268
+ 'A soft, low-opacity accent-colored radial gradient fading to transparent, dropped behind a hero or section as a "spotlight." It is a reflex AI decoration — the translucent cousin of the saturated radial halo. Let the surface stand on its own, or light the composition with a deliberate material accent rather than a floating colored haze.',
215
269
  skillSection: 'Color & Contrast',
216
270
  skillGuideline: 'dark mode with glowing accents',
217
271
  },
272
+ {
273
+ id: 'marquee',
274
+ category: 'slop',
275
+ name: 'Auto-scrolling marquee',
276
+ description:
277
+ 'Continuously auto-scrolling content demands attention it has not earned and hides half its content at any moment. Reserve motion for content that changes; let readers move at their own pace.',
278
+ skillSection: 'Motion',
279
+ skillGuideline: 'auto-scrolling marquee',
280
+ },
218
281
  {
219
282
  id: 'icon-tile-stack',
220
283
  category: 'slop',
@@ -246,33 +309,37 @@ const ANTIPATTERNS = [
246
309
  skillGuideline: 'tiny uppercase tracked label above the hero headline',
247
310
  },
248
311
  {
249
- id: 'repeated-section-kickers',
312
+ id: 'kicker-above-heading',
250
313
  category: 'slop',
251
314
  scopes: ['type'],
252
- severity: 'advisory',
253
- name: 'Repeated section kicker labels',
315
+ name: 'Kicker / eyebrow label above heading',
254
316
  description:
255
- 'Repeating tiny uppercase tracked labels above section headings turns a brand page into AI editorial scaffolding. Replace them with stronger structure, artifacts, imagery, or a deliberate brand system.',
317
+ 'A tiny tracked uppercase or small-caps label sitting as its own block directly above a heading is banned outright, repeated or not. Generated kickers never earn their place: the heading carries its own weight. Delete the label and let the heading speak; if the words matter, work them into the heading or the body.',
256
318
  skillSection: 'Typography',
257
- skillGuideline: 'repeated eyebrow or kicker labels as section scaffolding',
319
+ skillGuideline: 'kicker or eyebrow labels above headings',
258
320
  },
259
321
  {
260
- id: 'numbered-section-markers',
322
+ id: 'numbered-section-labels',
261
323
  category: 'slop',
262
- scopes: ['layout'],
324
+ scopes: ['type'],
263
325
  severity: 'advisory',
264
- name: 'Numbered section markers (01 / 02 / 03)',
326
+ name: 'Tiny numbered section labels',
265
327
  description:
266
- 'Numbered display markers as section labels (01, 02, 03) are the AI editorial scaffold one tier deeper than tracked eyebrow chips. If you find yourself reaching for them, choose a different section cadence.',
328
+ 'Small numeric index labels riding next to section headings, repeated section after section, are AI editorial scaffolding a page numbering its own chapters instead of earning structure. Let hierarchy, content, and rhythm carry the sequence.',
267
329
  skillSection: 'Layout & Space',
268
330
  skillGuideline: 'numbered section markers',
269
331
  },
270
332
  {
271
333
  id: 'em-dash-overuse',
272
334
  category: 'slop',
335
+ // Advisory: humans use em-dashes legitimately, so this rule is opt-in noise
336
+ // rather than a failure. It fires only on the AI saturation pattern, not on
337
+ // ordinary prose. Advisory findings are surfaced separately, never counted
338
+ // as failures, and skipped by the design hook unless a project opts in.
339
+ advisory: true,
273
340
  name: 'Em-dash overuse',
274
341
  description:
275
- 'More than two em-dashes (— or --) in body copy is an AI cadence tell. Use commas, colons, periods, or parentheses instead.',
342
+ 'Em-dash saturation in body copy is an AI cadence tell. Advisory only: humans use em-dashes legitimately, so this fires only on saturation — at least 8 em-dashes (— or --) at a density near one per 500 characters of body text never on a long article that uses a few. Prefer commas, colons, periods, or parentheses.',
276
343
  skillSection: 'Copy',
277
344
  skillGuideline: 'no em dashes',
278
345
  },
@@ -325,6 +392,49 @@ const ANTIPATTERNS = [
325
392
  },
326
393
 
327
394
  // ── Quality: general design and accessibility issues ──
395
+ {
396
+ id: 'script-error',
397
+ category: 'quality',
398
+ severity: 'error',
399
+ name: 'Uncaught script error on load',
400
+ description:
401
+ 'A script threw an uncaught exception or failed to parse while the page loaded. Broken JavaScript silently kills reveals, interactions, and dynamic content, and can leave most of a page invisible. Fix the error before judging anything else.',
402
+ },
403
+ {
404
+ id: 'content-hidden-at-rest',
405
+ category: 'quality',
406
+ severity: 'error',
407
+ scopes: ['layout'],
408
+ name: 'Content invisible at rest',
409
+ description:
410
+ 'A large share of the page text sits at opacity 0 or visibility hidden even after every reveal handler had a chance to run. This is the failed-reveal signature: the content shipped but never becomes visible. Make content visible by default and let JavaScript enhance its entrance instead of gating its existence.',
411
+ },
412
+ {
413
+ id: 'edge-flush-cards',
414
+ category: 'quality',
415
+ scopes: ['layout'],
416
+ name: 'Cards flush against the scroller edge',
417
+ description:
418
+ 'Cards inside a horizontal scroller or tab panel sit flush against the container edge at rest while keeping a gutter on the other side, so their edges and rounded corners get cut off. Usually the panel is sized wider than its clip box. Keep a consistent inset on both sides.',
419
+ },
420
+ {
421
+ id: 'text-occlusion',
422
+ category: 'quality',
423
+ scopes: ['layout'],
424
+ name: 'Text occluded by an overlapping element',
425
+ description:
426
+ 'Text is painted under an opaque element or a second text run, so part of it cannot be read. A decorative box, a stacked layer, or an inline element with leaked padding lands on the words instead of beside them. Give overlapping layers room, or move the text out from under the layer above it.',
427
+ skillSection: 'Layout & Space',
428
+ },
429
+ {
430
+ id: 'first-viewport-column-overflow',
431
+ category: 'quality',
432
+ scopes: ['layout'],
433
+ name: 'One column stretches the first viewport',
434
+ description:
435
+ 'A multi-column opening section lets one column run far past the fold while its sibling fits in a single viewport, so the short column floats in dead space and the fold falls deep inside one section. Balance the columns, cap the tall one, or let the long content flow below the opening row.',
436
+ skillSection: 'Layout & Space',
437
+ },
328
438
  {
329
439
  id: 'gray-on-color',
330
440
  category: 'quality',
@@ -394,6 +504,15 @@ const ANTIPATTERNS = [
394
504
  description:
395
505
  'Heading levels should not skip (e.g. h1 then h3 with no h2). Screen readers use heading hierarchy for navigation. Skipping levels breaks the document outline.',
396
506
  },
507
+ {
508
+ id: 'heading-rhythm',
509
+ category: 'quality',
510
+ scopes: ['layout', 'type'],
511
+ name: 'Heading crowded against the previous block',
512
+ description:
513
+ 'A heading binds to the content it introduces, so the rendered space above it should exceed the space below it. When headings across a page sit as close or closer to the block above than to their own content, every section reads as if it captions the previous one. Open up the space above each heading.',
514
+ skillSection: 'Layout & Space',
515
+ },
397
516
  {
398
517
  id: 'justified-text',
399
518
  category: 'quality',
@@ -410,6 +529,14 @@ const ANTIPATTERNS = [
410
529
  description:
411
530
  'Body text below 12px is hard to read, especially on high-DPI screens. Use at least 14px for body content, 16px is ideal.',
412
531
  },
532
+ {
533
+ id: 'undersized-ui-text',
534
+ category: 'quality',
535
+ scopes: ['type'],
536
+ name: 'Undersized functional text',
537
+ description:
538
+ 'Interactive and content-bearing UI text (links, buttons, nav items, labels, table cells, meta rows, timecodes) below 11px is a legibility failure, not a style choice. WCAG sets no absolute pixel floor, but functional text under 11px is a defensible quality bar: it fails on high-DPI and small viewports and it degrades tap and read targets. The 11px floor holds even inside a footer; only non-interactive legal smallprint gets the softer 10px floor. Being ON the DESIGN.md size ramp does not exempt a value here: adding 8px to the ramp launders the token but not the legibility problem, and that is exactly the escape hatch this rule closes. Exempts sup/sub, visually-hidden (sr-only) text, and code/terminal contexts. Decorative letterspaced micro-labels are still functional and stay in scope.',
539
+ },
413
540
  {
414
541
  id: 'all-caps-body',
415
542
  category: 'quality',
@@ -438,6 +565,13 @@ const ANTIPATTERNS = [
438
565
  skillSection: 'Layout & Space',
439
566
  skillGuideline: 'content wider than its container',
440
567
  },
568
+ {
569
+ id: 'repeated-container-text',
570
+ category: 'quality',
571
+ name: 'Same text repeated inside one container',
572
+ description:
573
+ 'The same literal text rendered three or more times in structurally different spots inside a single card or panel is redundant messaging — usually a status or label wired into every slot of a template. Say it once, in the slot where it matters most.',
574
+ },
441
575
  {
442
576
  id: 'clipped-overflow-container',
443
577
  category: 'quality',
@@ -490,12 +624,11 @@ const ANTIPATTERNS = [
490
624
  skillGuideline: 'font size outside the project design system',
491
625
  },
492
626
 
493
- // ── Provider tells: opt-in via --gpt / --gemini (gated off by default) ──
627
+ // ── Common generated-UI tells ───────────────────────────────────────────
494
628
  {
495
629
  id: 'gpt-thin-border-wide-shadow',
496
630
  category: 'slop',
497
631
  severity: 'advisory',
498
- gated: 'gpt',
499
632
  name: 'Hairline border with wide shadow',
500
633
  description:
501
634
  'A hairline border paired with a wide, diffuse shadow is a recurring generated-UI signature. Commit to one — a defined edge or a soft elevation — rather than both at once.',
@@ -506,7 +639,6 @@ const ANTIPATTERNS = [
506
639
  id: 'repeating-stripes-gradient',
507
640
  category: 'slop',
508
641
  severity: 'advisory',
509
- gated: 'gpt',
510
642
  name: 'Repeating-gradient stripes',
511
643
  description:
512
644
  'Repeating-gradient stripes used as surface decoration are a recurring generated-UI signature. Reach for a deliberate texture or leave the surface plain.',
@@ -517,10 +649,9 @@ const ANTIPATTERNS = [
517
649
  id: 'codex-grid-background',
518
650
  category: 'slop',
519
651
  severity: 'advisory',
520
- gated: 'gpt',
521
652
  name: 'Decorative grid-line background',
522
653
  description:
523
- 'A two-axis grid drawn with hairline linear-gradient layers ("1px, transparent 1px" on both axes) is a recurring generated-UI signature. Reserve grid overlays for actual canvas, map, blueprint, or measurement surfaces; elsewhere use product structure or a plain surface.',
654
+ 'A decorative grid or line-field background drawn with hairline linear-gradient layers tiled by a fixed pixel cell is a recurring generated-UI signature. Reserve grid overlays for actual canvas, map, blueprint, or measurement surfaces; elsewhere use product structure or a plain surface.',
524
655
  skillSection: 'Visual Details',
525
656
  skillGuideline: 'two-axis grid-line gradient background',
526
657
  },
@@ -528,7 +659,6 @@ const ANTIPATTERNS = [
528
659
  id: 'theater-slop-phrase',
529
660
  category: 'slop',
530
661
  severity: 'advisory',
531
- gated: 'gpt',
532
662
  name: 'Theater framing copy',
533
663
  description:
534
664
  'Dismissing something as "theater" is a recurring generated-copy tic. Say plainly what the thing does or does not do.',
@@ -539,7 +669,6 @@ const ANTIPATTERNS = [
539
669
  id: 'image-hover-transform',
540
670
  category: 'slop',
541
671
  severity: 'advisory',
542
- gated: 'gemini',
543
672
  name: 'Image hover transform',
544
673
  description:
545
674
  'Scaling or rotating an image on hover is a recurring generated-UI signature. Let imagery sit still, or use a subtler, purposeful interaction.',
@@ -698,8 +827,15 @@ const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
698
827
 
699
828
  // ─── Section 3: Pure Detection ──────────────────────────────────────────────
700
829
 
701
- function checkBorders(tag, widths, colors, radius) {
702
- if (BORDER_SAFE_TAGS.has(tag)) return [];
830
+ function checkBorders(tag, widths, colors, radius, opts = {}) {
831
+ // Badge-shaped <span>s (own visible background) are a real stripe target
832
+ // for the top/bottom variant — the inline-tag exemption exists to quiet
833
+ // text-level borders, not chips. They skip the left/right arms below.
834
+ const spanBadge = tag === 'span' && !!opts.badgeLike;
835
+ if (BORDER_SAFE_TAGS.has(tag) && !spanBadge) return [];
836
+ // A live status/alert region wears a colored single-edge border as a
837
+ // severity accent (toast, snackbar, callout), not as the side-tab tell.
838
+ if (opts.statusContext) return [];
703
839
  const findings = [];
704
840
  const sides = ['Top', 'Right', 'Bottom', 'Left'];
705
841
 
@@ -715,10 +851,20 @@ function checkBorders(tag, widths, colors, radius) {
715
851
  const isSide = side === 'Left' || side === 'Right';
716
852
 
717
853
  if (isSide) {
854
+ if (spanBadge) continue;
718
855
  if (radius > 0) findings.push({ id: 'side-tab', snippet: `border-${sn}: ${w}px + border-radius: ${radius}px` });
719
856
  else if (w >= 3) findings.push({ id: 'side-tab', snippet: `border-${sn}: ${w}px` });
720
857
  } else {
721
858
  if (radius > 0 && w >= 2) findings.push({ id: 'border-accent-on-rounded', snippet: `border-${sn}: ${w}px + border-radius: ${radius}px` });
859
+ // Horizontal variant of the side-tab stripe: a thick chromatic accent
860
+ // riding the top or bottom edge of a card/badge/container. Same
861
+ // dominant-edge + chroma gates as left/right, 3-12px band. Selected-
862
+ // tab underlines are exempt via opts.tabContext (adapters look for
863
+ // tablist/nav/tab ancestors and aria-selected); links, buttons,
864
+ // table cells, and <hr> never reach here (BORDER_SAFE_TAGS).
865
+ else if (!opts.tabContext && w >= 3 && w <= 12) {
866
+ findings.push({ id: 'side-tab', snippet: `border-${sn}: ${w}px` });
867
+ }
722
868
  }
723
869
  }
724
870
 
@@ -740,23 +886,45 @@ function isEmojiOnlyText(text) {
740
886
  function checkColors(opts) {
741
887
  const { tag, textColor, bgColor, effectiveBg, effectiveBgStops, fontSize, fontWeight, hasDirectText, isEmojiOnly, bgClip, bgImage, classList } = opts;
742
888
  if (SAFE_TAGS.has(tag)) {
743
- // Exception for <a> and <button> elements styled as buttons. SAFE_TAGS
744
- // exists to suppress contrast noise on inline links and unstyled controls,
745
- // where the element has no own background and the contrast against the
746
- // ancestor surface is already the intended visual. When the element has
747
- // its own opaque background and direct text, it is a styled button and
748
- // contrast on its own surface is a real, frequent bug worth flagging.
749
- const isStyledButton = (tag === 'a' || tag === 'button')
750
- && hasDirectText
751
- && bgColor && bgColor.a > 0.5;
752
- if (!isStyledButton) return [];
889
+ // Exception for elements styled as controls or chips. SAFE_TAGS exists to
890
+ // suppress contrast noise on inline links and unstyled spans, where the
891
+ // element has no own background and the contrast against the ancestor
892
+ // surface is already the intended visual. When the element paints its own
893
+ // opaque background under direct text, it is a styled button, chip, or
894
+ // badge regardless of tag, and contrast on its own surface is a real,
895
+ // frequent bug worth flagging. (The shipped miss: a <span> severity chip
896
+ // whose white text lost a specificity fight and rendered muted-on-red at
897
+ // 1.2:1; the old a/button-only exception never looked at it.) The 9px
898
+ // font floor keeps sub-text decorations out.
899
+ const isStyledControl = hasDirectText
900
+ && ((bgColor && bgColor.a > 0.5)
901
+ // A gradient painted on the element itself is an own surface the
902
+ // same way a solid background is. Without this branch a nav CTA
903
+ // built as `<a>` with `background: linear-gradient(…)` and a text
904
+ // color that fails against every stop sails through on the
905
+ // SAFE_TAGS suppression (the shipped escape).
906
+ || (bgImage && /gradient/i.test(bgImage)))
907
+ && fontSize >= 9;
908
+ if (!isStyledControl) return [];
753
909
  }
754
910
  const findings = [];
755
911
 
756
912
  if (hasDirectText && textColor && !isEmojiOnly) {
913
+ // Gradient-clipped text (`background-clip: text`, typically with a
914
+ // transparent text-fill) paints its glyphs *with* the element's own
915
+ // gradient. The `color` value the cascade still reports is never painted,
916
+ // and the gradient is the fill, not a backdrop — so measuring `color`
917
+ // against that gradient (which resolveGradientStops picks up as the
918
+ // element's own background-image) is a guaranteed false positive
919
+ // (issue #409 Case A). Skip the backdrop-contrast checks; the gradient-text
920
+ // rule below still flags the pattern itself. Skipping a rule beats a false
921
+ // positive here — the true painted contrast can't be measured from `color`.
922
+ const isGradientClippedText = bgClip === 'text';
757
923
  // Run background-dependent checks against either a solid bg or, if the
758
924
  // ancestor is a gradient, against every gradient stop (use the worst case).
759
- const bgs = effectiveBg ? [effectiveBg] : (effectiveBgStops && effectiveBgStops.length ? effectiveBgStops : null);
925
+ const bgs = isGradientClippedText
926
+ ? null
927
+ : (effectiveBg ? [effectiveBg] : (effectiveBgStops && effectiveBgStops.length ? effectiveBgStops : null));
760
928
  if (bgs) {
761
929
  // Gray on colored background — flag if every stop is chromatic
762
930
  const textLum = relativeLuminance(textColor);
@@ -786,7 +954,11 @@ function checkColors(opts) {
786
954
  // like `text-paper/60` on `bg-ink` sections are the FP pattern.
787
955
  const isAlphaFallbackFP = !DETECTOR_IS_BROWSER && !effectiveBg && (textColor.a != null && textColor.a < 1);
788
956
  if (!isAlphaFallbackFP) {
789
- findings.push({ id: 'low-contrast', snippet: `${ratio.toFixed(1)}:1 (need ${threshold}:1) text ${colorToHex(textColor)} on ${colorToHex(bgs[worstIdx])}` });
957
+ // Near-threshold ratios (e.g. 4.497) would round to the threshold
958
+ // itself at one decimal and read as "4.5 needs 4.5" — show two
959
+ // decimals there so the finding stays legible.
960
+ const ratioLabel = ratio.toFixed(1) === threshold.toFixed(1) ? ratio.toFixed(2) : ratio.toFixed(1);
961
+ findings.push({ id: 'low-contrast', snippet: `${ratioLabel}:1 (need ${threshold}:1) — text ${colorToHex(textColor)} on ${colorToHex(bgs[worstIdx])}` });
790
962
  }
791
963
  }
792
964
  }
@@ -832,6 +1004,27 @@ function checkColors(opts) {
832
1004
  return findings;
833
1005
  }
834
1006
 
1007
+ // WCAG contrast for the :hover state of an element whose hover rules change
1008
+ // its text color and/or background. The classic miss: a nav CTA whose
1009
+ // author-intended hover pair passes AA, but a broader selector (e.g.
1010
+ // `.nav-links a:hover`) wins the specificity fight and swaps in a color
1011
+ // that fails. Only fires on elements that present as styled controls —
1012
+ // direct text plus an opaque-ish own background in either state — so plain
1013
+ // inline links keep the same suppression they get in checkColors.
1014
+ function checkHoverContrast(opts) {
1015
+ const { tag, textColor, bg, ownBgAlpha, fontSize, fontWeight, hasDirectText, isEmojiOnly } = opts;
1016
+ if (!hasDirectText || isEmojiOnly || !textColor || !bg) return [];
1017
+ if (SAFE_TAGS.has(tag) && !(ownBgAlpha != null && ownBgAlpha > 0.5)) return [];
1018
+ const ratio = contrastRatio(textColor, bg);
1019
+ const isLargeText = fontSize >= WCAG_LARGE_TEXT_PX || (fontSize >= WCAG_LARGE_BOLD_TEXT_PX && fontWeight >= 700);
1020
+ const threshold = isLargeText ? 3.0 : 4.5;
1021
+ if (ratio >= threshold) return [];
1022
+ return [{
1023
+ id: 'low-contrast',
1024
+ snippet: `:hover state ${ratio.toFixed(1)}:1 (need ${threshold}:1) — text ${colorToHex(textColor)} on ${colorToHex(bg)}`,
1025
+ }];
1026
+ }
1027
+
835
1028
  function isCardLikeFromProps(hasShadow, hasBorder, hasRadius, hasBg) {
836
1029
  if (!hasShadow && !hasBorder) return false;
837
1030
  return hasRadius || hasBg;
@@ -977,26 +1170,57 @@ function isAccentColor(cssColor) {
977
1170
  return false;
978
1171
  }
979
1172
 
1173
+ function resolveHeroHeadingSizePx(value) {
1174
+ const input = String(value || '').trim().toLowerCase();
1175
+ if (!input) return 0;
1176
+
1177
+ const simpleLengthPx = (token) => {
1178
+ const match = /^(-?\d*\.?\d+)\s*(px|rem|em|%)?$/.exec(String(token || '').trim());
1179
+ if (!match) return null;
1180
+ const amount = Number(match[1]);
1181
+ if (!Number.isFinite(amount)) return null;
1182
+ if (match[2] === 'rem' || match[2] === 'em') return amount * 16;
1183
+ if (match[2] === '%') return amount * 0.16;
1184
+ return amount;
1185
+ };
1186
+
1187
+ const direct = simpleLengthPx(input);
1188
+ if (direct !== null) return direct;
1189
+
1190
+ // Static CSS engines cannot resolve viewport units, but clamp's min/max
1191
+ // bounds still tell us whether the heading can ever reach hero scale.
1192
+ const clamp = /^clamp\((.*)\)$/.exec(input);
1193
+ if (clamp) {
1194
+ const parts = clamp[1].split(',');
1195
+ if (parts.length === 3) {
1196
+ const bounds = [simpleLengthPx(parts[0]), simpleLengthPx(parts[2])]
1197
+ .filter((candidate) => candidate !== null);
1198
+ if (bounds.length > 0) return Math.max(...bounds);
1199
+ }
1200
+ }
1201
+
1202
+ return 0;
1203
+ }
1204
+
980
1205
  // Sibling-relationship rule. Anchor on a hero-scale h1, look at the
981
1206
  // previousElementSibling, and gate on EITHER the classic tracked-
982
1207
  // uppercase eyebrow OR the modern accent-colored bold eyebrow.
983
1208
  function checkHeroEyebrow(opts) {
984
1209
  const {
985
1210
  headingTag, headingText, headingFontSize,
1211
+ headingInApplicationContext,
986
1212
  siblingTag, siblingText, siblingTextTransform,
987
1213
  siblingFontSize, siblingLetterSpacing,
988
1214
  siblingFontWeight, siblingColor,
1215
+ siblingHasAccentDashPseudo,
989
1216
  } = opts;
990
1217
  if (headingTag !== 'h1') return [];
991
- // We previously gated on headingFontSize >= 48 to anchor "hero scale".
992
- // But modern hero h1s use clamp() / vw / var(--text-*), none of which
993
- // jsdom can resolve the computed value comes back as "2em" or
994
- // "var(--text-9xl)" and parseFloat returns 2 or NaN. The gate fails
995
- // on virtually every Tailwind v4 / framework build. The other gates
996
- // (sibling text 2-60 chars, font-size ≤ 14px, accent-bold OR
997
- // tracked-caps) are tight enough to avoid false positives on non-
998
- // hero h1s — a tiny tan label directly above any h1 is the
999
- // antipattern regardless of how big the h1 ends up.
1218
+ // This is specifically a marketing-hero cliché, not a ban on compact
1219
+ // context labels in product UI (for example, a station name inside a tab
1220
+ // panel). Browser-computed sizes are reliable; the static adapter also
1221
+ // resolves ordinary px/rem/em and clamp() bounds before reaching here.
1222
+ if (headingInApplicationContext) return [];
1223
+ if (!(headingFontSize >= 48)) return [];
1000
1224
  if (!siblingTag) return [];
1001
1225
  // An h2 above an h1 is a different anti-pattern (heading hierarchy / dual
1002
1226
  // headings) — never an eyebrow.
@@ -1017,23 +1241,31 @@ function checkHeroEyebrow(opts) {
1017
1241
  const weight = Number(siblingFontWeight) || 400;
1018
1242
  const isAccentBold = weight >= 700 && isAccentColor(siblingColor || '');
1019
1243
 
1020
- if (!isClassicTracked && !isAccentBold) return [];
1244
+ // Branch C: dash-prefix eyebrow — sentence case, low tracking, regular
1245
+ // weight, but announced by a short chromatic ::before/::after bar
1246
+ // (the kicker dash). Same label-above-headline pattern, third styling.
1247
+ const isDashPrefixed = !!siblingHasAccentDashPseudo;
1248
+
1249
+ if (!isClassicTracked && !isAccentBold && !isDashPrefixed) return [];
1021
1250
 
1022
1251
  const headingTextSnippet = (headingText || '').trim().slice(0, 60);
1023
1252
  const eyebrowSnippet = text.slice(0, 40);
1024
- const style = isClassicTracked ? 'tracked-caps' : 'accent-bold';
1253
+ const style = isClassicTracked ? 'tracked-caps' : isAccentBold ? 'accent-bold' : 'dash-prefix';
1025
1254
  return [{
1026
1255
  id: 'hero-eyebrow-chip',
1027
1256
  snippet: `eyebrow chip (${style}) "${eyebrowSnippet}" above ${headingTag} "${headingTextSnippet}"`,
1028
1257
  }];
1029
1258
  }
1030
1259
 
1031
- function checkRepeatedSectionKickers(opts) {
1032
- const { candidates, minCount = 3 } = opts;
1033
- if (!Array.isArray(candidates) || candidates.length < minCount) return [];
1260
+ // Outright ban: one kicker is one too many, so every collected candidate is
1261
+ // a finding. The judgment lives in the candidate gate (isKickerCandidate) and
1262
+ // the collector's context skips, not in a repetition count.
1263
+ function checkKickerAboveHeading(opts) {
1264
+ const { candidates } = opts;
1265
+ if (!Array.isArray(candidates)) return [];
1034
1266
  return candidates.map(candidate => ({
1035
- id: 'repeated-section-kickers',
1036
- snippet: `repeated section kicker "${candidate.kickerText}" before ${candidate.headingTag} "${candidate.headingText}" (${candidates.length} on page)`,
1267
+ id: 'kicker-above-heading',
1268
+ snippet: `kicker "${candidate.kickerText}" above ${candidate.headingTag} "${candidate.headingText}"`,
1037
1269
  }));
1038
1270
  }
1039
1271
 
@@ -1082,38 +1314,882 @@ function checkMotion(opts) {
1082
1314
  return findings;
1083
1315
  }
1084
1316
 
1317
+ // Locate the color token in a single shadow layer. Returns
1318
+ // { color, start, end } where color is the parsed {r,g,b,a} (null when the
1319
+ // token exists but can't be parsed — e.g. an unresolved var() or an exotic
1320
+ // color space), or null when no color token is present at all. Handles both
1321
+ // serialization orders: computed style puts the color first
1322
+ // ("rgb(…) 0px 0px 20px"), authored CSS usually puts it last
1323
+ // ("0 0 20px #3b82f6").
1324
+ function findShadowColor(layer) {
1325
+ const fn = layer.match(/(?:rgba?|hsla?|hwb|oklch|oklab|lch|lab|color)\([^)]*\)/i);
1326
+ if (fn) return { color: parseAnyColor(fn[0]), start: fn.index, end: fn.index + fn[0].length };
1327
+ const hex = layer.match(/#[0-9a-fA-F]{3,8}\b/);
1328
+ if (hex) return { color: parseAnyColor(hex[0]), start: hex.index, end: hex.index + hex[0].length };
1329
+ const wordRe = /[a-zA-Z][a-zA-Z]*/g;
1330
+ let m;
1331
+ while ((m = wordRe.exec(layer)) !== null) {
1332
+ const named = CSS_NAMED_COLORS[m[0].toLowerCase()];
1333
+ if (named) return { color: { ...named, a: 1 }, start: m.index, end: m.index + m[0].length };
1334
+ }
1335
+ return null;
1336
+ }
1337
+
1338
+ // Extract the length values of a shadow layer in declaration order, with the
1339
+ // color token removed so its components aren't misread as lengths. Handles
1340
+ // computed-style px values AND authored unitless zeros ("0 0 20px"); rem/em
1341
+ // approximate at 16px. Result order is offset-x, offset-y, blur, [spread].
1342
+ function extractShadowLengths(layer, colorStart, colorEnd) {
1343
+ const stripped = colorStart != null
1344
+ ? layer.slice(0, colorStart) + ' ' + layer.slice(colorEnd)
1345
+ : layer;
1346
+ const vals = [];
1347
+ const re = /(-?\d*\.?\d+)(px|rem|em)?/g;
1348
+ let m;
1349
+ while ((m = re.exec(stripped)) !== null) {
1350
+ let v = parseFloat(m[1]);
1351
+ if (m[2] === 'rem' || m[2] === 'em') v *= 16;
1352
+ vals.push(v);
1353
+ }
1354
+ return vals;
1355
+ }
1356
+
1085
1357
  function checkGlow(opts) {
1086
- const { boxShadow, effectiveBg } = opts;
1087
- if (!boxShadow || boxShadow === 'none') return [];
1088
- if (!effectiveBg) return [];
1358
+ const { boxShadow, textShadow, effectiveBg } = opts;
1359
+ const onDarkBg = effectiveBg ? relativeLuminance(effectiveBg) < 0.1 : false;
1360
+
1361
+ // Scan one shadow list. Two glow tells, in any color format:
1362
+ // 1. Zero-offset chromatic halo (0 0 Npx <color>) — slop on ANY
1363
+ // background; the light radiates evenly outward, which is never how
1364
+ // real elevation shadows behave. Achromatic zero-offset shadows stay
1365
+ // legal (soft ambient elevation), as do focus rings (blur 0).
1366
+ // 2. Any chromatic shadow with real blur on a dark background — the
1367
+ // classic dark-mode glow accent.
1368
+ const scan = (value, prop) => {
1369
+ if (!value || value === 'none') return null;
1370
+ // Split multiple shadows (commas not inside parentheses)
1371
+ for (const layer of value.split(/,(?![^(]*\))/)) {
1372
+ const colorInfo = findShadowColor(layer);
1373
+ // No color token, or one we can't resolve (unresolved var(), exotic
1374
+ // color space): don't guess — skip rather than false-positive.
1375
+ if (!colorInfo || !colorInfo.color) continue;
1376
+ const color = colorInfo.color;
1377
+ if (!hasChroma(color, 30)) continue;
1378
+ const vals = extractShadowLengths(layer, colorInfo.start, colorInfo.end);
1379
+ // Third value is blur (offset-x, offset-y, blur, [spread])
1380
+ if (vals.length < 3 || vals[2] <= 4) continue;
1381
+ if (vals[0] === 0 && vals[1] === 0) {
1382
+ return { id: 'dark-glow', snippet: `Zero-offset ${prop} glow (${colorToHex(color)})` };
1383
+ }
1384
+ if (onDarkBg) {
1385
+ return { id: 'dark-glow', snippet: `Colored ${prop} glow (${colorToHex(color)}) on dark background` };
1386
+ }
1387
+ }
1388
+ return null;
1389
+ };
1089
1390
 
1090
- // Only flag on dark backgrounds (luminance < 0.1)
1091
- const bgLum = relativeLuminance(effectiveBg);
1092
- if (bgLum >= 0.1) return [];
1391
+ const found = scan(boxShadow, 'box-shadow') || scan(textShadow, 'text-shadow');
1392
+ return found ? [found] : [];
1393
+ }
1394
+
1395
+ // Collect CSS custom property declarations from raw stylesheet/HTML text.
1396
+ // First declaration wins (:root declarations usually come first); good
1397
+ // enough for the single-level var() resolution the text engines need.
1398
+ function collectCssCustomProps(content) {
1399
+ const map = new Map();
1400
+ const re = /(--[\w-]+)\s*:\s*([^;{}]+)/g;
1401
+ let m;
1402
+ while ((m = re.exec(content)) !== null) {
1403
+ if (!map.has(m[1])) map.set(m[1], m[2].trim());
1404
+ }
1405
+ return map;
1406
+ }
1093
1407
 
1094
- // Split multiple shadows (commas not inside parentheses)
1095
- const parts = boxShadow.split(/,(?![^(]*\))/);
1096
- for (const shadow of parts) {
1097
- const colorMatch = shadow.match(/rgba?\([^)]+\)/);
1098
- if (!colorMatch) continue;
1099
- const color = parseRgb(colorMatch[0]);
1100
- if (!color || !hasChroma(color, 30)) continue;
1408
+ // Text-level glow scan shared by the regex engine and the page-level HTML
1409
+ // pattern pass. Resolves single-level var() refs against custom properties
1410
+ // collected from the same text, then applies the same two glow tells as
1411
+ // checkGlow: zero-offset chromatic halo (any background) and chromatic
1412
+ // blurred shadow when the page has a dark background. Returns
1413
+ // [{ index, snippet }] — index is the offset of the shadow declaration.
1414
+ // Dark-page heuristic for raw CSS/HTML text: dark hex/rgb literals, Tailwind
1415
+ // dark bg utilities, or a ROOT-scoped (body/html/:root or <body style>)
1416
+ // background that resolves — via var() — to a dark color. The var/modern-
1417
+ // color extension is deliberately root-scoped: a light page with one dark
1418
+ // accent chip must not turn every tinted drop shadow into a "dark page"
1419
+ // signal. Shared by the glow and radial-halo text scanners.
1420
+ function cssTextHasDarkRootBg(content, customProps) {
1421
+ const darkBgRe = /background(?:-color)?\s*:\s*(?:#(?:0[0-9a-f]|1[0-9a-f]|2[0-3])[0-9a-f]{4}\b|#(?:0|1)[0-9a-f]{2}\b|rgb\(\s*(\d{1,2})\s*,\s*(\d{1,2})\s*,\s*(\d{1,2})\s*\))/i;
1422
+ const twDarkBg = /\bbg-(?:gray|slate|zinc|neutral|stone)-(?:9\d{2}|800)\b/;
1423
+ if (darkBgRe.test(content) || twDarkBg.test(content)) return true;
1424
+ const rootScopes = [];
1425
+ const blockRe = /(?:^|[}\s,;>])(?:body|html|:root)\s*(?:,[^{]*)?\{([^}]*)\}/gi;
1426
+ let sm;
1427
+ while ((sm = blockRe.exec(content)) !== null) rootScopes.push(sm[1]);
1428
+ const inlineBody = content.match(/<body[^>]*\bstyle\s*=\s*"([^"]*)"/i);
1429
+ if (inlineBody) rootScopes.push(inlineBody[1]);
1430
+ for (const scope of rootScopes) {
1431
+ const bgRe = /background(?:-color)?\s*:\s*([^;{}]+)/gi;
1432
+ let bm;
1433
+ while ((bm = bgRe.exec(scope)) !== null) {
1434
+ const c = parseAnyColor(resolveVarRefs(bm[1].trim(), customProps));
1435
+ if (c && (c.a ?? 1) > 0.5 && relativeLuminance(c) < 0.1) return true;
1436
+ }
1437
+ }
1438
+ return false;
1439
+ }
1101
1440
 
1102
- // Extract px values — in computed style: "color Xpx Ypx BLURpx [SPREADpx]"
1103
- const afterColor = shadow.substring(shadow.indexOf(colorMatch[0]) + colorMatch[0].length);
1104
- const beforeColor = shadow.substring(0, shadow.indexOf(colorMatch[0]));
1105
- const pxVals = [...beforeColor.matchAll(/([\d.]+)px/g), ...afterColor.matchAll(/([\d.]+)px/g)]
1106
- .map(m => parseFloat(m[1]));
1441
+ function scanCssTextForGlow(content) {
1442
+ const customProps = collectCssCustomProps(content);
1443
+ const hasDarkBg = cssTextHasDarkRootBg(content, customProps);
1107
1444
 
1108
- // Third value is blur (offset-x, offset-y, blur, [spread])
1109
- if (pxVals.length >= 3 && pxVals[2] > 4) {
1110
- return [{ id: 'dark-glow', snippet: `Colored glow (${colorToHex(color)}) on dark background` }];
1445
+ const results = [];
1446
+ const shadowRe = /\b(box-shadow|text-shadow)\s*:\s*([^;{}]+)/gi;
1447
+ let m;
1448
+ while ((m = shadowRe.exec(content)) !== null) {
1449
+ const prop = m[1].toLowerCase();
1450
+ const value = resolveVarRefs(m[2].trim(), customProps);
1451
+ for (const layer of value.split(/,(?![^(]*\))/)) {
1452
+ const colorInfo = findShadowColor(layer);
1453
+ if (!colorInfo || !colorInfo.color || !hasChroma(colorInfo.color, 30)) continue;
1454
+ const vals = extractShadowLengths(layer, colorInfo.start, colorInfo.end);
1455
+ if (vals.length < 3 || vals[2] <= 4) continue;
1456
+ const zeroOffset = vals[0] === 0 && vals[1] === 0;
1457
+ if (!zeroOffset && !hasDarkBg) continue;
1458
+ results.push({
1459
+ index: m.index,
1460
+ snippet: zeroOffset
1461
+ ? `Zero-offset ${prop} glow (${colorToHex(colorInfo.color)})`
1462
+ : `Colored ${prop} glow (${colorToHex(colorInfo.color)}) on dark page`,
1463
+ });
1464
+ break; // one finding per declaration
1111
1465
  }
1112
1466
  }
1467
+ return results;
1468
+ }
1113
1469
 
1470
+ // Decorative grid or line-field backgrounds drawn with hairline
1471
+ // linear-gradient layers tiled by a fixed pixel cell. Shared by the HTML
1472
+ // pattern pass and the regex source engine so standalone CSS, component
1473
+ // styles, and inline styles receive the same coverage. Both signals must
1474
+ // co-occur in one declaration block; unrelated rules must not add up across
1475
+ // the file. Returns [{ index, snippet }], capped at one finding per source to
1476
+ // match the page-level HTML check's existing behavior.
1477
+ function scanCssTextForGridBackground(content) {
1478
+ const hairlineRe = /\b\d{1,3}px\s*,\s*transparent\s+\d{1,3}px/gi;
1479
+ const invertedHairlineRe = /transparent\s+calc\(100%\s*-\s*\d{1,3}px\)/gi;
1480
+ const sizeDeclPxRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\b/i;
1481
+ const sizeDeclPxPairRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\s+\d{1,3}px/i;
1482
+ const shorthandPxAnyRe = /\/\s*\d{1,3}px\b/;
1483
+ const shorthandPxPairRe = /\/\s*\d{1,3}px\s+\d{1,3}px/;
1484
+ const bgDeclRe = /\bbackground(?:-image)?\s*:\s*([^;{}"']*)/gi;
1485
+ const blockRe = /\{([^{}]*)\}|style\s*=\s*"([^"]*)"|style\s*=\s*'([^']*)'/gi;
1486
+ let blk;
1487
+ while ((blk = blockRe.exec(content)) !== null) {
1488
+ const block = blk[1] || blk[2] || blk[3] || '';
1489
+ let hairlineCount = 0;
1490
+ let bgJoined = '';
1491
+ let bm;
1492
+ bgDeclRe.lastIndex = 0;
1493
+ while ((bm = bgDeclRe.exec(block)) !== null) {
1494
+ hairlineCount += (bm[1].match(hairlineRe) || []).length;
1495
+ hairlineCount += (bm[1].match(invertedHairlineRe) || []).length;
1496
+ bgJoined += `${bm[1]};`;
1497
+ }
1498
+ if (hairlineCount === 0) continue;
1499
+ const hasPxCell = sizeDeclPxRe.test(block) || shorthandPxAnyRe.test(bgJoined);
1500
+ const hasPxPairCell = sizeDeclPxPairRe.test(block) || shorthandPxPairRe.test(bgJoined);
1501
+ if ((hairlineCount >= 2 && hasPxCell) || hasPxPairCell) {
1502
+ return [{
1503
+ index: blk.index,
1504
+ snippet: hairlineCount >= 2
1505
+ ? 'two-axis grid-line gradient background'
1506
+ : 'px-tiled hairline line-field background',
1507
+ }];
1508
+ }
1509
+ }
1114
1510
  return [];
1115
1511
  }
1116
1512
 
1513
+ // Decorative chromatic halo drawn as a radial-gradient background on a dark
1514
+ // page: a saturated center stop dissolving to transparent. The gradient
1515
+ // sibling of the dark-glow shadow tell. Mechanical gates, in order:
1516
+ // * page has a dark root background (shared heuristic with the glow scan)
1517
+ // * declaration has no url() layer (photographic imagery is exempt)
1518
+ // * the gradient's first color stop is chromatic (RGB spread >= 24) and
1519
+ // visible (alpha >= 0.7 — deliberately translucent light-scene washes
1520
+ // composite with content instead of painting a flat halo, and stay legal)
1521
+ // * the gradient's last stop is transparent / near-zero alpha
1522
+ // * no small pixel-sized stop positions (<= 24px = dot/texture patterns)
1523
+ // * not a repeating-* gradient
1524
+ // Achromatic vignettes fail the chroma gate; panel sheens that fade to an
1525
+ // opaque surface color fail the transparent-end gate.
1526
+ function scanCssTextForRadialHalo(content) {
1527
+ const customProps = collectCssCustomProps(content);
1528
+ if (!cssTextHasDarkRootBg(content, customProps)) return [];
1529
+
1530
+ const findings = [];
1531
+ const seen = new Set();
1532
+ const declRe = /background(?:-image)?\s*:\s*([^;{}]+)/gi;
1533
+ let m;
1534
+ while ((m = declRe.exec(content)) !== null) {
1535
+ const value = resolveVarRefs(m[1].trim(), customProps);
1536
+ if (/url\s*\(/i.test(value)) continue;
1537
+
1538
+ const gradRe = /(repeating-)?radial-gradient\(/gi;
1539
+ let g;
1540
+ while ((g = gradRe.exec(value)) !== null) {
1541
+ if (g[1]) continue; // repeating-* = pattern, not halo
1542
+ // Balanced-paren capture of the gradient arguments.
1543
+ let depth = 0, end = -1;
1544
+ const open = value.indexOf('(', g.index);
1545
+ for (let i = open; i < value.length; i++) {
1546
+ if (value[i] === '(') depth++;
1547
+ else if (value[i] === ')') { depth--; if (depth === 0) { end = i; break; } }
1548
+ }
1549
+ if (end < 0) break;
1550
+ const args = splitTopLevelCommas(value.slice(open + 1, end));
1551
+ if (args.length < 2) continue;
1552
+
1553
+ // Optional prelude (shape / size / `at <pos>`) carries no color.
1554
+ const colorTokenRe = /(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color-mix)\([^)]*(?:\([^)]*\))?[^)]*\)|#[0-9a-f]{3,8}\b|\btransparent\b/i;
1555
+ const stops = args.filter(a => colorTokenRe.test(a));
1556
+ if (stops.length < 2) continue;
1557
+
1558
+ // Dot/texture exemption: px-sized stop positions mean a repeating
1559
+ // background-size pattern, not a page-scale halo.
1560
+ const pxStop = stops.some(s => {
1561
+ const pm = s.match(/(-?[\d.]+)px\b/);
1562
+ return pm && Math.abs(parseFloat(pm[1])) <= 24;
1563
+ });
1564
+ if (pxStop) continue;
1565
+
1566
+ const first = stops[0].match(colorTokenRe);
1567
+ const last = stops[stops.length - 1].match(colorTokenRe);
1568
+ if (!first || !last) continue;
1569
+
1570
+ const lastColor = /^transparent$/i.test(last[0]) ? { r: 0, g: 0, b: 0, a: 0 } : parseAnyColor(last[0]);
1571
+ if (!lastColor || (lastColor.a ?? 1) > 0.05) continue;
1572
+
1573
+ const firstColor = /^transparent$/i.test(first[0]) ? null : parseAnyColor(first[0]);
1574
+ if (!firstColor) continue;
1575
+ if ((firstColor.a ?? 1) < 0.7) continue;
1576
+ const spread = Math.max(firstColor.r, firstColor.g, firstColor.b) - Math.min(firstColor.r, firstColor.g, firstColor.b);
1577
+ if (spread < 24) continue;
1578
+
1579
+ const snippet = `radial-gradient halo (${colorToHex(firstColor)} → transparent) on dark page`;
1580
+ if (seen.has(snippet)) continue;
1581
+ seen.add(snippet);
1582
+ findings.push({ index: m.index, snippet });
1583
+ }
1584
+ }
1585
+ return findings;
1586
+ }
1587
+
1588
+ // ---------------------------------------------------------------------------
1589
+ // Text-level CSS rule-block scanners (pseudo-element stripes, pulsing dots)
1590
+ // ---------------------------------------------------------------------------
1591
+
1592
+ // Iterate `selector { declarations }` pairs in raw CSS/HTML text. The block
1593
+ // body excludes braces, so nested structures (@media, @keyframes) naturally
1594
+ // yield their innermost rules with the innermost selector text. Callers
1595
+ // create the regex locally — a shared /g instance is not re-entrant.
1596
+ const CSS_RULE_BLOCK_SOURCE = String.raw`([^{};]+)\{([^{}]*)\}`;
1597
+
1598
+ // Parse a declaration block into a prop → value map (last declaration wins,
1599
+ // approximating the cascade inside one block). Values keep their raw text
1600
+ // with any !important suffix stripped.
1601
+ function parseCssDeclBlock(block) {
1602
+ const decls = new Map();
1603
+ for (const part of String(block || '').split(';')) {
1604
+ const idx = part.indexOf(':');
1605
+ if (idx <= 0) continue;
1606
+ const prop = part.slice(0, idx).trim().toLowerCase();
1607
+ const value = part.slice(idx + 1).replace(/\s*!important\s*$/i, '').trim();
1608
+ if (prop && value) decls.set(prop, value);
1609
+ }
1610
+ return decls;
1611
+ }
1612
+
1613
+ function cssLengthToPx(value) {
1614
+ const m = String(value || '').trim().match(/^(-?[\d.]+)(px|rem|em)$/i);
1615
+ if (!m) return null;
1616
+ const n = parseFloat(m[1]);
1617
+ return m[2].toLowerCase() === 'px' ? n : n * 16;
1618
+ }
1619
+
1620
+ function isZeroOffset(value) {
1621
+ return value != null && /^-?0(?:px|%|rem|em)?$/.test(String(value).trim());
1622
+ }
1623
+
1624
+ // Side-tab variant: the accent stripe drawn as an absolutely-positioned
1625
+ // ::before/::after pseudo-element (narrow colored box hugging a vertical
1626
+ // edge) instead of a border-left/right. The element-level border checks
1627
+ // never see it — pseudo-elements aren't part of the DOM the cascade walks —
1628
+ // so this scans stylesheet text directly, mirroring the border rule's
1629
+ // gates: >= 3px thick, chromatic fill, full height against a side edge.
1630
+ function scanCssTextForPseudoStripe(rawContent) {
1631
+ // Blank comment bodies byte-for-byte so commented-out rules are not
1632
+ // scanned as live CSS and every rule keeps its source offset (each
1633
+ // finding carries `index` so line-based callers can attribute it and
1634
+ // line-scoped inline ignores can match).
1635
+ const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
1636
+ (block) => block.replace(/[^\n]/g, ' '));
1637
+ const customProps = collectCssCustomProps(content);
1638
+ const findings = [];
1639
+ const seen = new Set();
1640
+ const ruleRe = new RegExp(CSS_RULE_BLOCK_SOURCE, 'g');
1641
+ let m;
1642
+ while ((m = ruleRe.exec(content)) !== null) {
1643
+ const selector = m[1].trim();
1644
+ if (!/::?(?:before|after)\b/i.test(selector)) continue;
1645
+ // Keep the border rule's prose exemptions (blockquote bars etc.).
1646
+ if (/\b(?:blockquote|pre|code|nav|hr)\b/i.test(selector)) continue;
1647
+ const decls = parseCssDeclBlock(m[2]);
1648
+ const position = decls.get('position');
1649
+ if (position !== 'absolute' && position !== 'fixed') continue;
1650
+
1651
+ const widthPx = cssLengthToPx(resolveVarRefs(
1652
+ decls.get('width') || decls.get('inline-size') || '', customProps));
1653
+ const heightPx = cssLengthToPx(resolveVarRefs(
1654
+ decls.get('height') || decls.get('block-size') || '', customProps));
1655
+ const verticalCandidate = widthPx != null && widthPx >= 3 && widthPx <= 12;
1656
+ // Horizontal variant (top/bottom stripe) carries extra exemptions:
1657
+ // link/button underline affordances, selected-state indicators
1658
+ // (aria-selected="true", aria-current, active/current/selected class
1659
+ // hints), and state-conditional (:hover/:focus/...) affordances are
1660
+ // not stripes. Tab-strip membership alone ([role=tab], .tabs, bare
1661
+ // [aria-selected]) is NOT exempt — a stripe on every tab in the
1662
+ // group is decoration; only the selected item's underline stays.
1663
+ const horizontalCandidate = heightPx != null && heightPx >= 3 && heightPx <= 12
1664
+ && !/(?:^|[\s>+~,(])(?:a|button|summary|tr|td|th|table|li)(?![\w-])/i.test(selector)
1665
+ && !/\[aria-selected\s*[*^$|~]?=\s*["']?true/i.test(selector)
1666
+ && !/\[aria-current(?!\s*[*^$|~]?=\s*["']?false)/i.test(selector)
1667
+ && !/(?:^|[\s._[-])(?:active|current|selected|btn[\w-]*|button[\w-]*|link[\w-]*)(?![\w])/i.test(selector)
1668
+ && !/:(?:hover|focus|focus-visible|focus-within|active|checked)\b/i.test(selector);
1669
+ if (!verticalCandidate && !horizontalCandidate) continue;
1670
+
1671
+ // Resolve edge offsets, letting an `inset` shorthand fill the gaps.
1672
+ const offsets = {
1673
+ top: decls.get('top'), right: decls.get('right'),
1674
+ bottom: decls.get('bottom'), left: decls.get('left'),
1675
+ };
1676
+ const inset = decls.get('inset');
1677
+ if (inset) {
1678
+ const p = inset.split(/\s+/);
1679
+ const [t, r, b, l] =
1680
+ p.length === 1 ? [p[0], p[0], p[0], p[0]]
1681
+ : p.length === 2 ? [p[0], p[1], p[0], p[1]]
1682
+ : p.length === 3 ? [p[0], p[1], p[2], p[1]]
1683
+ : p;
1684
+ if (offsets.top == null) offsets.top = t;
1685
+ if (offsets.right == null) offsets.right = r;
1686
+ if (offsets.bottom == null) offsets.bottom = b;
1687
+ if (offsets.left == null) offsets.left = l;
1688
+ }
1689
+ if (offsets.left == null) offsets.left = decls.get('inset-inline-start');
1690
+ if (offsets.right == null) offsets.right = decls.get('inset-inline-end');
1691
+
1692
+ const heightValue = String(resolveVarRefs(
1693
+ decls.get('height') || decls.get('block-size') || '', customProps)).trim();
1694
+ const widthValue = String(resolveVarRefs(
1695
+ decls.get('width') || decls.get('inline-size') || '', customProps)).trim();
1696
+
1697
+ let edge = null;
1698
+ let thicknessPx = null;
1699
+ if (verticalCandidate) {
1700
+ // Full-height stripes hug both corners; the "floating" variant backs
1701
+ // off each end by a small inset (top/bottom a few px) so the bar
1702
+ // clears the card's corners. Both read as the same side-tab accent —
1703
+ // corner treatment is styling, not a different pattern.
1704
+ const topPx = cssLengthToPx(resolveVarRefs(String(offsets.top ?? ''), customProps));
1705
+ const bottomPx = cssLengthToPx(resolveVarRefs(String(offsets.bottom ?? ''), customProps));
1706
+ const fullHeight = (isZeroOffset(offsets.top) && isZeroOffset(offsets.bottom))
1707
+ || /^100(?:\.0*)?%$/.test(heightValue)
1708
+ || (topPx != null && bottomPx != null
1709
+ && topPx >= 0 && topPx <= 20 && bottomPx >= 0 && bottomPx <= 20);
1710
+ if (fullHeight) {
1711
+ edge = isZeroOffset(offsets.left) ? 'left'
1712
+ : isZeroOffset(offsets.right) ? 'right' : null;
1713
+ thicknessPx = widthPx;
1714
+ }
1715
+ }
1716
+ if (!edge && horizontalCandidate) {
1717
+ const fullWidth = (isZeroOffset(offsets.left) && isZeroOffset(offsets.right))
1718
+ || /^100(?:\.0*)?%$/.test(widthValue);
1719
+ if (fullWidth) {
1720
+ edge = isZeroOffset(offsets.top) ? 'top'
1721
+ : isZeroOffset(offsets.bottom) ? 'bottom' : null;
1722
+ thicknessPx = heightPx;
1723
+ }
1724
+ }
1725
+ if (!edge) continue;
1726
+
1727
+ // Chromatic fill only — a neutral hairline divider is not an accent
1728
+ // stripe. Unresolvable colors err toward detection, matching the
1729
+ // border rule's unknown-format default.
1730
+ const bg = String(resolveVarRefs(
1731
+ decls.get('background-color') || decls.get('background') || '', customProps)).trim();
1732
+ if (!bg || /^(?:none|transparent|inherit|initial|unset|currentcolor)$/i.test(bg)) continue;
1733
+ const colorToken = bg.match(/(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb)\([^)]*\)|#[0-9a-f]{3,8}\b/i);
1734
+ const parsed = parseAnyColor(colorToken ? colorToken[0] : bg);
1735
+ if (parsed) {
1736
+ if ((parsed.a ?? 1) < 0.1) continue;
1737
+ const spread = Math.max(parsed.r, parsed.g, parsed.b) - Math.min(parsed.r, parsed.g, parsed.b);
1738
+ if (spread < 30) continue;
1739
+ } else if (/^(?:white|black|gray|grey|silver)$/i.test(bg)) {
1740
+ continue;
1741
+ }
1742
+
1743
+ if (seen.has(selector)) continue;
1744
+ seen.add(selector);
1745
+ // The selector group absorbs whitespace trailing the previous rule;
1746
+ // advance past it so `index` points at the selector itself.
1747
+ const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
1748
+ findings.push({
1749
+ id: 'side-tab',
1750
+ snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
1751
+ index: selectorStart,
1752
+ });
1753
+ }
1754
+ return findings;
1755
+ }
1756
+
1757
+ // Side-tab stripe drawn as a single-edge inset box-shadow
1758
+ // (x or y offset 3-12px, other axis 0, no blur/spread, chromatic color):
1759
+ // paints a bar along one edge with no border property involved, so the
1760
+ // element-level border checks never see it. Selection-state indicators
1761
+ // are exempt — an inset stripe on [aria-current] / .active / [role=tab]
1762
+ // marks the selected item; the same stripe unconditionally on every item
1763
+ // is decoration and flags.
1764
+ function scanCssTextForInsetStripe(content) {
1765
+ const customProps = collectCssCustomProps(content);
1766
+ const findings = [];
1767
+ const seen = new Set();
1768
+ const ruleRe = new RegExp(CSS_RULE_BLOCK_SOURCE, 'g');
1769
+ let m;
1770
+ while ((m = ruleRe.exec(content)) !== null) {
1771
+ const selector = m[1].trim();
1772
+ // Selection-state contexts: current-item markers and interaction
1773
+ // states. Tab-strip membership alone ([role=tab], .tabs, bare
1774
+ // [aria-selected]) is NOT exempt — a stripe on every tab in the
1775
+ // group is decoration; only the selected item's indicator stays.
1776
+ if (/:(?:hover|focus|focus-visible|focus-within|active|checked|target)\b/i.test(selector)) continue;
1777
+ if (/\[aria-selected\s*[*^$|~]?=\s*["']?true/i.test(selector)) continue;
1778
+ if (/\[aria-current(?!\s*[*^$|~]?=\s*["']?false)/i.test(selector)) continue;
1779
+ if (/(?:^|[\s._[-])(?:active|current|selected)(?![\w])/i.test(selector)) continue;
1780
+ // Structural tags where a single-edge inset shadow is depth/quoting,
1781
+ // not an accent stripe.
1782
+ if (/(?:^|[\s>+~,(])(?:button|hr|tr|td|th|table|blockquote|pre|code)(?![\w-])/i.test(selector)) continue;
1783
+
1784
+ const decls = parseCssDeclBlock(m[2]);
1785
+ const shadow = decls.get('box-shadow');
1786
+ if (!shadow || !/\binset\b/i.test(shadow)) continue;
1787
+ // Narrow fixed-width elements (logo marks, icon glyphs) use inset
1788
+ // fills as artwork, not edge stripes. Stripe targets — cards, badges,
1789
+ // menu items — are wider or leave width to layout.
1790
+ const declaredWidth = cssLengthToPx(resolveVarRefs(decls.get('width') || decls.get('inline-size') || '', customProps));
1791
+ if (declaredWidth != null && declaredWidth <= 40) continue;
1792
+ const value = resolveVarRefs(shadow, customProps);
1793
+ for (const layer of value.split(/,(?![^(]*\))/)) {
1794
+ if (!/\binset\b/i.test(layer)) continue;
1795
+ const colorInfo = findShadowColor(layer);
1796
+ // Unresolvable colors (currentColor, external vars): don't guess.
1797
+ if (!colorInfo || !colorInfo.color) continue;
1798
+ const c = colorInfo.color;
1799
+ if ((c.a ?? 1) < 0.1) continue;
1800
+ const chroma = Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b);
1801
+ if (chroma < 30) continue;
1802
+ const vals = extractShadowLengths(layer, colorInfo.start, colorInfo.end);
1803
+ const x = vals[0] || 0, y = vals[1] || 0, blur = vals[2] || 0, sp = vals[3] || 0;
1804
+ if (blur !== 0 || sp !== 0) continue;
1805
+ const ax = Math.abs(x), ay = Math.abs(y);
1806
+ const isStripe = (ax >= 3 && ax <= 12 && ay === 0) || (ay >= 3 && ay <= 12 && ax === 0);
1807
+ if (!isStripe) continue;
1808
+ if (seen.has(selector)) break;
1809
+ seen.add(selector);
1810
+ const edge = ay === 0 ? (x > 0 ? 'left' : 'right') : (y > 0 ? 'top' : 'bottom');
1811
+ findings.push({
1812
+ id: 'side-tab',
1813
+ snippet: `${selector} — inset box-shadow ${ay === 0 ? ax : ay}px stripe (${edge})`,
1814
+ });
1815
+ break;
1816
+ }
1817
+ }
1818
+ return findings;
1819
+ }
1820
+
1821
+ // Collect @keyframes names whose body travels horizontally — the marquee
1822
+ // loop. X travel is measured across every translateX/translate/translate3d
1823
+ // X component in the body: a centered element animating something else
1824
+ // keeps a constant -50% X (zero travel) and never qualifies, while a
1825
+ // ticker moves from its resting position to a large offset. Keyframes
1826
+ // with a single X sample that also vary scale/opacity read as pulses or
1827
+ // breathes, not marquees.
1828
+ function collectMarqueeKeyframes(content) {
1829
+ const names = new Set();
1830
+ const re = /@(?:-webkit-)?keyframes\s+([\w-]+)\s*\{/g;
1831
+ let m;
1832
+ while ((m = re.exec(content)) !== null) {
1833
+ let depth = 1;
1834
+ let i = re.lastIndex;
1835
+ while (i < content.length && depth > 0) {
1836
+ const ch = content.charCodeAt(i);
1837
+ if (ch === 0x7b /* { */) depth++;
1838
+ else if (ch === 0x7d /* } */) depth--;
1839
+ i++;
1840
+ }
1841
+ const body = content.slice(re.lastIndex, Math.max(re.lastIndex, i - 1));
1842
+ re.lastIndex = i;
1843
+
1844
+ // Only percentage travel qualifies: a content marquee translates by a
1845
+ // fraction of its own (unknown) track width, so generated tickers use
1846
+ // -50% / -100%. Pixel-travel loops are bespoke product animations —
1847
+ // sweeping playheads, progress indicators — not marquees.
1848
+ const pct = [];
1849
+ const xRe = /\btranslate(?:X|3d)?\(\s*(-?[\d.]+)%/gi;
1850
+ let xm;
1851
+ while ((xm = xRe.exec(body)) !== null) pct.push(parseFloat(xm[1]));
1852
+ if (pct.length === 0) continue;
1853
+ if (pct.length === 1 && /\bscale\(|\bopacity\s*:/i.test(body)) continue;
1854
+ // Implicit start: a lone declared X animates from the element's
1855
+ // resting position, so its magnitude is the travel.
1856
+ const travelPct = pct.length > 1 ? Math.max(...pct) - Math.min(...pct) : Math.abs(pct[0]);
1857
+ if (travelPct >= 20) names.add(m[1]);
1858
+ }
1859
+ return names;
1860
+ }
1861
+
1862
+ // Auto-scrolling marquee: a <marquee> element, or an infinite animation
1863
+ // bound to a keyframe loop that travels a large horizontal distance.
1864
+ // Rotation/opacity animations never qualify (no X travel); JS-driven
1865
+ // carousels with user controls have no infinite CSS X-loop to match.
1866
+ function scanCssTextForMarquee(content) {
1867
+ const findings = [];
1868
+ if (/<marquee\b/i.test(content)) {
1869
+ findings.push({ id: 'marquee', snippet: '<marquee> element' });
1870
+ }
1871
+ const marqueeKeyframes = collectMarqueeKeyframes(content);
1872
+ if (marqueeKeyframes.size === 0) return findings;
1873
+ const seen = new Set();
1874
+ const ruleRe = new RegExp(CSS_RULE_BLOCK_SOURCE, 'g');
1875
+ let m;
1876
+ while ((m = ruleRe.exec(content)) !== null) {
1877
+ const selector = m[1].trim();
1878
+ const decls = parseCssDeclBlock(m[2]);
1879
+ for (const name of infiniteAnimationNames(decls)) {
1880
+ if (!marqueeKeyframes.has(name)) continue;
1881
+ const key = `${selector} ${name}`;
1882
+ if (seen.has(key)) continue;
1883
+ seen.add(key);
1884
+ findings.push({ id: 'marquee', snippet: `${selector} — infinite horizontal loop animation "${name}"` });
1885
+ }
1886
+ }
1887
+ return findings;
1888
+ }
1889
+
1890
+ // Collect @keyframes names and whether each one reads as a "pulse" —
1891
+ // i.e. it varies opacity, scale, or box-shadow. Rotation-only keyframes
1892
+ // (spinners) are explicitly not pulses.
1893
+ function collectPulseKeyframes(content) {
1894
+ const map = new Map();
1895
+ const re = /@(?:-webkit-)?keyframes\s+([\w-]+)\s*\{/g;
1896
+ let m;
1897
+ while ((m = re.exec(content)) !== null) {
1898
+ let depth = 1;
1899
+ let i = re.lastIndex;
1900
+ while (i < content.length && depth > 0) {
1901
+ const ch = content.charCodeAt(i);
1902
+ if (ch === 0x7b /* { */) depth++;
1903
+ else if (ch === 0x7d /* } */) depth--;
1904
+ i++;
1905
+ }
1906
+ const body = content.slice(re.lastIndex, Math.max(re.lastIndex, i - 1));
1907
+ const pulses = /\bopacity\s*:/i.test(body)
1908
+ || /\bbox-shadow\s*:/i.test(body)
1909
+ || /\btransform\s*:[^;{}]*\bscale/i.test(body);
1910
+ if (!map.has(m[1]) || pulses) map.set(m[1], pulses);
1911
+ re.lastIndex = i;
1912
+ }
1913
+ return map;
1914
+ }
1915
+
1916
+ const ANIMATION_VALUE_KEYWORDS = new Set([
1917
+ 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear',
1918
+ 'infinite', 'alternate', 'alternate-reverse', 'normal', 'reverse',
1919
+ 'none', 'forwards', 'backwards', 'both', 'running', 'paused',
1920
+ 'step-start', 'step-end', 'inherit', 'initial', 'unset',
1921
+ ]);
1922
+
1923
+ // Extract animation names that run with iteration-count: infinite from a
1924
+ // declaration block (shorthand layers or animation-name + iteration-count).
1925
+ function infiniteAnimationNames(decls) {
1926
+ const out = [];
1927
+ const shorthand = decls.get('animation');
1928
+ if (shorthand) {
1929
+ for (const layer of shorthand.split(/,(?![^(]*\))/)) {
1930
+ if (!/\binfinite\b/i.test(layer)) continue;
1931
+ const name = layer.split(/\s+/).find(t =>
1932
+ /^[a-zA-Z_-][\w-]*$/.test(t) && !ANIMATION_VALUE_KEYWORDS.has(t.toLowerCase()));
1933
+ if (name) out.push(name);
1934
+ }
1935
+ }
1936
+ const nameDecl = decls.get('animation-name');
1937
+ if (nameDecl && /\binfinite\b/i.test(decls.get('animation-iteration-count') || '')) {
1938
+ for (const raw of nameDecl.split(',')) {
1939
+ const t = raw.trim();
1940
+ if (t && t.toLowerCase() !== 'none') out.push(t);
1941
+ }
1942
+ }
1943
+ return out;
1944
+ }
1945
+
1946
+ function isRoundDotRadius(radiusValue, w, h) {
1947
+ if (!radiusValue) return false;
1948
+ const first = String(radiusValue).trim().split(/\s+/)[0];
1949
+ const pct = first.match(/^([\d.]+)%$/);
1950
+ if (pct) return parseFloat(pct[1]) >= 40;
1951
+ const px = cssLengthToPx(first);
1952
+ if (px == null) return false;
1953
+ return px >= 999 || px >= 0.4 * Math.min(w, h);
1954
+ }
1955
+
1956
+ // Remove @media blocks whose condition is prefers-reduced-motion: reduce.
1957
+ // Those blocks describe the accessibility fallback, not the default
1958
+ // experience that ships — an `animation: none` reset inside one must not
1959
+ // mask the resting-state animation the page plays for everyone else.
1960
+ function stripReducedMotionBlocks(content) {
1961
+ const re = /@media[^{]*prefers-reduced-motion\s*:\s*reduce[^{]*\{/gi;
1962
+ let out = '';
1963
+ let last = 0;
1964
+ let m;
1965
+ while ((m = re.exec(content)) !== null) {
1966
+ let depth = 1;
1967
+ let i = re.lastIndex;
1968
+ while (i < content.length && depth > 0) {
1969
+ const ch = content.charCodeAt(i);
1970
+ if (ch === 0x7b /* { */) depth++;
1971
+ else if (ch === 0x7d /* } */) depth--;
1972
+ i++;
1973
+ }
1974
+ out += content.slice(last, m.index);
1975
+ last = i;
1976
+ re.lastIndex = i;
1977
+ }
1978
+ return out + content.slice(last);
1979
+ }
1980
+
1981
+ // Source-index ranges of <header> and <nav> landmark elements in an HTML
1982
+ // string. Lets string-level scans decide whether a matched element sits in
1983
+ // the page chrome (the hero/nav region) without needing a DOM.
1984
+ function landmarkSourceRanges(content) {
1985
+ const ranges = [];
1986
+ for (const tag of ['header', 'nav']) {
1987
+ const re = new RegExp(`<${tag}\\b|</${tag}\\s*>`, 'gi');
1988
+ const stack = [];
1989
+ let m;
1990
+ while ((m = re.exec(content)) !== null) {
1991
+ if (m[0].charAt(1) === '/') {
1992
+ const start = stack.pop();
1993
+ if (start != null) ranges.push([start, m.index]);
1994
+ } else {
1995
+ stack.push(m.index);
1996
+ }
1997
+ }
1998
+ }
1999
+ return ranges;
2000
+ }
2001
+
2002
+ function indexInSourceRanges(index, ranges) {
2003
+ return ranges.some(([start, end]) => index >= start && index < end);
2004
+ }
2005
+
2006
+ // Does any element targeted by the final compound of `selector` appear
2007
+ // inside a header/nav landmark range of the HTML source? Resolves the last
2008
+ // .class or #id token of the selector against class/id attributes; a
2009
+ // tag-only compound is never resolvable this way and returns false
2010
+ // (conservative: no promotion without placement evidence).
2011
+ function selectorHitsLandmark(content, selector, ranges) {
2012
+ if (!ranges || ranges.length === 0) return false;
2013
+ const last = selector.split(/[\s>+~]+/).filter(Boolean).pop() || '';
2014
+ const idMatch = last.match(/#([A-Za-z_][\w-]*)/);
2015
+ const classMatch = last.match(/\.([A-Za-z_][\w-]*)/);
2016
+ let attrRe = null;
2017
+ if (idMatch) {
2018
+ const id = idMatch[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2019
+ attrRe = new RegExp(`<[a-zA-Z][^>]*\\bid\\s*=\\s*["']${id}["']`, 'gi');
2020
+ } else if (classMatch) {
2021
+ const cls = classMatch[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2022
+ attrRe = new RegExp(`<[a-zA-Z][^>]*\\bclass\\s*=\\s*["'][^"']*(?<![\\w-])${cls}(?![\\w-])[^"']*["']`, 'gi');
2023
+ }
2024
+ if (!attrRe) return false;
2025
+ let m;
2026
+ while ((m = attrRe.exec(content)) !== null) {
2027
+ if (indexInSourceRanges(m.index, ranges)) return true;
2028
+ }
2029
+ return false;
2030
+ }
2031
+
2032
+ // Small circular indicator bound to an infinite pulse animation — the
2033
+ // decorative "live" dot. Gates: tiny (<= 16px square-ish), round
2034
+ // (border-radius >= 40% or pill values), and an infinite animation whose
2035
+ // keyframes vary opacity/scale/box-shadow (or a pulse/blink/ping name when
2036
+ // the keyframes aren't in the scanned text). Rotation-only animations
2037
+ // (spinners) never flag.
2038
+ //
2039
+ // Declarations for one selector are merged across rule blocks before the
2040
+ // predicate runs: size in the base rule plus the animation added in a
2041
+ // second block (or inside a matching @media block) is the construction
2042
+ // that ships. prefers-reduced-motion: reduce overrides are stripped first
2043
+ // so their animation resets don't mask the default experience. A dot whose
2044
+ // element sits inside a header/nav landmark is the hero liveness cliché
2045
+ // and is promoted to error severity; occurrences elsewhere keep the
2046
+ // registry default severity.
2047
+ function scanCssTextForPulsingDot(content) {
2048
+ const customProps = collectCssCustomProps(content);
2049
+ const keyframes = collectPulseKeyframes(content);
2050
+ const heroRanges = landmarkSourceRanges(content);
2051
+ const findings = [];
2052
+ const seen = new Set();
2053
+
2054
+ // Merge declarations per selector across rule blocks, approximating the
2055
+ // cascade: later declarations for the same property win. Comma lists are
2056
+ // split so `.a, .b { … }` contributes to both selectors. Comments are
2057
+ // stripped first so they neither pollute selector keys nor smuggle a
2058
+ // comma into the selector-list split.
2059
+ const scanText = stripReducedMotionBlocks(content).replace(/\/\*[\s\S]*?\*\//g, ' ');
2060
+ const merged = new Map();
2061
+ const ruleRe = new RegExp(CSS_RULE_BLOCK_SOURCE, 'g');
2062
+ let m;
2063
+ while ((m = ruleRe.exec(scanText)) !== null) {
2064
+ const decls = parseCssDeclBlock(m[2]);
2065
+ if (decls.size === 0) continue;
2066
+ for (const rawSelector of m[1].split(',')) {
2067
+ const selector = rawSelector.trim();
2068
+ if (!selector || selector.startsWith('@')) continue;
2069
+ let acc = merged.get(selector);
2070
+ if (!acc) {
2071
+ acc = new Map();
2072
+ merged.set(selector, acc);
2073
+ }
2074
+ for (const [prop, value] of decls) acc.set(prop, value);
2075
+ }
2076
+ }
2077
+
2078
+ for (const [selector, decls] of merged) {
2079
+ const names = infiniteAnimationNames(decls);
2080
+ if (names.length === 0) continue;
2081
+ const pulseName = names.find(n => {
2082
+ const known = keyframes.get(n);
2083
+ if (known != null) return known;
2084
+ return /pulse|blink|ping/i.test(n);
2085
+ });
2086
+ if (!pulseName) continue;
2087
+
2088
+ const w = cssLengthToPx(resolveVarRefs(
2089
+ decls.get('width') || decls.get('inline-size') || '', customProps));
2090
+ const h = cssLengthToPx(resolveVarRefs(
2091
+ decls.get('height') || decls.get('block-size') || '', customProps));
2092
+ if (w == null || h == null || w < 2 || h < 2 || w > 16 || h > 16) continue;
2093
+
2094
+ const radius = resolveVarRefs(decls.get('border-radius') || '', customProps);
2095
+ if (!isRoundDotRadius(radius, w, h)) continue;
2096
+
2097
+ if (seen.has(selector)) continue;
2098
+ seen.add(selector);
2099
+ const inLandmark = selectorHitsLandmark(content, selector, heroRanges);
2100
+ findings.push({
2101
+ id: 'pulsing-dot',
2102
+ snippet: `${selector} — ${w}x${h}px dot with infinite "${pulseName}" animation${inLandmark ? ' in header/nav' : ''}`,
2103
+ selector,
2104
+ ...(inLandmark ? { severity: 'error' } : {}),
2105
+ });
2106
+ }
2107
+
2108
+ // Tailwind utilities: animate-ping / animate-pulse on a tiny rounded-full
2109
+ // element declared entirely in the class attribute.
2110
+ const classRe = /class\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
2111
+ let cm;
2112
+ while ((cm = classRe.exec(content)) !== null) {
2113
+ const cls = cm[1] || cm[2] || '';
2114
+ const anim = cls.match(/\banimate-(ping|pulse)\b/);
2115
+ if (!anim) continue;
2116
+ if (!/\brounded-full\b/.test(cls)) continue;
2117
+ if (!/\b(?:w|h|size)-(?:1|1\.5|2|2\.5|3|3\.5|4)\b/.test(cls)) continue;
2118
+ const key = `tw:${cls}`;
2119
+ if (seen.has(key)) continue;
2120
+ seen.add(key);
2121
+ const inLandmark = indexInSourceRanges(cm.index, heroRanges);
2122
+ findings.push({
2123
+ id: 'pulsing-dot',
2124
+ snippet: `animate-${anim[1]} on tiny rounded-full element${inLandmark ? ' in header/nav' : ''}`,
2125
+ ...(inLandmark ? { severity: 'error' } : {}),
2126
+ });
2127
+ }
2128
+
2129
+ return findings;
2130
+ }
2131
+
2132
+ // Shape-assembled illustration: a large inline SVG composing a pictorial
2133
+ // scene from many primitive shapes (rect / circle / ellipse / polygon) in
2134
+ // several fill colors — the clip-art hero mascot. Gates keep the legitimate
2135
+ // SVG population out:
2136
+ // • icons and logos: intrinsic size gate (>= 200px on both axes, from
2137
+ // width/height attributes or the viewBox when no explicit size is set)
2138
+ // • charts / labeled diagrams: more than two <text>/<tspan> nodes exempts
2139
+ // the graphic (axis labels, callouts)
2140
+ // • line drawings / technical diagrams: primitive count < 8 or fewer
2141
+ // than 3 distinct fills never qualifies (stroke-only art has no fills)
2142
+ // • tiling background textures: any <pattern> definition exempts
2143
+ function scanHtmlForShapeAssembledIllustration(html) {
2144
+ const findings = [];
2145
+ const svgRe = /<svg\b[^>]*>[\s\S]*?<\/svg>/gi;
2146
+ let m;
2147
+ while ((m = svgRe.exec(html)) !== null) {
2148
+ const block = m[0];
2149
+ const openTag = (block.match(/^<svg\b[^>]*>/i) || [''])[0];
2150
+
2151
+ // Data-bearing or annotated graphics: axis labels and callout text
2152
+ // mark a chart or diagram, not a mascot.
2153
+ const textCount = (block.match(/<(?:text|tspan)\b/gi) || []).length;
2154
+ if (textCount > 2) continue;
2155
+ // Tiling texture definitions are decorative backgrounds, not scenes.
2156
+ if (/<pattern\b/i.test(block)) continue;
2157
+
2158
+ const primitives = (block.match(/<(?:rect|circle|ellipse|polygon)\b/gi) || []).length;
2159
+ if (primitives < 8) continue;
2160
+
2161
+ // Intrinsic size: explicit width/height attributes win; fall back to
2162
+ // the viewBox box. Percentage or missing sizes stay unresolvable on
2163
+ // that axis and the viewBox speaks for them.
2164
+ const attrDim = (name) => {
2165
+ // (?<![-\w]) keeps compound attributes like stroke-width from
2166
+ // masquerading as the svg's own width.
2167
+ const am = openTag.match(new RegExp(`(?<![-\\w])${name}\\s*=\\s*["']\\s*([\\d.]+)(?:px)?\\s*["']`, 'i'));
2168
+ return am ? parseFloat(am[1]) : null;
2169
+ };
2170
+ const vb = openTag.match(/\bviewBox\s*=\s*["']\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)\s*["']/i);
2171
+ const w = attrDim('width') ?? (vb ? parseFloat(vb[1]) : null);
2172
+ const h = attrDim('height') ?? (vb ? parseFloat(vb[2]) : null);
2173
+ if (w == null || h == null || w < 200 || h < 200) continue;
2174
+
2175
+ // Distinct fill paints (attributes and inline styles), excluding
2176
+ // non-paints. Multiple fills are what turn a shape pile into a scene.
2177
+ const fills = new Set();
2178
+ for (const fm of block.matchAll(/\bfill\s*[:=]\s*["']?\s*([^"';>}\s]+)/gi)) {
2179
+ const paint = fm[1].trim().toLowerCase();
2180
+ if (!paint || ['none', 'transparent', 'currentcolor', 'inherit'].includes(paint)) continue;
2181
+ fills.add(paint);
2182
+ }
2183
+ if (fills.size < 3) continue;
2184
+
2185
+ findings.push({
2186
+ id: 'shape-assembled-illustration',
2187
+ snippet: `inline <svg> scene: ${primitives} primitive shapes, ~${Math.round(w)}x${Math.round(h)}px, ${fills.size} fill colors`,
2188
+ });
2189
+ }
2190
+ return findings;
2191
+ }
2192
+
1117
2193
  /**
1118
2194
  * Regex-on-HTML checks shared between browser and Node page-level detection.
1119
2195
  * These don't need DOM access, just the raw HTML string.
@@ -1147,6 +2223,16 @@ function checkHtmlPatterns(html) {
1147
2223
  findings.push({ id: 'gradient-text', snippet: 'bg-clip-text + bg-gradient (Tailwind)' });
1148
2224
  }
1149
2225
 
2226
+ // --- Borders ---
2227
+
2228
+ // Side-tab accent stripe drawn as an absolutely-positioned pseudo-element
2229
+ // (no border property involved, so the element-level border checks and
2230
+ // the border-left regexes never see it).
2231
+ findings.push(...scanCssTextForPseudoStripe(html));
2232
+
2233
+ // Side-tab accent stripe drawn as a single-edge inset box-shadow.
2234
+ findings.push(...scanCssTextForInsetStripe(html));
2235
+
1150
2236
  // --- Layout ---
1151
2237
 
1152
2238
  // Monotonous spacing
@@ -1222,33 +2308,34 @@ function checkHtmlPatterns(html) {
1222
2308
  }
1223
2309
  }
1224
2310
 
1225
- // --- Dark glow ---
2311
+ // Pulsing status dots (tiny circular elements on infinite pulse animations)
2312
+ findings.push(...scanCssTextForPulsingDot(html));
1226
2313
 
1227
- const darkBgRe = /background(?:-color)?\s*:\s*(?:#(?:0[0-9a-f]|1[0-9a-f]|2[0-3])[0-9a-f]{4}\b|#(?:0|1)[0-9a-f]{2}\b|rgb\(\s*(\d{1,2})\s*,\s*(\d{1,2})\s*,\s*(\d{1,2})\s*\))/gi;
1228
- const twDarkBg = /\bbg-(?:gray|slate|zinc|neutral|stone)-(?:9\d{2}|800)\b/;
1229
- if (darkBgRe.test(html) || twDarkBg.test(html)) {
1230
- const shadowRe = /box-shadow\s*:\s*([^;{}]+)/gi;
1231
- let shm;
1232
- while ((shm = shadowRe.exec(html)) !== null) {
1233
- const val = shm[1];
1234
- const colorMatch = val.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
1235
- if (!colorMatch) continue;
1236
- const [r, g, b] = [+colorMatch[1], +colorMatch[2], +colorMatch[3]];
1237
- if ((Math.max(r, g, b) - Math.min(r, g, b)) < 30) continue;
1238
- const pxVals = [...val.matchAll(/(\d+)px|(?<![.\d])\b(0)\b(?![.\d])/g)].map(p => +(p[1] || p[2]));
1239
- if (pxVals.length >= 3 && pxVals[2] > 4) {
1240
- findings.push({ id: 'dark-glow', snippet: `Colored glow (rgb(${r},${g},${b})) on dark page` });
1241
- break;
1242
- }
1243
- }
2314
+ // Shape-assembled illustrations (large pictorial SVGs built from primitives)
2315
+ findings.push(...scanHtmlForShapeAssembledIllustration(html));
2316
+
2317
+ // Auto-scrolling marquees (<marquee> or infinite horizontal loop animations)
2318
+ findings.push(...scanCssTextForMarquee(html));
2319
+
2320
+ // --- Dark glow / chromatic halo shadows ---
2321
+
2322
+ const glowHits = scanCssTextForGlow(html);
2323
+ if (glowHits.length > 0) {
2324
+ findings.push({ id: 'dark-glow', snippet: glowHits[0].snippet });
2325
+ }
2326
+
2327
+ // Radial-gradient background halo (gradient-drawn sibling of dark-glow)
2328
+ const haloHits = scanCssTextForRadialHalo(html);
2329
+ if (haloHits.length > 0) {
2330
+ findings.push({ id: 'radial-halo', snippet: haloHits[0].snippet });
1244
2331
  }
1245
2332
 
1246
- // --- Provider tells (gated): repeating-gradient stripes (GPT) ---
2333
+ // --- Generated-UI tells: repeating-gradient stripes ---
1247
2334
  if (/repeating-(?:linear|radial|conic)-gradient\s*\(/i.test(html)) {
1248
2335
  findings.push({ id: 'repeating-stripes-gradient', snippet: 'repeating-gradient decorative stripes' });
1249
2336
  }
1250
2337
 
1251
- // --- Provider tells (gated): two-axis grid-line background (Codex/GPT) ---
2338
+ // --- Generated-UI tells: two-axis grid-line background ---
1252
2339
  // The Codex grid tell is two hairline `linear-gradient(... <color> 1px,
1253
2340
  // transparent 1px)` layers (one per axis) tiled by a repeating
1254
2341
  // `background-size` cell. Both signals must co-occur in the SAME style block
@@ -1261,27 +2348,9 @@ function checkHtmlPatterns(html) {
1261
2348
  // in for the second axis. Colors like `oklch(96% 0.012 82 / 0.055)` carry
1262
2349
  // nested parens, so match the hairline stop directly rather than parsing
1263
2350
  // whole gradient layers.
1264
- {
1265
- const hairlineRe = /\b\d{1,3}px\s*,\s*transparent\s+\d{1,3}px/gi;
1266
- const gridSizeRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\b/i;
1267
- const bgDeclRe = /\bbackground(?:-image)?\s*:\s*([^;{}"']*)/gi;
1268
- const blockRe = /\{([^{}]*)\}|style\s*=\s*"([^"]*)"|style\s*=\s*'([^']*)'/gi;
1269
- let blk;
1270
- while ((blk = blockRe.exec(html)) !== null) {
1271
- const block = blk[1] || blk[2] || blk[3] || '';
1272
- if (!gridSizeRe.test(block)) continue;
1273
- let hairlineCount = 0;
1274
- let bm;
1275
- bgDeclRe.lastIndex = 0;
1276
- while ((bm = bgDeclRe.exec(block)) !== null) {
1277
- const stops = bm[1].match(hairlineRe);
1278
- if (stops) hairlineCount += stops.length;
1279
- }
1280
- if (hairlineCount >= 2) {
1281
- findings.push({ id: 'codex-grid-background', snippet: 'two-axis grid-line gradient background' });
1282
- break;
1283
- }
1284
- }
2351
+ const gridHits = scanCssTextForGridBackground(html);
2352
+ if (gridHits.length > 0) {
2353
+ findings.push({ id: 'codex-grid-background', snippet: gridHits[0].snippet });
1285
2354
  }
1286
2355
 
1287
2356
  function stripHtmlBlocks(content, tagNames) {
@@ -1303,7 +2372,7 @@ function checkHtmlPatterns(html) {
1303
2372
  return out;
1304
2373
  }
1305
2374
 
1306
- // --- Provider tells (gated): "X theater" framing copy (GPT) ---
2375
+ // --- Generated-copy tells: "X theater" framing copy ---
1307
2376
  // Lives here (regex-on-HTML) rather than in the text-content analyzers so it
1308
2377
  // runs in the bundled browser path too, not just the CLI/static path.
1309
2378
  {
@@ -1312,7 +2381,7 @@ function checkHtmlPatterns(html) {
1312
2381
  if (tm) findings.push({ id: 'theater-slop-phrase', snippet: `"${tm[0].trim()}"` });
1313
2382
  }
1314
2383
 
1315
- // --- Provider tells (gated): image hover transform (Gemini) ---
2384
+ // --- Generated-UI tells: image hover transform ---
1316
2385
  // A CSS `img...:hover { transform: ... }` rule, or a Tailwind hover:scale /
1317
2386
  // hover:rotate / hover:translate utility on an <img>. Each distinct
1318
2387
  // mechanism is its own finding.
@@ -1341,7 +2410,11 @@ function checkHtmlPatterns(html) {
1341
2410
  // `background: #abc`. Real browsers always decompose, so the fallback is
1342
2411
  // a no-op there.
1343
2412
  function readOwnBackgroundColor(el, computedStyle) {
1344
- const bg = parseRgb(computedStyle.backgroundColor);
2413
+ // Real browsers keep wide-gamut/computed color functions (oklch(), oklab(),
2414
+ // color-mix() results) in getComputedStyle output, which plain parseRgb
2415
+ // misses — a flat oklch button background would silently skip every
2416
+ // contrast check without the parseAnyColor fallback.
2417
+ const bg = parseRgb(computedStyle.backgroundColor) || parseAnyColor(computedStyle.backgroundColor);
1345
2418
  if (DETECTOR_IS_BROWSER || (bg && bg.a >= 0.1)) return bg;
1346
2419
  const rawStyle = el.getAttribute?.('style') || '';
1347
2420
  const bgMatch = rawStyle.match(/background(?:-color)?\s*:\s*([^;]+)/i);
@@ -1363,6 +2436,18 @@ function readOwnBackgroundColor(el, computedStyle) {
1363
2436
 
1364
2437
  function resolveBackground(el, win, customPropMap) {
1365
2438
  let current = el;
2439
+ // Translucent layers (0.1 < a < 1) found on the way down to an opaque
2440
+ // base. A browser composites these over the base; the old behavior
2441
+ // either returned them as-if-opaque (browser mode) or skipped them
2442
+ // entirely (static mode), both of which misstate the effective surface
2443
+ // for contrast checks (e.g. `background: color-mix(in oklab, var(--hot)
2444
+ // 16%, transparent)` chips on dark pages).
2445
+ const overlays = [];
2446
+ const flatten = (base) => {
2447
+ let acc = base;
2448
+ for (let i = overlays.length - 1; i >= 0; i--) acc = compositeColorOver(overlays[i], acc);
2449
+ return acc;
2450
+ };
1366
2451
  while (current && current.nodeType === 1) {
1367
2452
  const style = DETECTOR_IS_BROWSER ? getComputedStyle(current) : win.getComputedStyle(current);
1368
2453
  const bgImage = style.backgroundImage || '';
@@ -1375,7 +2460,9 @@ function resolveBackground(el, win, customPropMap) {
1375
2460
  // decorative. The old behavior bailed on any gradient ancestor, which
1376
2461
  // caused massive false-positive contrast findings on grain-textured
1377
2462
  // body backgrounds.
1378
- let bg = parseRgb(style.backgroundColor);
2463
+ // Real browsers serialize wide-gamut computed values as oklab()/oklch()
2464
+ // (e.g. any color-mix() result), which plain parseRgb misses.
2465
+ let bg = parseRgb(style.backgroundColor) || parseAnyColor(style.backgroundColor);
1379
2466
  if (!DETECTOR_IS_BROWSER && (!bg || bg.a < 0.1)) {
1380
2467
  // jsdom returns literal "var(--X)" / "oklch(...)" strings. Resolve
1381
2468
  // through customPropMap so Tailwind v4 color tokens become RGB.
@@ -1395,7 +2482,8 @@ function resolveBackground(el, win, customPropMap) {
1395
2482
  }
1396
2483
 
1397
2484
  if (bg && bg.a > 0.1) {
1398
- if (DETECTOR_IS_BROWSER || bg.a >= 0.5) return bg;
2485
+ if (bg.a >= 0.99) return flatten(bg);
2486
+ overlays.push(bg);
1399
2487
  }
1400
2488
  // No solid bg-color at this level. If THIS level has a gradient/url
1401
2489
  // with no underlying solid color we can read:
@@ -1411,41 +2499,66 @@ function resolveBackground(el, win, customPropMap) {
1411
2499
  // bgs worth checking against).
1412
2500
  if (hasGradientOrUrl) {
1413
2501
  if (current.tagName === 'BODY' || current.tagName === 'HTML') {
1414
- return { r: 255, g: 255, b: 255, a: 1 };
2502
+ return flatten({ r: 255, g: 255, b: 255, a: 1 });
1415
2503
  }
1416
2504
  return null;
1417
2505
  }
1418
2506
  current = current.parentElement;
1419
2507
  }
1420
- return { r: 255, g: 255, b: 255 };
2508
+ return flatten({ r: 255, g: 255, b: 255, a: 1 });
1421
2509
  }
1422
2510
 
1423
2511
  // Walk parents looking for a gradient background and return its color stops.
1424
2512
  // Used as a fallback when resolveBackground() returns null because the
1425
2513
  // effective background is a gradient (no single solid color to compare against).
1426
- function resolveGradientStops(el, win) {
2514
+ function resolveGradientStops(el, win, customPropMap) {
1427
2515
  let current = el;
1428
2516
  while (current && current.nodeType === 1) {
1429
2517
  const style = DETECTOR_IS_BROWSER ? getComputedStyle(current) : win.getComputedStyle(current);
1430
2518
  const bgImage = style.backgroundImage || '';
2519
+ let stops = null;
1431
2520
  if (bgImage && bgImage !== 'none' && /gradient/i.test(bgImage)) {
1432
- const stops = parseGradientColors(bgImage);
1433
- if (stops.length > 0) return stops;
2521
+ const parsed = parseGradientColors(bgImage);
2522
+ if (parsed.length > 0) stops = parsed;
1434
2523
  }
1435
- if (!DETECTOR_IS_BROWSER) {
2524
+ if (!stops && !DETECTOR_IS_BROWSER) {
1436
2525
  // jsdom doesn't decompose `background:` shorthand — peek at the raw inline style
1437
2526
  const rawStyle = current.getAttribute?.('style') || '';
1438
2527
  const bgMatch = rawStyle.match(/background(?:-image)?\s*:\s*([^;]+)/i);
1439
2528
  if (bgMatch && /gradient/i.test(bgMatch[1])) {
1440
- const stops = parseGradientColors(bgMatch[1]);
1441
- if (stops.length > 0) return stops;
2529
+ const parsed = parseGradientColors(bgMatch[1]);
2530
+ if (parsed.length > 0) stops = parsed;
1442
2531
  }
1443
2532
  }
2533
+ if (stops) return compositeGradientStops(stops, current, win, customPropMap);
1444
2534
  current = current.parentElement;
1445
2535
  }
1446
2536
  return null;
1447
2537
  }
1448
2538
 
2539
+ // A translucent gradient stop (e.g. a faint `rgba(52,192,168,0.09)` accent
2540
+ // glow) paints over whatever surface sits beneath the gradient — the browser
2541
+ // composites it, so its effective color is far closer to the base than to the
2542
+ // full-opacity accent. Treating the stop as opaque flags every text child of a
2543
+ // softly-glowing section as low-contrast (issue #409 Case B). Composite each
2544
+ // alpha stop over the resolved surface beneath the gradient element. When that
2545
+ // surface isn't resolvable (another gradient above, no opaque ancestor), drop
2546
+ // the translucent stop rather than guess: a dropped stop can't manufacture a
2547
+ // false finding, and skipping beats a wrong ratio.
2548
+ function compositeGradientStops(stops, gradientEl, win, customPropMap) {
2549
+ const hasAlpha = stops.some(s => (s.a ?? 1) < 0.99);
2550
+ if (!hasAlpha) return stops;
2551
+ const base = resolveBackground(gradientEl.parentElement || gradientEl, win, customPropMap);
2552
+ const out = [];
2553
+ for (const s of stops) {
2554
+ const a = s.a ?? 1;
2555
+ if (a >= 0.99) { out.push(s); continue; }
2556
+ if (base) out.push(compositeColorOver(s, base));
2557
+ // else: unresolvable base — drop the translucent stop (skip, don't guess).
2558
+ }
2559
+ return out.length ? out : null;
2560
+ }
2561
+
1449
2562
  // Parse a single CSS length token to pixels. Accepts "12px", "50%", a
1450
2563
  // shorthand like "12px 4px" (uses the first value), or empty / null.
1451
2564
  // Returns the pixel value, or null when the input is unparseable.
@@ -1479,6 +2592,43 @@ function resolveBorderRadiusPx(el, style, widthPx, win) {
1479
2592
 
1480
2593
  // Browser adapters — call getComputedStyle/getBoundingClientRect on live DOM
1481
2594
 
2595
+ // Selected-state context for accent stripes. Only an actual selection
2596
+ // marker exempts the stripe as the standard active-item indicator:
2597
+ // aria-selected="true", aria-current (any non-false value), or an
2598
+ // active/current/selected class hint. Tab-strip MEMBERSHIP alone
2599
+ // ([role=tablist]/[role=tab]/.tabs ancestry, aria-selected="false")
2600
+ // deliberately does not — a chromatic stripe repeated on every tab in
2601
+ // the group, or on every menu item, is decoration, not state; the
2602
+ // selected item's own underline stays legal.
2603
+ function isTabContextElement(el) {
2604
+ if (!el) return false;
2605
+ try {
2606
+ if (el.closest?.('[aria-selected="true"], [aria-current]:not([aria-current="false"])')) return true;
2607
+ } catch { /* selector engine differences — fall through to class scan */ }
2608
+ let cur = el, depth = 0;
2609
+ while (cur && cur.nodeType === 1 && depth < 6) {
2610
+ const cls = String(cur.getAttribute?.('class') || cur.className || '');
2611
+ if (/(?:^|[\s_-])(?:active|current|selected)(?:$|[\s_-])/i.test(cls)) return true;
2612
+ cur = cur.parentElement;
2613
+ depth++;
2614
+ }
2615
+ return false;
2616
+ }
2617
+
2618
+ // Status-surface context for accent borders. On a live status/alert region
2619
+ // (role=status|alert|alertdialog|log, or aria-live=polite|assertive) a colored
2620
+ // single-edge border is the established severity-accent convention — a toast,
2621
+ // snackbar, or callout bar — not the decorative side-tab tell. The element
2622
+ // itself or a wrapping live region qualifies. This never fires from the
2623
+ // CSS-only / regex scanners, which have no role information.
2624
+ function isStatusContextElement(el) {
2625
+ if (!el) return false;
2626
+ try {
2627
+ if (el.closest?.('[role="status"], [role="alert"], [role="alertdialog"], [role="log"], [aria-live="polite"], [aria-live="assertive"]')) return true;
2628
+ } catch { /* selector engine differences — fall through */ }
2629
+ return false;
2630
+ }
2631
+
1482
2632
  function checkElementBordersDOM(el) {
1483
2633
  const tag = el.tagName.toLowerCase();
1484
2634
  if (BORDER_SAFE_TAGS.has(tag)) return [];
@@ -1491,7 +2641,105 @@ function checkElementBordersDOM(el) {
1491
2641
  widths[s] = parseFloat(style[`border${s}Width`]) || 0;
1492
2642
  colors[s] = style[`border${s}Color`] || '';
1493
2643
  }
1494
- return checkBorders(tag, widths, colors, parseFloat(style.borderRadius) || 0);
2644
+ const ownBg = parseRgb(style.backgroundColor) || parseAnyColor(style.backgroundColor);
2645
+ return checkBorders(tag, widths, colors, parseFloat(style.borderRadius) || 0, {
2646
+ tabContext: isTabContextElement(el),
2647
+ statusContext: isStatusContextElement(el),
2648
+ badgeLike: !!(ownBg && (ownBg.a ?? 1) > 0.1),
2649
+ });
2650
+ }
2651
+
2652
+ // Browser-side twin of scanCssTextForPseudoStripe. The text scanner reads
2653
+ // stylesheet source, so a stripe whose color only exists at runtime (an
2654
+ // inline per-card custom property, a JS-assigned var) or whose geometry
2655
+ // resolves in layout never matches it. In a real browser the pseudo-element's
2656
+ // computed style carries the actual used color and px geometry — check those
2657
+ // directly. Gates mirror the text scanner: 3-12px thick, chromatic fill,
2658
+ // spanning (nearly) the full edge; corner rounding on the host card is
2659
+ // irrelevant. Exemptions stay narrow: structural/prose tags, real selection
2660
+ // markers (isTabContextElement), and button/link affordances for the
2661
+ // horizontal variant.
2662
+ function checkElementPseudoStripeDOM(el) {
2663
+ const tag = el.tagName.toLowerCase();
2664
+ if (BORDER_SAFE_TAGS.has(tag) || tag === 'summary') return [];
2665
+ if (el.closest?.('nav, blockquote, pre')) return [];
2666
+ if (!isRenderedForBrowserRule(el)) return [];
2667
+ const rect = el.getBoundingClientRect();
2668
+ if (rect.width < 40 || rect.height < 20) return [];
2669
+ if (isTabContextElement(el)) return [];
2670
+
2671
+ const findings = [];
2672
+ for (const which of ['::before', '::after']) {
2673
+ let ps;
2674
+ try { ps = getComputedStyle(el, which); } catch { continue; }
2675
+ if (!ps || ps.content === 'none' || ps.content === '') continue;
2676
+ if (ps.position !== 'absolute' && ps.position !== 'fixed') continue;
2677
+ if ((parseFloat(ps.opacity) || 0) <= 0.01 || ps.display === 'none') continue;
2678
+ const w = parseFloat(ps.width) || 0;
2679
+ const h = parseFloat(ps.height) || 0;
2680
+ if (!(w > 0 && h > 0)) continue;
2681
+
2682
+ // Used values: for absolutely-positioned boxes the browser resolves
2683
+ // both edge offsets after layout, so left/right (and top/bottom) are
2684
+ // real distances, never "auto".
2685
+ const left = parseFloat(ps.left);
2686
+ const right = parseFloat(ps.right);
2687
+ const top = parseFloat(ps.top);
2688
+ const bottom = parseFloat(ps.bottom);
2689
+ const hugs = (v) => Number.isFinite(v) && v >= -2 && v <= 2;
2690
+
2691
+ let edge = null;
2692
+ let thickness = null;
2693
+ // Vertical stripe: narrow box spanning (nearly) the full height of the
2694
+ // host, hugging its left or right edge. "Nearly" tolerates the floating
2695
+ // variant that backs off each end by a small inset.
2696
+ if (w >= 3 && w <= 12 && h >= rect.height - 44 && h >= rect.height * 0.5) {
2697
+ edge = hugs(left) ? 'left' : hugs(right) ? 'right' : null;
2698
+ thickness = w;
2699
+ }
2700
+ // Horizontal stripe riding the top or bottom edge. Button/link-styled
2701
+ // hosts keep their underline affordances.
2702
+ if (!edge && h >= 3 && h <= 12 && w >= rect.width - 44 && w >= rect.width * 0.5) {
2703
+ const cls = String(el.getAttribute?.('class') || el.className || '');
2704
+ if (!/(?:^|[\s_-])(?:btn|button|link)(?:$|[\s\w_-])/i.test(cls)) {
2705
+ edge = hugs(top) ? 'top' : hugs(bottom) ? 'bottom' : null;
2706
+ thickness = h;
2707
+ }
2708
+ }
2709
+ if (!edge) continue;
2710
+
2711
+ const bg = parseRgb(ps.backgroundColor) || parseAnyColor(ps.backgroundColor);
2712
+ if (!bg || (bg.a ?? 1) < 0.1) continue;
2713
+ if (Math.max(bg.r, bg.g, bg.b) - Math.min(bg.r, bg.g, bg.b) < 30) continue;
2714
+
2715
+ findings.push({
2716
+ id: 'side-tab',
2717
+ snippet: `${classSelector(el)}${which} — absolute ${thickness}px pseudo-element stripe (${edge})`,
2718
+ });
2719
+ }
2720
+ return findings;
2721
+ }
2722
+
2723
+ // Full-cover surface pseudo (browser): a ::before/::after positioned
2724
+ // absolute/fixed whose box covers (nearly) the whole host and carries an
2725
+ // opaque background. That pseudo is the element's visible surface even
2726
+ // though the element's own background-color reads transparent — the nav-CTA
2727
+ // construction that otherwise escapes every own-background contrast gate.
2728
+ function readPseudoSurfaceDOM(el, rect) {
2729
+ for (const which of ['::before', '::after']) {
2730
+ let ps;
2731
+ try { ps = getComputedStyle(el, which); } catch { continue; }
2732
+ if (!ps || ps.content === 'none' || ps.content === '') continue;
2733
+ if (ps.position !== 'absolute' && ps.position !== 'fixed') continue;
2734
+ if (ps.display === 'none' || (parseFloat(ps.opacity) || 1) < 0.9) continue;
2735
+ const w = parseFloat(ps.width) || 0;
2736
+ const h = parseFloat(ps.height) || 0;
2737
+ if (w < rect.width - 4 || h < rect.height - 4) continue;
2738
+ const bg = parseRgb(ps.backgroundColor) || parseAnyColor(ps.backgroundColor);
2739
+ if (!bg || (bg.a ?? 1) < 0.9) continue;
2740
+ return bg;
2741
+ }
2742
+ return null;
1495
2743
  }
1496
2744
 
1497
2745
  function checkElementColorsDOM(el) {
@@ -1504,11 +2752,24 @@ function checkElementColorsDOM(el) {
1504
2752
  const style = getComputedStyle(el);
1505
2753
  const directText = [...el.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join('');
1506
2754
  const hasDirectText = directText.trim().length > 0;
1507
- const effectiveBg = resolveBackground(el);
2755
+ let effectiveBg = resolveBackground(el);
2756
+ let ownBg = readOwnBackgroundColor(el, style);
2757
+ if (!ownBg || (ownBg.a ?? 1) <= 0.5) {
2758
+ const pseudoSurface = readPseudoSurfaceDOM(el, rect);
2759
+ if (pseudoSurface) {
2760
+ ownBg = pseudoSurface;
2761
+ effectiveBg = pseudoSurface;
2762
+ }
2763
+ }
1508
2764
  return checkColors({
1509
2765
  tag,
1510
- textColor: parseRgb(style.color),
1511
- bgColor: readOwnBackgroundColor(el, style),
2766
+ // Chrome serializes computed colors specified in modern spaces as
2767
+ // oklch()/oklab() strings; without the parseAnyColor fallback the text
2768
+ // color comes back null and the low-contrast / gray-on-color checks
2769
+ // silently never run (the shipped miss: a nav CTA whose text color was
2770
+ // an oklch token near its own oklch background).
2771
+ textColor: parseRgb(style.color) || parseAnyColor(style.color),
2772
+ bgColor: ownBg,
1512
2773
  effectiveBg,
1513
2774
  effectiveBgStops: effectiveBg ? null : resolveGradientStops(el),
1514
2775
  fontSize: parseFloat(style.fontSize) || 16,
@@ -1569,6 +2830,21 @@ function checkElementItalicSerifDOM(el) {
1569
2830
  });
1570
2831
  }
1571
2832
 
2833
+ function domAccentDashPseudo(el) {
2834
+ for (const which of ['::before', '::after']) {
2835
+ let ps;
2836
+ try { ps = getComputedStyle(el, which); } catch { continue; }
2837
+ if (!ps || ps.content === 'none' || ps.content === '') continue;
2838
+ const w = parseFloat(ps.width) || 0;
2839
+ const h = parseFloat(ps.height) || 0;
2840
+ if (!(w >= 8 && w <= 80 && h >= 1 && h <= 6)) continue;
2841
+ const bg = parseRgb(ps.backgroundColor) || parseAnyColor(ps.backgroundColor);
2842
+ if (!bg || (bg.a ?? 1) < 0.1) continue;
2843
+ if (Math.max(bg.r, bg.g, bg.b) - Math.min(bg.r, bg.g, bg.b) >= 30) return true;
2844
+ }
2845
+ return false;
2846
+ }
2847
+
1572
2848
  function checkElementHeroEyebrowDOM(el) {
1573
2849
  const tag = el.tagName.toLowerCase();
1574
2850
  if (tag !== 'h1') return [];
@@ -1580,6 +2856,7 @@ function checkElementHeroEyebrowDOM(el) {
1580
2856
  headingTag: tag,
1581
2857
  headingText: el.textContent || '',
1582
2858
  headingFontSize: parseFloat(headStyle.fontSize) || 0,
2859
+ headingInApplicationContext: !!el.closest('[role="tabpanel"], [role="dialog"], [role="application"], dialog'),
1583
2860
  siblingTag: sibling.tagName.toLowerCase(),
1584
2861
  siblingText: sibling.textContent || '',
1585
2862
  siblingTextTransform: sibStyle.textTransform || '',
@@ -1587,6 +2864,7 @@ function checkElementHeroEyebrowDOM(el) {
1587
2864
  siblingLetterSpacing: parseFloat(sibStyle.letterSpacing) || 0,
1588
2865
  siblingFontWeight: sibStyle.fontWeight || '',
1589
2866
  siblingColor: sibStyle.color || '',
2867
+ siblingHasAccentDashPseudo: domAccentDashPseudo(sibling),
1590
2868
  });
1591
2869
  }
1592
2870
 
@@ -1651,8 +2929,10 @@ function resolveVarRefs(raw, customPropMap, depth = 0) {
1651
2929
  // detector's contrast / color checks.
1652
2930
  function oklchToRgb(L, C, H) {
1653
2931
  const hRad = (H * Math.PI) / 180;
1654
- const a = C * Math.cos(hRad);
1655
- const b = C * Math.sin(hRad);
2932
+ return oklabToRgb(L, C * Math.cos(hRad), C * Math.sin(hRad));
2933
+ }
2934
+
2935
+ function oklabToRgb(L, a, b) {
1656
2936
  const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
1657
2937
  const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
1658
2938
  const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
@@ -1672,13 +2952,178 @@ function oklchToRgb(L, C, H) {
1672
2952
  };
1673
2953
  }
1674
2954
 
1675
- // Extended color parser: rgb/rgba/hex/oklch. Returns null on no match.
1676
- // Use this when the input might be any CSS color form; use plain parseRgb
1677
- // when you only expect computed rgb() values from real browsers.
2955
+ function hslToRgb(h, s, l) {
2956
+ h = ((h % 360) + 360) % 360;
2957
+ const c = (1 - Math.abs(2 * l - 1)) * s;
2958
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
2959
+ const m0 = l - c / 2;
2960
+ const [r, g, b] =
2961
+ h < 60 ? [c, x, 0] :
2962
+ h < 120 ? [x, c, 0] :
2963
+ h < 180 ? [0, c, x] :
2964
+ h < 240 ? [0, x, c] :
2965
+ h < 300 ? [x, 0, c] : [c, 0, x];
2966
+ return {
2967
+ r: Math.round((r + m0) * 255),
2968
+ g: Math.round((g + m0) * 255),
2969
+ b: Math.round((b + m0) * 255),
2970
+ a: 1,
2971
+ };
2972
+ }
2973
+
2974
+ function hwbToRgb(h, w, bl) {
2975
+ if (w + bl >= 1) {
2976
+ const g = Math.round((w / (w + bl)) * 255);
2977
+ return { r: g, g, b: g, a: 1 };
2978
+ }
2979
+ const base = hslToRgb(h, 1, 0.5);
2980
+ const mix = (c) => Math.round(((c / 255) * (1 - w - bl) + w) * 255);
2981
+ return { r: mix(base.r), g: mix(base.g), b: mix(base.b), a: 1 };
2982
+ }
2983
+
2984
+ // Common CSS named colors — the handful that actually show up in generated
2985
+ // UIs, not the full 148-name spec list. Includes the achromatic names so a
2986
+ // named gray parses (and correctly reads as no-chroma) instead of being
2987
+ // treated as an unknown color.
2988
+ const CSS_NAMED_COLORS = {
2989
+ black: { r: 0, g: 0, b: 0 },
2990
+ white: { r: 255, g: 255, b: 255 },
2991
+ gray: { r: 128, g: 128, b: 128 },
2992
+ grey: { r: 128, g: 128, b: 128 },
2993
+ silver: { r: 192, g: 192, b: 192 },
2994
+ dimgray: { r: 105, g: 105, b: 105 },
2995
+ darkgray: { r: 169, g: 169, b: 169 },
2996
+ lightgray: { r: 211, g: 211, b: 211 },
2997
+ gainsboro: { r: 220, g: 220, b: 220 },
2998
+ whitesmoke: { r: 245, g: 245, b: 245 },
2999
+ red: { r: 255, g: 0, b: 0 },
3000
+ crimson: { r: 220, g: 20, b: 60 },
3001
+ tomato: { r: 255, g: 99, b: 71 },
3002
+ coral: { r: 255, g: 127, b: 80 },
3003
+ salmon: { r: 250, g: 128, b: 114 },
3004
+ orange: { r: 255, g: 165, b: 0 },
3005
+ gold: { r: 255, g: 215, b: 0 },
3006
+ yellow: { r: 255, g: 255, b: 0 },
3007
+ olive: { r: 128, g: 128, b: 0 },
3008
+ lime: { r: 0, g: 255, b: 0 },
3009
+ green: { r: 0, g: 128, b: 0 },
3010
+ teal: { r: 0, g: 128, b: 128 },
3011
+ turquoise: { r: 64, g: 224, b: 208 },
3012
+ cyan: { r: 0, g: 255, b: 255 },
3013
+ aqua: { r: 0, g: 255, b: 255 },
3014
+ skyblue: { r: 135, g: 206, b: 235 },
3015
+ dodgerblue: { r: 30, g: 144, b: 255 },
3016
+ blue: { r: 0, g: 0, b: 255 },
3017
+ navy: { r: 0, g: 0, b: 128 },
3018
+ indigo: { r: 75, g: 0, b: 130 },
3019
+ rebeccapurple: { r: 102, g: 51, b: 153 },
3020
+ purple: { r: 128, g: 0, b: 128 },
3021
+ violet: { r: 238, g: 130, b: 238 },
3022
+ orchid: { r: 218, g: 112, b: 214 },
3023
+ magenta: { r: 255, g: 0, b: 255 },
3024
+ fuchsia: { r: 255, g: 0, b: 255 },
3025
+ hotpink: { r: 255, g: 105, b: 180 },
3026
+ pink: { r: 255, g: 192, b: 203 },
3027
+ maroon: { r: 128, g: 0, b: 0 },
3028
+ };
3029
+
3030
+ // Split a string on top-level commas (ignoring commas nested in parens).
3031
+ function splitTopLevelCommas(str) {
3032
+ const parts = [];
3033
+ let depth = 0, start = 0;
3034
+ for (let i = 0; i < str.length; i++) {
3035
+ const ch = str[i];
3036
+ if (ch === '(') depth++;
3037
+ else if (ch === ')') depth = Math.max(0, depth - 1);
3038
+ else if (ch === ',' && depth === 0) {
3039
+ parts.push(str.slice(start, i).trim());
3040
+ start = i + 1;
3041
+ }
3042
+ }
3043
+ const tail = str.slice(start).trim();
3044
+ if (tail) parts.push(tail);
3045
+ return parts;
3046
+ }
3047
+
3048
+ // Evaluate a CSS color-mix() expression to {r,g,b,a}. Returns null when
3049
+ // the expression can't be resolved (unresolved var(), unknown colors).
3050
+ //
3051
+ // Mixing is done with premultiplied alpha in sRGB regardless of the
3052
+ // declared interpolation space. That is exact for the dominant generated-UI
3053
+ // pattern — `color-mix(in oklab, <color> N%, transparent)` — where the
3054
+ // result is simply <color> at alpha N% in ANY rectangular space, and a
3055
+ // close-enough approximation for opaque-opaque mixes (the detector only
3056
+ // consumes these values for contrast/chroma thresholds, not for display).
3057
+ function parseColorMix(str) {
3058
+ const m = String(str).trim().match(/^color-mix\(/i);
3059
+ if (!m) return null;
3060
+ // Balanced-paren capture of the arguments.
3061
+ let depth = 0, end = -1;
3062
+ const open = str.indexOf('(');
3063
+ for (let i = open; i < str.length; i++) {
3064
+ if (str[i] === '(') depth++;
3065
+ else if (str[i] === ')') { depth--; if (depth === 0) { end = i; break; } }
3066
+ }
3067
+ if (end < 0) return null;
3068
+ const args = splitTopLevelCommas(str.slice(open + 1, end));
3069
+ if (args.length !== 3 || !/^in\s/i.test(args[0])) return null;
3070
+
3071
+ const parseComponent = (component) => {
3072
+ // Percentage may lead or trail the color per spec.
3073
+ let pct = null;
3074
+ let colorStr = component;
3075
+ const trail = component.match(/\s+([\d.]+)%$/);
3076
+ const lead = component.match(/^([\d.]+)%\s+/);
3077
+ if (trail) { pct = parseFloat(trail[1]); colorStr = component.slice(0, trail.index).trim(); }
3078
+ else if (lead) { pct = parseFloat(lead[1]); colorStr = component.slice(lead[0].length).trim(); }
3079
+ let color;
3080
+ if (/^transparent$/i.test(colorStr)) color = { r: 0, g: 0, b: 0, a: 0 };
3081
+ else color = parseAnyColor(colorStr);
3082
+ if (!color) return null;
3083
+ return { color, pct };
3084
+ };
3085
+
3086
+ const c1 = parseComponent(args[1]);
3087
+ const c2 = parseComponent(args[2]);
3088
+ if (!c1 || !c2) return null;
3089
+ let p1 = c1.pct, p2 = c2.pct;
3090
+ if (p1 == null && p2 == null) { p1 = 50; p2 = 50; }
3091
+ else if (p1 == null) p1 = 100 - p2;
3092
+ else if (p2 == null) p2 = 100 - p1;
3093
+ const sum = p1 + p2;
3094
+ if (sum <= 0) return null;
3095
+ // Per spec: weights normalize to sum; when sum < 100 the result alpha is
3096
+ // additionally scaled by sum/100.
3097
+ const w1 = p1 / sum, w2 = p2 / sum;
3098
+ const alphaScale = sum < 100 ? sum / 100 : 1;
3099
+ const a1 = c1.color.a ?? 1, a2 = c2.color.a ?? 1;
3100
+ const a = (a1 * w1 + a2 * w2) * alphaScale;
3101
+ if (a <= 0) return { r: 0, g: 0, b: 0, a: 0 };
3102
+ const mix = (ch) => Math.round((c1.color[ch] * a1 * w1 + c2.color[ch] * a2 * w2) / (a1 * w1 + a2 * w2));
3103
+ return { r: mix('r'), g: mix('g'), b: mix('b'), a: Math.min(1, a) };
3104
+ }
3105
+
3106
+ // Composite a translucent color over an opaque(ish) base (simple
3107
+ // source-over in sRGB). Returns an opaque {r,g,b,a:1}.
3108
+ function compositeColorOver(top, base) {
3109
+ const a = top.a ?? 1;
3110
+ return {
3111
+ r: Math.round(top.r * a + base.r * (1 - a)),
3112
+ g: Math.round(top.g * a + base.g * (1 - a)),
3113
+ b: Math.round(top.b * a + base.b * (1 - a)),
3114
+ a: 1,
3115
+ };
3116
+ }
3117
+
3118
+ // Extended color parser: rgb/rgba/hex/oklch/oklab/hsl/hwb/color-mix/common
3119
+ // named colors. Returns null on no match. Use this when the input might be
3120
+ // any CSS color form; use plain parseRgb when you only expect computed rgb()
3121
+ // values from real browsers.
1678
3122
  function parseAnyColor(s) {
1679
3123
  if (!s || typeof s !== 'string') return null;
1680
3124
  const str = s.trim();
1681
3125
  if (str === 'transparent' || str === 'currentcolor' || str === 'inherit') return null;
3126
+ if (/^color-mix\(/i.test(str)) return parseColorMix(str);
1682
3127
  let m;
1683
3128
  m = str.match(/rgba?\(\s*(\d+(?:\.\d+)?)\s*,?\s*(\d+(?:\.\d+)?)\s*,?\s*(\d+(?:\.\d+)?)(?:\s*[,/]\s*([\d.]+))?\s*\)/);
1684
3129
  if (m) return { r: Math.round(+m[1]), g: Math.round(+m[2]), b: Math.round(+m[3]), a: m[4] !== undefined ? +m[4] : 1 };
@@ -1716,6 +3161,41 @@ function parseAnyColor(s) {
1716
3161
  }
1717
3162
  return rgb;
1718
3163
  }
3164
+ // OKLAB — a/b are signed axes; percentages map 100% → 0.4.
3165
+ m = str.match(/oklab\(\s*([\d.]+)(%?)\s+(-?[\d.]+)(%?)\s+(-?[\d.]+)(%?)(?:\s*\/\s*([\d.]+)(%)?)?\s*\)/i);
3166
+ if (m) {
3167
+ const L = m[2] === '%' ? parseFloat(m[1]) / 100 : parseFloat(m[1]);
3168
+ const a = m[4] === '%' ? parseFloat(m[3]) * 0.004 : parseFloat(m[3]);
3169
+ const b = m[6] === '%' ? parseFloat(m[5]) * 0.004 : parseFloat(m[5]);
3170
+ const rgb = oklabToRgb(L, a, b);
3171
+ if (m[7] !== undefined) {
3172
+ const alpha = parseFloat(m[7]);
3173
+ rgb.a = m[8] === '%' ? alpha / 100 : alpha;
3174
+ }
3175
+ return rgb;
3176
+ }
3177
+ // HSL/HSLA — comma or space syntax, optional deg on hue.
3178
+ m = str.match(/hsla?\(\s*(-?[\d.]+)(?:deg)?\s*[,\s]\s*([\d.]+)%\s*[,\s]\s*([\d.]+)%(?:\s*[,/]\s*([\d.]+)(%)?)?\s*\)/i);
3179
+ if (m) {
3180
+ const rgb = hslToRgb(parseFloat(m[1]), parseFloat(m[2]) / 100, parseFloat(m[3]) / 100);
3181
+ if (m[4] !== undefined) {
3182
+ const alpha = parseFloat(m[4]);
3183
+ rgb.a = m[5] === '%' ? alpha / 100 : alpha;
3184
+ }
3185
+ return rgb;
3186
+ }
3187
+ // HWB — hue whiteness% blackness%.
3188
+ m = str.match(/hwb\(\s*(-?[\d.]+)(?:deg)?\s+([\d.]+)%\s+([\d.]+)%(?:\s*\/\s*([\d.]+)(%)?)?\s*\)/i);
3189
+ if (m) {
3190
+ const rgb = hwbToRgb(parseFloat(m[1]), parseFloat(m[2]) / 100, parseFloat(m[3]) / 100);
3191
+ if (m[4] !== undefined) {
3192
+ const alpha = parseFloat(m[4]);
3193
+ rgb.a = m[5] === '%' ? alpha / 100 : alpha;
3194
+ }
3195
+ return rgb;
3196
+ }
3197
+ const named = CSS_NAMED_COLORS[str.toLowerCase()];
3198
+ if (named) return { ...named, a: 1 };
1719
3199
  return null;
1720
3200
  }
1721
3201
 
@@ -1728,7 +3208,7 @@ function parseColorResolved(str, customPropMap) {
1728
3208
  return parseAnyColor(resolved);
1729
3209
  }
1730
3210
 
1731
- const REPEATED_KICKER_SKIP_SELECTOR = [
3211
+ const KICKER_SKIP_SELECTOR = [
1732
3212
  'nav',
1733
3213
  'form',
1734
3214
  'table',
@@ -1747,7 +3227,7 @@ const REPEATED_KICKER_SKIP_SELECTOR = [
1747
3227
  '[data-impeccable-allow-kickers]',
1748
3228
  ].join(',');
1749
3229
 
1750
- const REPEATED_KICKER_CARD_CONTEXT_SELECTOR = [
3230
+ const KICKER_CARD_CONTEXT_SELECTOR = [
1751
3231
  'article',
1752
3232
  'button',
1753
3233
  'a',
@@ -1765,23 +3245,32 @@ function cleanInlineText(el) {
1765
3245
  .trim();
1766
3246
  }
1767
3247
 
1768
- function isRepeatedKickerCardContext(heading, kicker) {
1769
- const item = heading.closest?.(REPEATED_KICKER_CARD_CONTEXT_SELECTOR);
3248
+ function isKickerCardContext(heading, kicker) {
3249
+ const item = heading.closest?.(KICKER_CARD_CONTEXT_SELECTOR);
1770
3250
  return Boolean(item && (!item.contains || item.contains(kicker)));
1771
3251
  }
1772
3252
 
1773
- function isRepeatedKickerCandidate(opts) {
3253
+ // Meta lines above headlines join category and date (or path crumbs) with
3254
+ // separator glyphs, or carry a year. A kicker is one short phrase; metadata
3255
+ // keeps its markers.
3256
+ const KICKER_META_TEXT_RE = /[·•|]|\s[\/›»>]\s|\b(19|20)\d{2}\b/;
3257
+ // Legal and document numbering: "Section 4.2", "Article IX", "§ 12.3",
3258
+ // dotted decimal outlines. The label identifies the clause, so it stays.
3259
+ const KICKER_DOC_NUMBERING_RE = /^(§|\d+(\.\d+)+\b|(section|article|clause|appendix|exhibit|schedule|chapter|part|rule|title)\s+([\divxlc]+\b|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\b)/i;
3260
+
3261
+ function isKickerCandidate(opts) {
1774
3262
  const {
1775
- headingTag,
3263
+ headingLevel,
1776
3264
  headingText,
1777
3265
  headingFontSize,
1778
3266
  kickerTag,
1779
3267
  kickerText,
1780
3268
  kickerTextTransform,
3269
+ kickerFontVariant,
1781
3270
  kickerFontSize,
1782
3271
  kickerLetterSpacing,
1783
3272
  } = opts;
1784
- if (!['h2', 'h3', 'h4'].includes(headingTag)) return false;
3273
+ if (!headingLevel || headingLevel > 4) return false;
1785
3274
  if (!headingText || headingText.length < 3) return false;
1786
3275
  if (/^\/[\w-]+/i.test(headingText.replace(/^"|"$/g, '').trim())) return false;
1787
3276
  if (!(headingFontSize >= 20)) return false;
@@ -1789,9 +3278,13 @@ function isRepeatedKickerCandidate(opts) {
1789
3278
  if (!['p', 'span', 'div', 'small'].includes(kickerTag)) return false;
1790
3279
  if (!kickerText || kickerText.length < 2 || kickerText.length > 34) return false;
1791
3280
  if (/^step\s*\d+/i.test(kickerText) || /^\d{1,2}$/.test(kickerText)) return false;
3281
+ if (KICKER_META_TEXT_RE.test(kickerText)) return false;
3282
+ if (KICKER_DOC_NUMBERING_RE.test(kickerText)) return false;
1792
3283
 
3284
+ const isSmallCaps = /small-caps/.test(kickerFontVariant || '');
1793
3285
  const isUppercased = kickerTextTransform === 'uppercase'
1794
- || (/[A-Z]/.test(kickerText) && !/[a-z]/.test(kickerText));
3286
+ || (/[A-Z]/.test(kickerText) && !/[a-z]/.test(kickerText))
3287
+ || isSmallCaps;
1795
3288
  if (!isUppercased) return false;
1796
3289
  if (!(kickerFontSize > 0 && kickerFontSize <= 14)) return false;
1797
3290
  const minTrackedSpacing = Math.max(1, kickerFontSize * 0.08);
@@ -1799,37 +3292,64 @@ function isRepeatedKickerCandidate(opts) {
1799
3292
  return true;
1800
3293
  }
1801
3294
 
1802
- function collectRepeatedSectionKickerCandidates(doc, getStyle, resolveLetterSpacing) {
3295
+ // Resolve a heading level for the anchor element: 1-4 for h1-h4, aria-level
3296
+ // (default 2) for role="heading" elements, 0 otherwise.
3297
+ function kickerHeadingLevel(heading) {
3298
+ const tag = heading.tagName.toLowerCase();
3299
+ const byTag = /^h([1-6])$/.exec(tag);
3300
+ if (byTag) return parseInt(byTag[1], 10);
3301
+ const role = heading.getAttribute?.('role') || '';
3302
+ if (role.toLowerCase() !== 'heading') return 0;
3303
+ const ariaLevel = parseInt(heading.getAttribute?.('aria-level') || '', 10);
3304
+ return Number.isFinite(ariaLevel) && ariaLevel >= 1 ? ariaLevel : 2;
3305
+ }
3306
+
3307
+ function collectKickerCandidates(doc, getStyle, resolveLetterSpacing) {
1803
3308
  const candidates = [];
1804
- for (const heading of doc.querySelectorAll('h2, h3, h4')) {
1805
- if (heading.closest?.(REPEATED_KICKER_SKIP_SELECTOR)) continue;
3309
+ for (const heading of doc.querySelectorAll('h1, h2, h3, h4, [role="heading"]')) {
3310
+ const headingLevel = kickerHeadingLevel(heading);
3311
+ if (!headingLevel || headingLevel > 4) continue;
3312
+ if (heading.closest?.(KICKER_SKIP_SELECTOR)) continue;
3313
+ // Application contexts (tab panels, dialogs) use compact context labels
3314
+ // above headings to describe state, not to decorate. Same carve-out the
3315
+ // hero-eyebrow rule makes.
3316
+ if (heading.closest?.('[role="tabpanel"], [role="dialog"], [role="application"], dialog')) continue;
1806
3317
  const kicker = heading.previousElementSibling;
1807
- if (!kicker || kicker.closest?.(REPEATED_KICKER_SKIP_SELECTOR)) continue;
1808
- if (isRepeatedKickerCardContext(heading, kicker)) continue;
3318
+ if (!kicker || kicker.closest?.(KICKER_SKIP_SELECTOR)) continue;
3319
+ if (isKickerCardContext(heading, kicker)) continue;
1809
3320
 
1810
3321
  const headingStyle = getStyle(heading);
1811
3322
  const kickerStyle = getStyle(kicker);
3323
+ const headingTag = heading.tagName.toLowerCase();
1812
3324
  const headingText = (heading.textContent || '').replace(/\s+/g, ' ').trim();
1813
3325
  const kickerText = cleanInlineText(kicker) || (kicker.textContent || '').replace(/\s+/g, ' ').trim();
1814
3326
  const headingFontSize = resolveLetterSpacing(headingStyle.fontSize || '', 16) || parseFloat(headingStyle.fontSize) || 0;
1815
3327
  const kickerFontSize = resolveLetterSpacing(kickerStyle.fontSize || '', 16) || parseFloat(kickerStyle.fontSize) || 0;
1816
3328
  const kickerLetterSpacing = resolveLetterSpacing(kickerStyle.letterSpacing || '', kickerFontSize);
1817
3329
 
1818
- if (!isRepeatedKickerCandidate({
1819
- headingTag: heading.tagName.toLowerCase(),
3330
+ if (!isKickerCandidate({
3331
+ headingLevel,
1820
3332
  headingText,
1821
3333
  headingFontSize,
1822
3334
  kickerTag: kicker.tagName.toLowerCase(),
1823
3335
  kickerText,
1824
3336
  kickerTextTransform: kickerStyle.textTransform || '',
3337
+ kickerFontVariant: `${kickerStyle.fontVariant || ''} ${kickerStyle.fontVariantCaps || ''}`,
1825
3338
  kickerFontSize,
1826
3339
  kickerLetterSpacing,
1827
3340
  })) {
1828
3341
  continue;
1829
3342
  }
1830
3343
 
3344
+ // A tracked-caps eyebrow above a hero-scale h1 belongs to
3345
+ // hero-eyebrow-chip (which also covers the accent-bold and dash-prefix
3346
+ // stylings there). Stand down so one element gets one finding.
3347
+ if (headingTag === 'h1' && headingFontSize >= 48 && kickerLetterSpacing >= 1.6) {
3348
+ continue;
3349
+ }
3350
+
1831
3351
  candidates.push({
1832
- headingTag: heading.tagName.toLowerCase(),
3352
+ headingTag,
1833
3353
  headingText: headingText.replace(/^"|"$/g, '').slice(0, 60),
1834
3354
  kickerText: kickerText.slice(0, 40),
1835
3355
  });
@@ -1837,13 +3357,177 @@ function collectRepeatedSectionKickerCandidates(doc, getStyle, resolveLetterSpac
1837
3357
  return candidates;
1838
3358
  }
1839
3359
 
1840
- function checkRepeatedSectionKickersDOM() {
1841
- const candidates = collectRepeatedSectionKickerCandidates(
3360
+ function checkKickerAboveHeadingDOM() {
3361
+ const candidates = collectKickerCandidates(
3362
+ document,
3363
+ (el) => getComputedStyle(el),
3364
+ (value, fontSize) => resolveLengthPx(value, fontSize) || 0,
3365
+ );
3366
+ return checkKickerAboveHeading({ candidates });
3367
+ }
3368
+
3369
+ // ── Numbered section labels ─────────────────────────────────────────────────
3370
+ // Sibling of the kicker-above-heading rule: instead of a tracked uppercase word,
3371
+ // the section scaffold is a tiny numeric index riding beside each section
3372
+ // heading — bare and zero-padded, or an index joined to a short micro-label
3373
+ // by a separator glyph. The kicker rule deliberately excludes bare 1-2 digit
3374
+ // labels; this rule owns that shape.
3375
+
3376
+ const NUMBERED_LABEL_TAGS = new Set(['span', 'p', 'div', 'small', 'em', 'strong', 'b']);
3377
+
3378
+ // Returns { index, text } when the trimmed text reads as a section index
3379
+ // label, else null. Two accepted shapes: a zero-padded/two-digit bare index,
3380
+ // or a 1-2 digit index followed by a non-word separator and a short label.
3381
+ function parseNumberedLabelText(rawText) {
3382
+ const text = (rawText || '').replace(/\s+/g, ' ').trim();
3383
+ if (!text || text.length > 40) return null;
3384
+ let m = /^(\d{2})$/.exec(text);
3385
+ if (!m) m = /^(\d{1,2})\s*[^\w\s]\s*\S/.exec(text);
3386
+ if (!m) return null;
3387
+ const index = parseInt(m[1], 10);
3388
+ if (!Number.isFinite(index) || index > 40) return null;
3389
+ return { index, text };
3390
+ }
3391
+
3392
+ function isNumberedSectionLabelCandidate(opts) {
3393
+ const {
3394
+ headingTag, headingText, headingFontSize,
3395
+ labelTag, labelIndex, labelText,
3396
+ labelFontSize, labelLetterSpacing, labelFontWeight,
3397
+ labelFontFamily, labelTextTransform, labelColor,
3398
+ } = opts;
3399
+ if (!['h2', 'h3', 'h4'].includes(headingTag)) return false;
3400
+ if (!headingText || headingText.length < 3) return false;
3401
+ if (!labelTag || !NUMBERED_LABEL_TAGS.has(labelTag)) return false;
3402
+ if (labelIndex == null || !labelText) return false;
3403
+ // Tiny rendered size is the tell — a display-scale section number is a
3404
+ // different (deliberate) device and stays legal.
3405
+ if (!(labelFontSize > 0 && labelFontSize <= 13)) return false;
3406
+ // The heading must be visibly larger where we can resolve its size.
3407
+ // clamp()/var() sizes come back unparseable (0) in the static engine —
3408
+ // the remaining gates carry the check there.
3409
+ if (headingFontSize > 0 && headingFontSize < labelFontSize * 1.3) return false;
3410
+ // Deliberate micro-label styling separates the scaffold from incidental
3411
+ // small text: mono face, bold weight, tracking, uppercase, or accent color.
3412
+ const weight = Number(labelFontWeight) || 400;
3413
+ return /mono/i.test(labelFontFamily || '')
3414
+ || weight >= 600
3415
+ || (labelLetterSpacing || 0) >= 0.5
3416
+ || (labelTextTransform || '') === 'uppercase'
3417
+ || isAccentColor(labelColor || '');
3418
+ }
3419
+
3420
+ function collectNumberedSectionLabelCandidates(doc, getStyle, resolveLetterSpacing) {
3421
+ const candidates = [];
3422
+ const seenLabels = new Set();
3423
+ for (const heading of doc.querySelectorAll('h2, h3, h4')) {
3424
+ if (heading.closest?.(KICKER_SKIP_SELECTOR)) continue;
3425
+ // The index sits either directly before the heading, or before the
3426
+ // wrapper the heading leads (label | <div><h2>…</h2>…</div>).
3427
+ let label = heading.previousElementSibling;
3428
+ if (!label) {
3429
+ const parent = heading.parentElement;
3430
+ const firstChild = parent?.children?.[0];
3431
+ if (firstChild === heading) label = parent.previousElementSibling;
3432
+ }
3433
+ if (!label || seenLabels.has(label)) continue;
3434
+ if (label.closest?.(KICKER_SKIP_SELECTOR)) continue;
3435
+ if (HEADING_TAGS.has(label.tagName.toLowerCase())) continue;
3436
+ if (isKickerCardContext(heading, label)) continue;
3437
+
3438
+ const labelText = cleanInlineText(label) || (label.textContent || '').replace(/\s+/g, ' ').trim();
3439
+ const parsed = parseNumberedLabelText(labelText);
3440
+ if (!parsed) continue;
3441
+
3442
+ const headingStyle = getStyle(heading);
3443
+ const labelStyle = getStyle(label);
3444
+ const headingText = (heading.textContent || '').replace(/\s+/g, ' ').trim();
3445
+ const headingFontSize = resolveLetterSpacing(headingStyle.fontSize || '', 16) || parseFloat(headingStyle.fontSize) || 0;
3446
+ const labelFontSize = resolveLetterSpacing(labelStyle.fontSize || '', 16) || parseFloat(labelStyle.fontSize) || 0;
3447
+
3448
+ if (!isNumberedSectionLabelCandidate({
3449
+ headingTag: heading.tagName.toLowerCase(),
3450
+ headingText,
3451
+ headingFontSize,
3452
+ labelTag: label.tagName.toLowerCase(),
3453
+ labelIndex: parsed.index,
3454
+ labelText: parsed.text,
3455
+ labelFontSize,
3456
+ labelLetterSpacing: resolveLetterSpacing(labelStyle.letterSpacing || '', labelFontSize),
3457
+ labelFontWeight: labelStyle.fontWeight || '',
3458
+ labelFontFamily: labelStyle.fontFamily || '',
3459
+ labelTextTransform: labelStyle.textTransform || '',
3460
+ labelColor: labelStyle.color || '',
3461
+ })) {
3462
+ continue;
3463
+ }
3464
+
3465
+ seenLabels.add(label);
3466
+ candidates.push({
3467
+ index: parsed.index,
3468
+ labelText: parsed.text.slice(0, 24),
3469
+ headingTag: heading.tagName.toLowerCase(),
3470
+ headingText: headingText.replace(/^"|"$/g, '').slice(0, 60),
3471
+ });
3472
+ }
3473
+ return candidates;
3474
+ }
3475
+
3476
+ function checkNumberedSectionLabels(opts) {
3477
+ const { candidates, minCount = 2 } = opts;
3478
+ if (!Array.isArray(candidates) || candidates.length < minCount) return [];
3479
+ // A repeated identical number is some other device; the scaffold counts up.
3480
+ const distinctIndices = new Set(candidates.map(c => c.index));
3481
+ if (distinctIndices.size < 2) return [];
3482
+ return candidates.map(candidate => ({
3483
+ id: 'numbered-section-labels',
3484
+ snippet: `tiny numbered label "${candidate.labelText}" beside ${candidate.headingTag} "${candidate.headingText}" (${candidates.length} on page)`,
3485
+ }));
3486
+ }
3487
+
3488
+ function checkNumberedSectionLabelsFromDoc(doc, win) {
3489
+ const candidates = collectNumberedSectionLabelCandidates(
3490
+ doc,
3491
+ (el) => win.getComputedStyle(el),
3492
+ (value, fontSize) => resolveLengthPx(value, fontSize) || 0,
3493
+ );
3494
+ return checkNumberedSectionLabels({ candidates });
3495
+ }
3496
+
3497
+ function checkNumberedSectionLabelsDOM() {
3498
+ const candidates = collectNumberedSectionLabelCandidates(
1842
3499
  document,
1843
3500
  (el) => getComputedStyle(el),
1844
3501
  (value, fontSize) => resolveLengthPx(value, fontSize) || 0,
1845
3502
  );
1846
- return checkRepeatedSectionKickers({ candidates });
3503
+ return checkNumberedSectionLabels({ candidates });
3504
+ }
3505
+
3506
+ // Em-dash overuse (ADVISORY) — pure logic shared by the browser DOM check.
3507
+ // Mirrors the regex/static-HTML analyzer in engines/regex/detect-text.mjs:
3508
+ // two gates (absolute floor + density) so a long article using a few dashes is
3509
+ // left alone while a short, dash-per-clause page is flagged. Operates on
3510
+ // already-rendered text, so no HTML-entity decoding is needed (the browser has
3511
+ // resolved `&mdash;` to the literal glyph). Exported for jsdom unit tests.
3512
+ function checkEmDashOveruse(text) {
3513
+ const body = typeof text === 'string' ? text.replace(/\s+/g, ' ') : '';
3514
+ let count = 0;
3515
+ const re = /[—]|--(?=\S)/g;
3516
+ while (re.exec(body) !== null) count++;
3517
+ if (count < EM_DASH_FLOOR) return [];
3518
+ if (body.length > count * EM_DASH_CHARS_PER_DASH) return [];
3519
+ return [{ id: 'em-dash-overuse', snippet: `${count} em-dashes in body text` }];
3520
+ }
3521
+
3522
+ function checkEmDashOveruseDOM() {
3523
+ const body = document.body;
3524
+ if (!body) return [];
3525
+ // innerText reflects rendered, visible text; fall back to textContent for
3526
+ // engines (jsdom) that don't compute innerText.
3527
+ const text = typeof body.innerText === 'string' && body.innerText
3528
+ ? body.innerText
3529
+ : (body.textContent || '');
3530
+ return checkEmDashOveruse(text);
1847
3531
  }
1848
3532
 
1849
3533
  function checkElementMotionDOM(el) {
@@ -1862,7 +3546,14 @@ function checkElementMotionDOM(el) {
1862
3546
  function checkElementGlowDOM(el) {
1863
3547
  const tag = el.tagName.toLowerCase();
1864
3548
  const style = getComputedStyle(el);
1865
- if (!style.boxShadow || style.boxShadow === 'none') return [];
3549
+ const boxShadow = style.boxShadow && style.boxShadow !== 'none' ? style.boxShadow : '';
3550
+ // text-shadow inherits: only check the element that introduces it, so one
3551
+ // declaration doesn't produce a finding on every descendant.
3552
+ let textShadow = style.textShadow && style.textShadow !== 'none' ? style.textShadow : '';
3553
+ if (textShadow && el.parentElement && getComputedStyle(el.parentElement).textShadow === textShadow) {
3554
+ textShadow = '';
3555
+ }
3556
+ if (!boxShadow && !textShadow) return [];
1866
3557
  // Use parent's background — glow radiates outward, so the surrounding context matters
1867
3558
  // If resolveBackground returns null (gradient), try to infer from the gradient colors
1868
3559
  let parentBg = el.parentElement ? resolveBackground(el.parentElement) : resolveBackground(el);
@@ -1885,7 +3576,7 @@ function checkElementGlowDOM(el) {
1885
3576
  cur = cur.parentElement;
1886
3577
  }
1887
3578
  }
1888
- return checkGlow({ tag, boxShadow: style.boxShadow, effectiveBg: parentBg });
3579
+ return checkGlow({ tag, boxShadow, textShadow, effectiveBg: parentBg });
1889
3580
  }
1890
3581
 
1891
3582
  function checkElementAIPaletteDOM(el) {
@@ -1942,7 +3633,132 @@ function checkElementAIPaletteDOM(el) {
1942
3633
  }
1943
3634
  }
1944
3635
 
1945
- return findings;
3636
+ return findings;
3637
+ }
3638
+
3639
+ // ─── Decorative radial spotlight glow ───────────────────────────────────────
3640
+ // A soft, low-opacity chromatic radial-gradient fading to transparent, painted
3641
+ // as a decorative wash behind a hero or section. The translucent sibling of the
3642
+ // `radial-halo` tell: `radial-halo` requires a saturated, near-opaque center on
3643
+ // a dark page; this catches the low-alpha "spotlight" the halo gate lets slip
3644
+ // (e.g. `radial-gradient(circle at 52% 38%, rgba(80,111,255,0.26),
3645
+ // transparent 44%)`). The two alpha bands are disjoint, so they never
3646
+ // double-report the same declaration.
3647
+ const SPOTLIGHT_COLOR_TOKEN_RE = /(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color-mix)\([^)]*(?:\([^)]*\))?[^)]*\)|#[0-9a-f]{3,8}\b|\btransparent\b/i;
3648
+
3649
+ // Parse the FIRST non-repeating radial-gradient in a background value into its
3650
+ // ordered color stops. Each stop is { color: {r,g,b,a} | null, transparent }.
3651
+ // Returns null when there is no plain radial-gradient to read.
3652
+ function parseRadialGradientStops(value) {
3653
+ if (!value || !/radial-gradient/i.test(value)) return null;
3654
+ const gradRe = /(repeating-)?radial-gradient\(/gi;
3655
+ let g;
3656
+ while ((g = gradRe.exec(value)) !== null) {
3657
+ if (g[1]) continue; // repeating-* is a pattern, not a spotlight
3658
+ let depth = 0, end = -1;
3659
+ const open = value.indexOf('(', g.index);
3660
+ for (let i = open; i < value.length; i++) {
3661
+ if (value[i] === '(') depth++;
3662
+ else if (value[i] === ')') { depth--; if (depth === 0) { end = i; break; } }
3663
+ }
3664
+ if (end < 0) return null;
3665
+ const args = splitTopLevelCommas(value.slice(open + 1, end));
3666
+ // The optional prelude (shape / size / `at <pos>`) carries no color token.
3667
+ const stopArgs = args.filter(a => SPOTLIGHT_COLOR_TOKEN_RE.test(a));
3668
+ if (stopArgs.length < 2) return null;
3669
+ return stopArgs.map(a => {
3670
+ const tok = a.match(SPOTLIGHT_COLOR_TOKEN_RE);
3671
+ if (!tok) return { color: null, transparent: false };
3672
+ if (/^transparent$/i.test(tok[0])) return { color: null, transparent: true };
3673
+ const color = parseAnyColor(tok[0]);
3674
+ return { color, transparent: !!color && (color.a ?? 1) <= 0.05 };
3675
+ });
3676
+ }
3677
+ return null;
3678
+ }
3679
+
3680
+ // Pure gate. `label` is a stable identifier the fixture test keys on.
3681
+ function checkRadialSpotlight({ gradientValue, width, height, label }) {
3682
+ const stops = parseRadialGradientStops(gradientValue);
3683
+ if (!stops || stops.length < 2) return [];
3684
+
3685
+ // Must fade OUT: the last stop is transparent / near-zero alpha. A gradient
3686
+ // between two visible surfaces is a real background, not a floating glow.
3687
+ const last = stops[stops.length - 1];
3688
+ const lastAlpha = last.transparent ? 0 : (last.color ? (last.color.a ?? 1) : 1);
3689
+ if (lastAlpha > 0.05) return [];
3690
+
3691
+ // The visible (non-transparent, parseable) color stops.
3692
+ const colored = stops.filter(s => !s.transparent && s.color && (s.color.a ?? 1) > 0.05);
3693
+ if (colored.length === 0) return [];
3694
+ // One soft glow, not a multi-color composition: at most two visible stops.
3695
+ if (colored.length > 2) return [];
3696
+ // Every visible stop must be LOW opacity. Any opaque stop means a real fill
3697
+ // or a saturated halo (`radial-halo`'s job), not this translucent spotlight.
3698
+ if (colored.some(s => (s.color.a ?? 1) >= 0.45)) return [];
3699
+ // At least one visible stop must be chromatic. A neutral (grayscale)
3700
+ // near-black / near-white vignette is a legitimate lighting move, exempt.
3701
+ const chromatic = colored.find(s => hasChroma(s.color, 24));
3702
+ if (!chromatic) return [];
3703
+
3704
+ // Decorative-scale gate. Badges, avatars, and actual small "lights" are
3705
+ // exempt; a spotlight glow only reads as slop when it washes a large surface.
3706
+ if (!(width >= 240 && height >= 160)) return [];
3707
+
3708
+ const alpha = (chromatic.color.a ?? 1).toFixed(2);
3709
+ const name = label || 'section';
3710
+ return [{
3711
+ id: 'radial-spotlight-glow',
3712
+ snippet: `radial-gradient spotlight glow "${name}" (${colorToHex(chromatic.color)} a${alpha} → transparent) on ${Math.round(width)}x${Math.round(height)} surface`,
3713
+ }];
3714
+ }
3715
+
3716
+ // Read the raw radial-gradient source off an element's computed style, with a
3717
+ // fallback to the `background` shorthand and the inline style attribute for
3718
+ // engines that don't decompose the shorthand into backgroundImage.
3719
+ function elementGradientValue(style, el) {
3720
+ const bgImage = style.backgroundImage && style.backgroundImage !== 'none' ? style.backgroundImage : '';
3721
+ if (/radial-gradient/i.test(bgImage)) return bgImage;
3722
+ const bg = style.background || '';
3723
+ if (/radial-gradient/i.test(bg)) return bg;
3724
+ const rawStyle = el?.getAttribute?.('style') || '';
3725
+ const m = rawStyle.match(/background(?:-image)?\s*:\s*([^;]+)/i);
3726
+ if (m && /radial-gradient/i.test(m[1])) return m[1];
3727
+ return '';
3728
+ }
3729
+
3730
+ function spotlightLabel(el) {
3731
+ const dataName = el.getAttribute?.('data-name');
3732
+ if (dataName) return dataName;
3733
+ if (typeof el.id === 'string' && el.id) return el.id;
3734
+ const cls = typeof el.className === 'string' ? el.className.trim().split(/\s+/)[0] : '';
3735
+ if (cls) return cls;
3736
+ return el.tagName ? el.tagName.toLowerCase() : 'section';
3737
+ }
3738
+
3739
+ function checkElementRadialSpotlightDOM(el) {
3740
+ const style = getComputedStyle(el);
3741
+ const gradientValue = elementGradientValue(style, el);
3742
+ if (!gradientValue) return [];
3743
+ const rect = el.getBoundingClientRect();
3744
+ return checkRadialSpotlight({
3745
+ gradientValue,
3746
+ width: rect.width,
3747
+ height: rect.height,
3748
+ label: spotlightLabel(el),
3749
+ });
3750
+ }
3751
+
3752
+ function checkElementRadialSpotlight(el, style, tag, window) {
3753
+ const gradientValue = elementGradientValue(style, el);
3754
+ if (!gradientValue) return [];
3755
+ // Static engine does no layout — read explicit pixel dimensions from CSS.
3756
+ return checkRadialSpotlight({
3757
+ gradientValue,
3758
+ width: parseFloat(style.width) || 0,
3759
+ height: parseFloat(style.height) || 0,
3760
+ label: spotlightLabel(el),
3761
+ });
1946
3762
  }
1947
3763
 
1948
3764
  const QUALITY_TEXT_TAGS = new Set(['p', 'li', 'td', 'th', 'dd', 'blockquote', 'figcaption']);
@@ -2066,6 +3882,55 @@ function textDescendantsFlushSides(el, rect) {
2066
3882
  return flush;
2067
3883
  }
2068
3884
 
3885
+ // Screen-reader-only ("visually hidden") text is exempt from the tiny-text
3886
+ // floors: it is never rendered, so its size is irrelevant. Detect the two
3887
+ // standard idioms — a known sr-only class on the element or an ancestor, and
3888
+ // the clip / 1px-box pattern. Works in both jsdom (declared styles) and the
3889
+ // browser (computed styles).
3890
+ const SR_ONLY_SELECTOR = '.sr-only, .visually-hidden, .visuallyhidden, .screen-reader, .screen-reader-only, .screenreader, .a11y-hidden, .hidden-visually, [class*="sr-only" i], [class*="visually-hidden" i], [class*="visuallyhidden" i], [class*="screen-reader" i], [class*="screenreader" i]';
3891
+ function isVisuallyHidden(el, style) {
3892
+ if ((el.matches && el.matches(SR_ONLY_SELECTOR)) || (el.closest && el.closest(SR_ONLY_SELECTOR))) return true;
3893
+ const pos = style.position || '';
3894
+ if (pos === 'absolute' || pos === 'fixed') {
3895
+ const clip = style.clip || '';
3896
+ const clipPath = style.clipPath || style.webkitClipPath || style['clip-path'] || '';
3897
+ if (/rect\(\s*0/.test(clip) || /inset\(\s*(?:50%|99|100%)/.test(clipPath)) return true;
3898
+ const w = parseFloat(style.width);
3899
+ const h = parseFloat(style.height);
3900
+ const overflow = style.overflow || '';
3901
+ if ((w === 1 || h === 1) && (overflow === 'hidden' || overflow === 'clip')) return true;
3902
+ }
3903
+ return false;
3904
+ }
3905
+
3906
+ // Elements whose text is never painted: document metadata and script/style
3907
+ // payloads. Their JS / CSS / JSON-LD text satisfies `hasDirectText`, and on
3908
+ // sites that set `html { font-size: 62.5% }` their inherited computed size is
3909
+ // 10px — so the text-size floors flag them as tiny body copy even though
3910
+ // nothing renders (issue #408: dozens of phantom "10px body text" findings on
3911
+ // every Shopify page). Exclude them, plus anything the cascade resolves to
3912
+ // display:none / visibility:hidden. The jsdom path can't lay out, so the
3913
+ // tag/attribute-based exclusions carry the weight there; the display checks are
3914
+ // computed-style reads that resolve without layout in both adapters.
3915
+ const NON_RENDERED_TAGS = new Set([
3916
+ 'script', 'style', 'title', 'noscript', 'template', 'head',
3917
+ 'meta', 'link', 'base', 'param', 'source', 'track', 'datalist',
3918
+ 'col', 'colgroup', 'map', 'area',
3919
+ ]);
3920
+ function isNonRenderedText(el, tag, style) {
3921
+ const t = (tag || '').toLowerCase();
3922
+ if (NON_RENDERED_TAGS.has(t)) return true;
3923
+ // Descendants of <head> never render even when the tag itself would
3924
+ // (some sites nest <noscript>/<template> content there).
3925
+ if (el && el.closest && el.closest('head')) return true;
3926
+ if (style) {
3927
+ if (style.display === 'none') return true;
3928
+ const vis = style.visibility;
3929
+ if (vis === 'hidden' || vis === 'collapse') return true;
3930
+ }
3931
+ return false;
3932
+ }
3933
+
2069
3934
  // Pure quality checks. Most run on computed CSS and DOM-only inputs (work in
2070
3935
  // jsdom and the browser). Two checks (line-length, cramped-padding) gate on
2071
3936
  // element rect dimensions, which jsdom can't compute — pass `rect: null` from
@@ -2076,8 +3941,13 @@ function textDescendantsFlushSides(el, rect) {
2076
3941
  function checkQuality(opts) {
2077
3942
  const { el, tag, style, hasDirectText, textLen, fontSize, lineHeightPx, letterSpacingPx, rect, lineMax = 80, viewportWidth = 0, win = null } = opts;
2078
3943
  const findings = [];
2079
- // Skip browser extension injected elements
2080
- const elId = el.id || '';
3944
+ // Skip browser extension injected elements. Read the id via getAttribute
3945
+ // whenever `el.id` is not a string: on a <form> (and other
3946
+ // [LegacyOverrideBuiltIns] hosts) a named control like <input name="id">
3947
+ // shadows the builtin `id` getter and returns the control element, whose
3948
+ // `.startsWith` is undefined and throws (issue #407 — every Shopify product
3949
+ // form ships an <input name="id">).
3950
+ const elId = typeof el.id === 'string' ? el.id : (el.getAttribute?.('id') || '');
2081
3951
  if (elId.startsWith('claude-') || elId.startsWith('cic-')) return findings;
2082
3952
 
2083
3953
  // --- Line length too long --- (browser-only: needs rect.width)
@@ -2345,11 +4215,67 @@ function checkQuality(opts) {
2345
4215
  const skipTags = ['sub', 'sup', 'code', 'kbd', 'samp', 'var', 'caption', 'figcaption'];
2346
4216
  const inUIContext = el.closest && el.closest('button, a, label, summary, pre, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="option"], nav, footer, [aria-hidden="true"], [class*="badge" i], [class*="caption" i], [class*="chip" i], [class*="code" i], [class*="console" i], [class*="diff" i], [class*="label" i], [class*="meta" i], [class*="mock" i], [class*="pill" i], [class*="preview" i], [class*="tag" i], [class*="terminal" i], [class*="writes" i]');
2347
4217
  const isUppercase = style.textTransform === 'uppercase';
2348
- if (!skipTags.includes(tag) && !inUIContext && !isUppercase) {
4218
+ if (!skipTags.includes(tag) && !inUIContext && !isUppercase && !isNonRenderedText(el, tag, style)) {
2349
4219
  findings.push({ id: 'tiny-text', snippet: `${fontSize}px body text` });
2350
4220
  }
2351
4221
  }
2352
4222
 
4223
+ // --- Undersized functional / UI text ---
4224
+ // Complements `tiny-text` above, which owns long body copy and deliberately
4225
+ // EXEMPTS the UI furniture layer (nav, footer, links, buttons, labels,
4226
+ // uppercase micro-labels). This rule targets exactly that blind spot: the
4227
+ // interactive and short content-bearing text — nav items, buttons, labels,
4228
+ // table cells, meta rows, timecodes — shipped below an 11px floor.
4229
+ //
4230
+ // The live failure it closes: a build shipped its entire furniture layer at
4231
+ // 8px, and the design hook waved it through because 8px had been added to
4232
+ // the DESIGN.md size ramp. Being on the ramp is a token argument, not a
4233
+ // legibility one, so this rule ignores the design system entirely — a value
4234
+ // on the ramp is still flagged.
4235
+ //
4236
+ // Floors: 11px for anything functional. The floor holds inside a footer;
4237
+ // only NON-interactive legal smallprint gets the softer 10px floor. Exempts
4238
+ // sup/sub, visually-hidden (sr-only) text, and code/terminal contexts.
4239
+ // Uppercase letterspaced micro-labels are still functional — not exempt.
4240
+ {
4241
+ const directText = [...el.childNodes]
4242
+ .filter(n => n.nodeType === 3)
4243
+ .map(n => n.textContent || '')
4244
+ .join('')
4245
+ .replace(/\s+/g, ' ')
4246
+ .trim();
4247
+ const dtLen = directText.length;
4248
+ // `option` renders (in native select popups) so it stays a local skip;
4249
+ // script/style/title/noscript/head-descendants and display:none /
4250
+ // visibility:hidden are handled by isNonRenderedText (shared with tiny-text).
4251
+ const UI_SKIP_TAGS = new Set(['sub', 'sup', 'option']);
4252
+ // jsdom resolves the parent chain in resolveFontSizePx, so em/rem/%-sized
4253
+ // text that computes at or above the floor never reaches here. The browser
4254
+ // adapter additionally catches values only resolvable with real layout
4255
+ // (e.g. viewport-relative units, cascade winners set in linked sheets).
4256
+ if (fontSize > 0 && fontSize < 11 && dtLen >= 2 && !UI_SKIP_TAGS.has(tag) && !isNonRenderedText(el, tag, style)) {
4257
+ const EXEMPT_CONTEXT = 'pre, code, kbd, samp, var, svg, [aria-hidden="true"], [class*="terminal" i], [class*="console" i], [class*="code" i], [class*="mock" i], [class*="editor" i], [class*="syntax" i], [class*="diff" i]';
4258
+ const isExemptContext = (el.matches && el.matches(EXEMPT_CONTEXT)) || (el.closest && el.closest(EXEMPT_CONTEXT));
4259
+ if (!isExemptContext && !isVisuallyHidden(el, style)) {
4260
+ const INTERACTIVE = 'a[href], button, summary, label, select, textarea, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="option"], [role="checkbox"], [role="radio"], [role="switch"], [role="treeitem"], [tabindex]';
4261
+ const FURNITURE = 'nav, [role="navigation"], td, th, [role="gridcell"], [role="cell"], caption, figcaption, dt, dd, footer, [class*="meta" i], [class*="label" i], [class*="badge" i], [class*="chip" i], [class*="pill" i], [class*="tag" i], [class*="kicker" i], [class*="eyebrow" i], [class*="breadcrumb" i], [class*="timestamp" i], [class*="category" i], [class*="caption" i], [class*="nav" i]';
4262
+ const SMALLPRINT = 'small, footer, [class*="legal" i], [class*="copyright" i], [class*="fineprint" i], [class*="fine-print" i], [class*="smallprint" i], [class*="small-print" i], [class*="disclaimer" i], [class*="disclosure" i], [class*="footnote" i]';
4263
+ const isInteractive = (el.matches && el.matches(INTERACTIVE)) || (el.closest && el.closest(INTERACTIVE));
4264
+ const isFurniture = (el.matches && el.matches(FURNITURE)) || (el.closest && el.closest(FURNITURE));
4265
+ const isSmallprint = (el.matches && el.matches(SMALLPRINT)) || (el.closest && el.closest(SMALLPRINT));
4266
+ const floor = (!isInteractive && isSmallprint) ? 10 : 11;
4267
+ // Fire on functional text only: interactive, structural furniture, or
4268
+ // any short (<=20-char) run — the label / meta / timecode shape. Long
4269
+ // non-furniture body copy stays with `tiny-text`, so the two rules
4270
+ // never double-flag the same element.
4271
+ if (fontSize < floor && (isInteractive || isFurniture || dtLen <= 20)) {
4272
+ const excerpt = directText.slice(0, 40);
4273
+ findings.push({ id: 'undersized-ui-text', snippet: `${fontSize}px functional text "${excerpt}" (below ${floor}px floor)` });
4274
+ }
4275
+ }
4276
+ }
4277
+ }
4278
+
2353
4279
  // --- All-caps body text ---
2354
4280
  if (hasDirectText && textLen > 30 && style.textTransform === 'uppercase') {
2355
4281
  if (!['h1','h2','h3','h4','h5','h6'].includes(tag)) {
@@ -2442,7 +4368,7 @@ function checkElementQuality(el, style, tag, window) {
2442
4368
  return checkQuality({ el, tag, style, hasDirectText, textLen, fontSize, lineHeightPx, letterSpacingPx, rect: null, win: window });
2443
4369
  }
2444
4370
 
2445
- function checkElementBorders(tag, style, overrides, resolvedRadius) {
4371
+ function checkElementBorders(tag, style, overrides, resolvedRadius, el = null) {
2446
4372
  const sides = ['Top', 'Right', 'Bottom', 'Left'];
2447
4373
  const widths = {}, colors = {};
2448
4374
  for (const s of sides) {
@@ -2469,7 +4395,12 @@ function checkElementBorders(tag, style, overrides, resolvedRadius) {
2469
4395
  const radius = resolvedRadius != null
2470
4396
  ? resolvedRadius
2471
4397
  : (parseFloat(style.borderRadius) || 0);
2472
- return checkBorders(tag, widths, colors, radius);
4398
+ const ownBg = parseAnyColor(style.backgroundColor);
4399
+ return checkBorders(tag, widths, colors, radius, {
4400
+ tabContext: isTabContextElement(el),
4401
+ statusContext: isStatusContextElement(el),
4402
+ badgeLike: !!(ownBg && (ownBg.a ?? 1) > 0.1),
4403
+ });
2473
4404
  }
2474
4405
 
2475
4406
  function checkElementColors(el, style, tag, window, customPropMap, hasAnchorInheritRule) {
@@ -2510,12 +4441,32 @@ function checkElementColors(el, style, tag, window, customPropMap, hasAnchorInhe
2510
4441
  }
2511
4442
  }
2512
4443
 
4444
+ // Own background: resolve var()/oklch() tokens through the custom-property
4445
+ // map first (mirrors the textColor path above). Without this a chip whose
4446
+ // background is `var(--sev)` reads as no-own-bg in the static engine and
4447
+ // the styled-control contrast exception never engages.
4448
+ let ownBg = (customPropMap ? parseColorResolved(style.backgroundColor, customPropMap) : null)
4449
+ || readOwnBackgroundColor(el, style);
4450
+
4451
+ // Full-cover surface pseudo (static): the cascade pass marks elements
4452
+ // whose ::before/::after paints an opaque covering surface. When the
4453
+ // element itself has no usable own background, that pseudo is the real
4454
+ // surface for contrast purposes.
4455
+ let finalEffectiveBg = effectiveBg;
4456
+ if ((!ownBg || (ownBg.a ?? 1) <= 0.5) && typeof window.getPseudoSurface === 'function') {
4457
+ const pseudoSurface = window.getPseudoSurface(el);
4458
+ if (pseudoSurface) {
4459
+ ownBg = pseudoSurface;
4460
+ finalEffectiveBg = pseudoSurface;
4461
+ }
4462
+ }
4463
+
2513
4464
  return checkColors({
2514
4465
  tag,
2515
4466
  textColor,
2516
- bgColor: readOwnBackgroundColor(el, style),
2517
- effectiveBg,
2518
- effectiveBgStops: effectiveBg ? null : resolveGradientStops(el, window),
4467
+ bgColor: ownBg,
4468
+ effectiveBg: finalEffectiveBg,
4469
+ effectiveBgStops: finalEffectiveBg ? null : resolveGradientStops(el, window, customPropMap),
2519
4470
  fontSize: parseFloat(style.fontSize) || 16,
2520
4471
  fontWeight: parseInt(style.fontWeight) || 400,
2521
4472
  hasDirectText,
@@ -2526,6 +4477,50 @@ function checkElementColors(el, style, tag, window, customPropMap, hasAnchorInhe
2526
4477
  });
2527
4478
  }
2528
4479
 
4480
+ // Static-engine adapter for hover-state contrast. Relies on the static
4481
+ // cascade's hover pass (css-cascade.mjs) exposing a per-element hover style
4482
+ // via window.getHoverStyle — present only when a :hover rule changed the
4483
+ // element's color or background-color relative to its resting state.
4484
+ function checkElementHoverContrast(el, style, tag, window) {
4485
+ if (typeof window.getHoverStyle !== 'function') return [];
4486
+ const hover = window.getHoverStyle(el);
4487
+ if (!hover) return [];
4488
+
4489
+ const directText = [...el.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join('');
4490
+ if (directText.trim().length === 0) return [];
4491
+
4492
+ const textColor = parseAnyColor(hover.color);
4493
+ if (!textColor || (textColor.a != null && textColor.a < 1)) return [];
4494
+
4495
+ const restingOwnBg = parseAnyColor(style.backgroundColor);
4496
+ const hoverOwnBg = parseAnyColor(hover.backgroundColor);
4497
+ const ownBg = hoverOwnBg || restingOwnBg;
4498
+
4499
+ // Effective hover background: the element's own hover bg composited over
4500
+ // whatever sits underneath. Bail when the surface can't be resolved to a
4501
+ // solid color — gradient ancestors are handled (as at rest) by the
4502
+ // resting-state check, not duplicated here.
4503
+ let bg = null;
4504
+ if (ownBg && ownBg.a >= 0.99) {
4505
+ bg = ownBg;
4506
+ } else {
4507
+ const under = resolveBackground(el.parentElement || el, window, null);
4508
+ if (!under) return [];
4509
+ bg = ownBg && ownBg.a > 0.1 ? compositeColorOver(ownBg, under) : under;
4510
+ }
4511
+
4512
+ return checkHoverContrast({
4513
+ tag,
4514
+ textColor,
4515
+ bg,
4516
+ ownBgAlpha: ownBg ? ownBg.a ?? 1 : null,
4517
+ fontSize: parseFloat(style.fontSize) || 16,
4518
+ fontWeight: parseInt(style.fontWeight) || 400,
4519
+ hasDirectText: true,
4520
+ isEmojiOnly: isEmojiOnlyText(directText),
4521
+ });
4522
+ }
4523
+
2529
4524
  function checkElementIconTile(el, tag, window) {
2530
4525
  if (!HEADING_TAGS.has(tag)) return [];
2531
4526
  const sibling = el.previousElementSibling;
@@ -2594,7 +4589,8 @@ function checkElementHeroEyebrow(el, style, tag, window, customPropMap) {
2594
4589
  return checkHeroEyebrow({
2595
4590
  headingTag: tag,
2596
4591
  headingText: el.textContent || '',
2597
- headingFontSize: parseFloat(headingFontSizeRaw) || 0,
4592
+ headingFontSize: resolveHeroHeadingSizePx(headingFontSizeRaw),
4593
+ headingInApplicationContext: !!el.closest?.('[role="tabpanel"], [role="dialog"], [role="application"], dialog'),
2598
4594
  siblingTag: sibling.tagName.toLowerCase(),
2599
4595
  siblingText: sibling.textContent || '',
2600
4596
  siblingTextTransform: sibStyle.textTransform || '',
@@ -2602,16 +4598,21 @@ function checkElementHeroEyebrow(el, style, tag, window, customPropMap) {
2602
4598
  siblingLetterSpacing: resolveLengthPx(letterSpacingRaw, siblingFontSize) || 0,
2603
4599
  siblingFontWeight: fontWeightRaw || '',
2604
4600
  siblingColor: colorRaw || '',
4601
+ // Static cascade marks elements matched by a ::before/::after rule
4602
+ // whose geometry is a short chromatic dash (css-cascade.mjs).
4603
+ siblingHasAccentDashPseudo: typeof window.hasAccentDashPseudo === 'function'
4604
+ ? window.hasAccentDashPseudo(sibling)
4605
+ : false,
2605
4606
  });
2606
4607
  }
2607
4608
 
2608
- function checkRepeatedSectionKickersFromDoc(doc, win) {
2609
- const candidates = collectRepeatedSectionKickerCandidates(
4609
+ function checkKickerAboveHeadingFromDoc(doc, win) {
4610
+ const candidates = collectKickerCandidates(
2610
4611
  doc,
2611
4612
  (el) => win.getComputedStyle(el),
2612
4613
  (value, fontSize) => resolveLengthPx(value, fontSize) || 0,
2613
4614
  );
2614
- return checkRepeatedSectionKickers({ candidates });
4615
+ return checkKickerAboveHeading({ candidates });
2615
4616
  }
2616
4617
 
2617
4618
  function checkElementMotion(tag, style) {
@@ -2625,8 +4626,10 @@ function checkElementMotion(tag, style) {
2625
4626
  }
2626
4627
 
2627
4628
  function checkElementGlow(tag, style, effectiveBg) {
2628
- if (!style.boxShadow || style.boxShadow === 'none') return [];
2629
- return checkGlow({ tag, boxShadow: style.boxShadow, effectiveBg });
4629
+ const boxShadow = style.boxShadow && style.boxShadow !== 'none' ? style.boxShadow : '';
4630
+ const textShadow = style.textShadow && style.textShadow !== 'none' ? style.textShadow : '';
4631
+ if (!boxShadow && !textShadow) return [];
4632
+ return checkGlow({ tag, boxShadow, textShadow, effectiveBg });
2630
4633
  }
2631
4634
 
2632
4635
  // ─── Section 6: Page-Level Checks ───────────────────────────────────────────
@@ -2736,6 +4739,157 @@ function checkLayout() {
2736
4739
  return findings;
2737
4740
  }
2738
4741
 
4742
+ // Heading rhythm (browser-only): a heading binds to the content it
4743
+ // introduces, so its rendered space above must exceed its space below.
4744
+ // Margins alone can't be trusted (collapsing, flex rows, section padding),
4745
+ // so this measures actual getBoundingClientRect gaps between the heading
4746
+ // and the nearest content genuinely above / below it. Fires only when two
4747
+ // or more headings violate the principle — a single occurrence is noise.
4748
+ function checkHeadingRhythmDOM() {
4749
+ const MIN_VIOLATIONS = 2;
4750
+ const CARD_EXEMPT_HEIGHT = 200;
4751
+ const MAX_BELOW_PX = 160; // beyond this the heading isn't binding to nearby content at all
4752
+ const MIN_DEFICIT_PX = 12;
4753
+
4754
+ function isVisibleFlow(el) {
4755
+ const style = getComputedStyle(el);
4756
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
4757
+ if (parseFloat(style.opacity || '1') <= 0.05) return false;
4758
+ if (style.position === 'absolute' || style.position === 'fixed' || style.position === 'sticky') return false;
4759
+ const rect = el.getBoundingClientRect();
4760
+ return rect.width >= 1 && rect.height >= 1;
4761
+ }
4762
+
4763
+ // Edges only count when they share the heading's column — grid layouts
4764
+ // put content beside a heading, and a far-away element in another column
4765
+ // says nothing about the heading's vertical rhythm.
4766
+ function overlapsX(sr, rect) {
4767
+ return Math.min(sr.right, rect.right) - Math.max(sr.left, rect.left) >= 8;
4768
+ }
4769
+
4770
+ // Does this container draw its own top boundary (background, top border,
4771
+ // shadow)? Crossing out of such a container means the container edge is
4772
+ // the separator above the heading, not raw whitespace — exempt.
4773
+ function hasOwnTopBoundary(el) {
4774
+ const style = getComputedStyle(el);
4775
+ const bg = parseAnyColor(style.backgroundColor || '');
4776
+ if (bg && (bg.a ?? 1) > 0.05) return true;
4777
+ if ((parseFloat(style.borderTopWidth) || 0) > 0) return true;
4778
+ if (style.boxShadow && style.boxShadow !== 'none') return true;
4779
+ return false;
4780
+ }
4781
+
4782
+ // Eyebrows, kickers, and index labels sitting directly on top of a
4783
+ // heading belong to the heading's own cluster — space above is measured
4784
+ // from the top of the cluster, not from the label to the heading.
4785
+ function clusterTop(h, rect) {
4786
+ const headingFontSize = parseFloat(getComputedStyle(h).fontSize) || 16;
4787
+ let topEl = h;
4788
+ let top = rect.top;
4789
+ for (let i = 0; i < 3; i++) {
4790
+ const sib = topEl.previousElementSibling;
4791
+ if (!sib || !isVisibleFlow(sib)) break;
4792
+ const sr = sib.getBoundingClientRect();
4793
+ if (!overlapsX(sr, rect)) break;
4794
+ const gap = top - sr.bottom;
4795
+ if (gap < 0 || gap >= 28 || sr.height > 60) break;
4796
+ const text = (sib.textContent || '').trim();
4797
+ const sibFontSize = parseFloat(getComputedStyle(sib).fontSize) || 16;
4798
+ const labelLike = sibFontSize < headingFontSize * 0.75 || text.length <= 40;
4799
+ if (!labelLike || text.length > 80) break;
4800
+ topEl = sib;
4801
+ top = sr.top;
4802
+ }
4803
+ return { topEl, top };
4804
+ }
4805
+
4806
+ // Nearest content edge strictly above the heading cluster. Walks
4807
+ // previous siblings, then out through ancestors. Skips elements that
4808
+ // vertically overlap (flex-row companions, sticky rails) or sit in
4809
+ // another column. Returns null when nothing qualifies — first content
4810
+ // on the page, or the top of a visually bounded container.
4811
+ function edgeAbove(startEl, top, rect) {
4812
+ let node = startEl;
4813
+ while (node && node !== document.body) {
4814
+ let sib = node.previousElementSibling;
4815
+ while (sib) {
4816
+ if (isVisibleFlow(sib)) {
4817
+ const sr = sib.getBoundingClientRect();
4818
+ if (sr.bottom <= top + 2 && overlapsX(sr, rect)) return sr.bottom;
4819
+ }
4820
+ sib = sib.previousElementSibling;
4821
+ }
4822
+ const parent = node.parentElement;
4823
+ if (!parent || parent === document.body) return null;
4824
+ // Leaving a container upward: if it draws its own top edge, that
4825
+ // edge separates the heading from whatever sits above.
4826
+ if (hasOwnTopBoundary(parent)) return null;
4827
+ node = parent;
4828
+ }
4829
+ return null;
4830
+ }
4831
+
4832
+ // Nearest content edge strictly below the heading — the block the
4833
+ // heading introduces. Crosses wrappers freely (headings often share a
4834
+ // row wrapper with an eyebrow or index label).
4835
+ function edgeBelow(h, rect) {
4836
+ let node = h;
4837
+ while (node && node !== document.body) {
4838
+ let sib = node.nextElementSibling;
4839
+ while (sib) {
4840
+ if (isVisibleFlow(sib)) {
4841
+ const sr = sib.getBoundingClientRect();
4842
+ if (sr.top >= rect.bottom - 2 && overlapsX(sr, rect)) return sr.top;
4843
+ }
4844
+ sib = sib.nextElementSibling;
4845
+ }
4846
+ node = node.parentElement;
4847
+ }
4848
+ return null;
4849
+ }
4850
+
4851
+ function insideSmallCard(h) {
4852
+ let cur = h.parentElement;
4853
+ while (cur && cur !== document.body) {
4854
+ if (isCardLikeDOM(cur)) {
4855
+ const cr = cur.getBoundingClientRect();
4856
+ if (cr.height < CARD_EXEMPT_HEIGHT) return true;
4857
+ }
4858
+ cur = cur.parentElement;
4859
+ }
4860
+ return false;
4861
+ }
4862
+
4863
+ const candidates = [];
4864
+ for (const h of document.querySelectorAll('h2, h3, h4')) {
4865
+ if (!isVisibleFlow(h)) continue;
4866
+ const text = (h.textContent || '').trim().replace(/\s+/g, ' ');
4867
+ if (text.length < 3) continue;
4868
+ const rect = h.getBoundingClientRect();
4869
+ const belowTop = edgeBelow(h, rect);
4870
+ if (belowTop == null) continue; // heading introduces nothing measurable
4871
+ const { topEl, top } = clusterTop(h, rect);
4872
+ const aboveBottom = edgeAbove(topEl, top, rect);
4873
+ if (aboveBottom == null) continue; // first content, or bounded container
4874
+ if (insideSmallCard(h)) continue;
4875
+ const above = Math.max(0, top - aboveBottom);
4876
+ const below = Math.max(0, belowTop - rect.bottom);
4877
+ if (below < 6 || below > MAX_BELOW_PX) continue;
4878
+ // Violation: the space above clearly fails to exceed the space below.
4879
+ // Near-equal gaps are ambiguous rather than inverted, so they pass.
4880
+ if (above < below * 0.75 && below - above >= MIN_DEFICIT_PX) {
4881
+ candidates.push({ el: h, tag: h.tagName.toLowerCase(), text: text.slice(0, 60), above, below });
4882
+ }
4883
+ }
4884
+
4885
+ if (candidates.length < MIN_VIOLATIONS) return [];
4886
+ return candidates.map(c => ({
4887
+ type: 'heading-rhythm',
4888
+ detail: `${c.tag} "${c.text}" has ${Math.round(c.above)}px above vs ${Math.round(c.below)}px below — it reads as bound to the block above (${candidates.length} headings on page)`,
4889
+ el: c.el,
4890
+ }));
4891
+ }
4892
+
2739
4893
  // Node page-level checks — take document/window as parameters
2740
4894
 
2741
4895
  function checkPageTypography(doc, win) {
@@ -2878,6 +5032,135 @@ function checkPageLayout(doc, win) {
2878
5032
  return findings;
2879
5033
  }
2880
5034
 
5035
+ // ── Repeated text inside one container ──────────────────────────────────────
5036
+ // The same literal string rendered 3+ times in structurally different spots
5037
+ // inside one bordered/elevated container — typically a status word wired
5038
+ // into every slot of a card template. Legitimate repetition is structural:
5039
+ // table columns, calendar grids, nav/menu lists, and templated sibling rows
5040
+ // all repeat text in *parallel* positions, so occurrences whose element
5041
+ // paths inside the container are identical (or live in dedicated repetition
5042
+ // structures) never count. Only 3+ occurrences at 3+ distinct structural
5043
+ // positions flag.
5044
+
5045
+ const REPEATED_TEXT_SKIP_SELECTOR = [
5046
+ 'table',
5047
+ 'select',
5048
+ 'datalist',
5049
+ 'nav',
5050
+ 'menu',
5051
+ '[role="navigation"]',
5052
+ '[role="menu"]',
5053
+ '[role="menubar"]',
5054
+ '[role="listbox"]',
5055
+ '[role="grid"]',
5056
+ '[role="tablist"]',
5057
+ '[role="radiogroup"]',
5058
+ '[aria-hidden="true"]',
5059
+ ].join(',');
5060
+
5061
+ const REPEATED_TEXT_CONTAINER_TAGS = new Set([
5062
+ 'div', 'section', 'article', 'aside', 'main', 'figure', 'form', 'fieldset', 'details', 'li',
5063
+ ]);
5064
+
5065
+ // A container worth attributing text to: visibly bounded (border on most
5066
+ // sides or an elevation shadow) and surface-like (radius or own background).
5067
+ function isRepeatedTextContainer(style) {
5068
+ if (!style) return false;
5069
+ const hasShadow = !!(style.boxShadow && style.boxShadow !== 'none' && style.boxShadow !== '');
5070
+ const borderSides = ['Top', 'Right', 'Bottom', 'Left']
5071
+ .filter(side => (parseFloat(style[`border${side}Width`]) || 0) >= 1).length;
5072
+ const hasBorder = borderSides >= 3;
5073
+ const hasRadius = (parseFloat(style.borderRadius) || 0) > 0;
5074
+ const bg = parseRgb(style.backgroundColor) || parseAnyColor(style.backgroundColor);
5075
+ const hasBg = !!(bg && (bg.a ?? 1) > 0.1);
5076
+ return isCardLikeFromProps(hasShadow, hasBorder, hasRadius, hasBg);
5077
+ }
5078
+
5079
+ function collectRepeatedContainerTextFindings(doc, getStyle, opts = {}) {
5080
+ const isVisible = opts.isVisible || (() => true);
5081
+ const findings = [];
5082
+
5083
+ const containers = [];
5084
+ const containerSet = new Set();
5085
+ for (const el of doc.querySelectorAll('*')) {
5086
+ if (!REPEATED_TEXT_CONTAINER_TAGS.has(el.tagName.toLowerCase())) continue;
5087
+ if (el.closest?.(REPEATED_TEXT_SKIP_SELECTOR)) continue;
5088
+ if (!isRepeatedTextContainer(getStyle(el))) continue;
5089
+ containers.push(el);
5090
+ containerSet.add(el);
5091
+ }
5092
+
5093
+ for (const container of containers) {
5094
+ if (!isVisible(container)) continue;
5095
+ const descendants = container.querySelectorAll('*');
5096
+ // Page-scale wrappers that merely happen to carry a background are not
5097
+ // the "one card" this rule reasons about.
5098
+ if (descendants.length > 250) continue;
5099
+
5100
+ const groups = new Map();
5101
+ for (const d of descendants) {
5102
+ // Attribute text to the innermost container only.
5103
+ let anc = d.parentElement;
5104
+ let ownedByInner = false;
5105
+ while (anc && anc !== container) {
5106
+ if (containerSet.has(anc)) { ownedByInner = true; break; }
5107
+ anc = anc.parentElement;
5108
+ }
5109
+ if (ownedByInner) continue;
5110
+ if (d.closest?.(REPEATED_TEXT_SKIP_SELECTOR)) continue;
5111
+ // Icon-font glyph names read as text but render as symbols.
5112
+ if (/icon|material-symbols|(?:^|\s)fa[srlbd]?(?:\s|-|$)/i.test(String(d.getAttribute?.('class') || ''))) continue;
5113
+ if (!isVisible(d)) continue;
5114
+
5115
+ const direct = [...d.childNodes]
5116
+ .filter(n => n.nodeType === 3)
5117
+ .map(n => n.textContent)
5118
+ .join(' ')
5119
+ .replace(/\s+/g, ' ')
5120
+ .trim();
5121
+ if (direct.length < 4 || direct.length > 48) continue;
5122
+ if (!/[a-zA-Z]/.test(direct)) continue;
5123
+
5124
+ // Structural signature: the element path from the occurrence up to
5125
+ // the container. Parallel/templated repetition shares one signature.
5126
+ const sig = [];
5127
+ for (let cur = d; cur && cur !== container; cur = cur.parentElement) {
5128
+ const cls = String(cur.getAttribute?.('class') || '')
5129
+ .trim().split(/\s+/).filter(Boolean).sort().join('.');
5130
+ sig.push(cur.tagName.toLowerCase() + (cls ? `.${cls}` : ''));
5131
+ }
5132
+ if (!groups.has(direct)) groups.set(direct, []);
5133
+ groups.get(direct).push(sig.join('>'));
5134
+ }
5135
+
5136
+ for (const [text, sigs] of groups) {
5137
+ if (sigs.length < 3) continue;
5138
+ if (new Set(sigs).size < 3) continue;
5139
+ findings.push({
5140
+ id: 'repeated-container-text',
5141
+ snippet: `"${text.slice(0, 40)}" rendered ${sigs.length}× in distinct spots inside ${classSelector(container)}`,
5142
+ });
5143
+ }
5144
+ }
5145
+ return findings;
5146
+ }
5147
+
5148
+ function checkRepeatedContainerTextFromDoc(doc, win) {
5149
+ return collectRepeatedContainerTextFindings(
5150
+ doc,
5151
+ (el) => win.getComputedStyle(el),
5152
+ { isVisible: (el) => String(win.getComputedStyle(el).display || '') !== 'none' },
5153
+ );
5154
+ }
5155
+
5156
+ function checkRepeatedContainerTextDOM() {
5157
+ return collectRepeatedContainerTextFindings(
5158
+ document,
5159
+ (el) => getComputedStyle(el),
5160
+ { isVisible: isRenderedForBrowserRule },
5161
+ );
5162
+ }
5163
+
2881
5164
  // ─── Cream / beige palette (the default "tasteful" AI surface) ────────────────
2882
5165
  // A warm, lightly-tinted off-white page background — light, with R≥G≥B and a
2883
5166
  // small warm tint (not white, not a strong color). The current reflex surface.
@@ -2991,7 +5274,7 @@ function checkElementOversizedH1DOM(el) {
2991
5274
  return checkOversizedH1({ tag, fontSize, headingText, rect, viewportWidth, viewportHeight });
2992
5275
  }
2993
5276
 
2994
- // ─── GPT tell: hairline border + wide diffuse shadow (gated --gpt) ────────────
5277
+ // ─── Generated-UI tell: hairline border + wide diffuse shadow ────────────────
2995
5278
  const CSS_COLOR_TOKEN_RE = /(?:rgba?|hsla?|oklch|oklab|lab|lch|color)\([^)]*\)|#[0-9a-fA-F]{3,8}\b|\b(?:black|white|transparent|currentcolor)\b/gi;
2996
5279
 
2997
5280
  function shadowLayerAlpha(layer) {
@@ -3316,9 +5599,645 @@ function checkElementTextOverflowDOM(el) {
3316
5599
  if (el.clientWidth > 0 && delta >= 16) {
3317
5600
  return [{ id: 'text-overflow', snippet: `${classSelector(el)} overflows its box by ${Math.round(delta)}px` }];
3318
5601
  }
5602
+
5603
+ // Inline text owners have no client geometry (clientWidth/scrollWidth are
5604
+ // both 0), so the scrollWidth path above never sees them. Their overflow
5605
+ // registers only on a block ancestor, and that ancestor has no direct text
5606
+ // so the ownership gate skips it. (The shipped miss: a nowrap inline
5607
+ // <span> spilling 45px past its fixed-width grid cell.) Measure the inline
5608
+ // box against the padding box of its nearest block container instead.
5609
+ if (el.clientWidth === 0 && rect && rect.width > 0) {
5610
+ let container = el.parentElement;
5611
+ while (container && container.clientWidth === 0) container = container.parentElement;
5612
+ if (!container) return [];
5613
+ // Transforms make rect comparisons lie; skip anything on that path.
5614
+ for (let p = el; p && p !== container.parentElement; p = p.parentElement) {
5615
+ const t = getComputedStyle(p).transform;
5616
+ if (t && t !== 'none') return [];
5617
+ }
5618
+ const cRect = container.getBoundingClientRect();
5619
+ const contentRight = cRect.left + container.clientLeft + container.clientWidth;
5620
+ const spill = rect.right - contentRight;
5621
+ if (spill >= 16) {
5622
+ return [{ id: 'text-overflow', snippet: `${classSelector(el)} overflows its container by ${Math.round(spill)}px` }];
5623
+ }
5624
+ }
3319
5625
  return [];
3320
5626
  }
3321
5627
 
5628
+ // ---------------------------------------------------------------------------
5629
+ // Blinking cursor (browser-only)
5630
+ // ---------------------------------------------------------------------------
5631
+
5632
+ // Block / underscore glyphs commonly used as a fake text cursor.
5633
+ const CURSOR_GLYPH_RE = /^[_|▀-▟■▮❙❚|]$/;
5634
+
5635
+ // How far down the page still counts as the first-viewport / hero region.
5636
+ // Hero compositions regularly run past a literal viewport height, so the
5637
+ // gate is a landing-region budget, not an exact fold line.
5638
+ const CURSOR_FIRST_VIEWPORT_PX = 1200;
5639
+
5640
+ // Do the named @keyframes only toggle visibility (opacity dropping to ~0 or
5641
+ // visibility:hidden), i.e. a blink rather than a fade/move/spin? Walks the
5642
+ // live CSSOM; cross-origin sheets are skipped.
5643
+ function keyframesToggleVisibilityDOM(name) {
5644
+ if (!name) return false;
5645
+ for (const sheet of document.styleSheets) {
5646
+ let rules;
5647
+ try { rules = sheet.cssRules || sheet.rules; } catch { continue; }
5648
+ if (!rules) continue;
5649
+ const stack = [...rules];
5650
+ while (stack.length) {
5651
+ const rule = stack.shift();
5652
+ if (rule.cssRules && rule.type !== 7) { stack.push(...rule.cssRules); continue; }
5653
+ if (rule.type !== 7 || rule.name !== name) continue; // 7 = KEYFRAMES_RULE
5654
+ let togglesOut = false;
5655
+ for (const frame of rule.cssRules || []) {
5656
+ const fs = frame.style;
5657
+ if (!fs) continue;
5658
+ for (let i = 0; i < fs.length; i++) {
5659
+ const prop = fs[i];
5660
+ if (prop === 'opacity') {
5661
+ if ((parseFloat(fs.getPropertyValue('opacity')) || 0) <= 0.15) togglesOut = true;
5662
+ } else if (prop === 'visibility') {
5663
+ if (/hidden/i.test(fs.getPropertyValue('visibility'))) togglesOut = true;
5664
+ } else if (prop !== 'animation-timing-function') {
5665
+ return false; // keyframes animate something else — not a blink
5666
+ }
5667
+ }
5668
+ }
5669
+ return togglesOut;
5670
+ }
5671
+ }
5672
+ return false;
5673
+ }
5674
+
5675
+ // Decorative blinking cursor: a small block / underscore element bound to an
5676
+ // infinite blink animation, sitting in the first-viewport region of a page.
5677
+ // Real editable surfaces (inputs, textareas, contenteditable, role=textbox)
5678
+ // draw their own caret and are exempt. Round pulsing dots stay with the
5679
+ // pulsing-dot rule.
5680
+ function checkElementBlinkingCursorDOM(el) {
5681
+ const tag = el.tagName.toLowerCase();
5682
+ if (['input', 'textarea', 'select', 'img', 'svg', 'script', 'style'].includes(tag)) return [];
5683
+ const style = getComputedStyle(el);
5684
+
5685
+ const iterations = (style.animationIterationCount || '').split(',').map(s => s.trim());
5686
+ if (!iterations.includes('infinite')) return [];
5687
+ const names = (style.animationName || '').split(',').map(s => s.trim()).filter(n => n && n !== 'none');
5688
+ if (names.length === 0) return [];
5689
+ const blinkName = names.find(n => /blink|caret|cursor/i.test(n))
5690
+ || names.find(n => keyframesToggleVisibilityDOM(n));
5691
+ if (!blinkName) return [];
5692
+
5693
+ // Real caret contexts are exempt.
5694
+ if (el.isContentEditable || el.closest('[contenteditable=""], [contenteditable="true"], [role="textbox"]')) return [];
5695
+
5696
+ const rect = el.getBoundingClientRect();
5697
+ if (rect.width <= 0 || rect.height <= 0) return [];
5698
+
5699
+ // First-viewport gate: the hero cliché, not a footer terminal.
5700
+ const pageTop = rect.top + (window.scrollY || 0);
5701
+ if (pageTop > CURSOR_FIRST_VIEWPORT_PX) return [];
5702
+
5703
+ // Cursor shape: a lone block/underscore glyph, or an empty solid
5704
+ // rectangle sized like a text caret (block or underscore form).
5705
+ const text = (el.textContent || '').trim();
5706
+ const glyphCursor = text.length === 1 && CURSOR_GLYPH_RE.test(text);
5707
+ let blockCursor = false;
5708
+ if (!glyphCursor) {
5709
+ if (text.length > 0 || el.childElementCount > 0) return [];
5710
+ const bg = parseAnyColor(style.backgroundColor || '');
5711
+ const filled = bg && (bg.a ?? 1) > 0.2;
5712
+ const hasBorderFill = ['Left', 'Right', 'Bottom'].some(
5713
+ side => (parseFloat(style[`border${side}Width`]) || 0) >= 1,
5714
+ );
5715
+ if (!filled && !hasBorderFill) return [];
5716
+ const vertical = rect.width >= 1 && rect.width <= 24 && rect.height >= 6 && rect.height <= 48 && rect.height >= rect.width;
5717
+ const underscore = rect.height >= 1 && rect.height <= 6 && rect.width >= 4 && rect.width <= 24;
5718
+ if (!vertical && !underscore) return [];
5719
+ // Round dots are the pulsing-dot rule's territory.
5720
+ const radiusPx = parseFloat(style.borderRadius) || 0;
5721
+ if (radiusPx >= 0.4 * Math.min(rect.width, rect.height)) return [];
5722
+ blockCursor = true;
5723
+ }
5724
+ if (!glyphCursor && !blockCursor) return [];
5725
+
5726
+ // Hero-region promotion: a fake caret blinking in the first ~900px or
5727
+ // inside the page chrome is the shipped hero cliché, not an incidental
5728
+ // flourish. Promote those from the registry's advisory to warning;
5729
+ // lower first-viewport occurrences keep the default severity.
5730
+ const inHeroRegion = pageTop <= 900
5731
+ || !!(el.closest && el.closest('header, nav, [role="banner"], [role="navigation"]'));
5732
+ return [{
5733
+ id: 'blinking-cursor',
5734
+ snippet: `${classSelector(el)} — ${Math.round(rect.width)}x${Math.round(rect.height)}px blinking cursor (animation "${blinkName}") in the first viewport`,
5735
+ ...(inHeroRegion ? { severity: 'warning' } : {}),
5736
+ }];
5737
+ }
5738
+
5739
+ // ---------------------------------------------------------------------------
5740
+ // Content invisible at rest (browser-only, driven by the URL engine)
5741
+ // ---------------------------------------------------------------------------
5742
+
5743
+ // Tags whose text never renders, or whose hidden state is legitimate UI
5744
+ // (templates, dialogs, native select options). Text inside them stays out of
5745
+ // both the numerator and the denominator.
5746
+ const HIDDEN_TEXT_EXCLUDE_TAGS = new Set([
5747
+ 'script', 'style', 'noscript', 'template', 'title', 'head', 'meta', 'link',
5748
+ 'option', 'optgroup', 'select', 'datalist', 'dialog',
5749
+ ]);
5750
+
5751
+ // Measure how many text characters currently render invisible (computed
5752
+ // opacity ~0 or visibility hidden anywhere on the ancestor chain) versus
5753
+ // visible. display:none / [hidden] / aria-hidden subtrees are legitimately
5754
+ // hidden UI (menus, tab panels, templates): they are excluded from the
5755
+ // denominator entirely rather than counted as invisible.
5756
+ function measureHiddenTextDOM() {
5757
+ const cache = new Map();
5758
+ function stateOf(el) {
5759
+ if (!el || el.nodeType !== 1 || el === document.documentElement) return 'visible';
5760
+ const cached = cache.get(el);
5761
+ if (cached) return cached;
5762
+ let state;
5763
+ const tag = el.tagName.toLowerCase();
5764
+ if (HIDDEN_TEXT_EXCLUDE_TAGS.has(tag)) {
5765
+ state = 'excluded';
5766
+ } else {
5767
+ const parentState = stateOf(el.parentElement);
5768
+ if (parentState === 'excluded') {
5769
+ state = 'excluded';
5770
+ } else {
5771
+ const style = getComputedStyle(el);
5772
+ if (style.display === 'none' || el.hidden || el.getAttribute('aria-hidden') === 'true'
5773
+ || String(style.contentVisibility || '').toLowerCase() === 'hidden') {
5774
+ state = 'excluded';
5775
+ } else if (parentState === 'invisible'
5776
+ || (parseFloat(style.opacity) || 0) <= 0.02
5777
+ || /^(hidden|collapse)$/.test(style.visibility)) {
5778
+ state = 'invisible';
5779
+ } else {
5780
+ state = 'visible';
5781
+ }
5782
+ }
5783
+ }
5784
+ cache.set(el, state);
5785
+ return state;
5786
+ }
5787
+
5788
+ let totalChars = 0;
5789
+ let hiddenChars = 0;
5790
+ const hiddenSamples = [];
5791
+ for (const el of document.querySelectorAll('body *')) {
5792
+ let len = 0;
5793
+ for (const node of el.childNodes) {
5794
+ if (node.nodeType === 3) len += node.textContent.replace(/\s+/g, ' ').trim().length;
5795
+ }
5796
+ if (!len) continue;
5797
+ const state = stateOf(el);
5798
+ if (state === 'excluded') continue;
5799
+ totalChars += len;
5800
+ if (state === 'invisible') {
5801
+ hiddenChars += len;
5802
+ if (hiddenSamples.length < 3) {
5803
+ const text = String(el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 40);
5804
+ if (text) hiddenSamples.push(text);
5805
+ }
5806
+ }
5807
+ }
5808
+ return { totalChars, hiddenChars, hiddenSamples };
5809
+ }
5810
+
5811
+ // Pure threshold check over a measureHiddenTextDOM() result. The URL engine
5812
+ // calls it AFTER a reveal sweep (scroll through the document so every
5813
+ // IntersectionObserver / scroll reveal had its chance to fire, then back to
5814
+ // the top): a healthy reveal-on-scroll page drops to ~0 invisible text after
5815
+ // the sweep, while a page whose reveal script died keeps most of its text at
5816
+ // opacity 0 forever. Fires only when the invisible share stays above 30%
5817
+ // with a real amount of text behind it.
5818
+ function checkContentHiddenAtRest({ totalChars = 0, hiddenChars = 0, hiddenSamples = [] } = {}) {
5819
+ if (totalChars < 200 || hiddenChars < 150) return [];
5820
+ const share = hiddenChars / totalChars;
5821
+ if (share <= 0.3) return [];
5822
+ const sample = hiddenSamples.length ? ` (e.g. "${hiddenSamples[0]}")` : '';
5823
+ return [{
5824
+ id: 'content-hidden-at-rest',
5825
+ snippet: `${Math.round(share * 100)}% of page text (${hiddenChars} of ${totalChars} chars) stays at opacity 0 / visibility hidden after reveal handlers ran${sample}`,
5826
+ }];
5827
+ }
5828
+
5829
+ // ---------------------------------------------------------------------------
5830
+ // Edge-flush cards in horizontal scrollers (browser-only)
5831
+ // ---------------------------------------------------------------------------
5832
+
5833
+ // A visually-defined card (own opaque background, or borders on 2+ sides)
5834
+ // inside a horizontal scroller, sitting flush against one edge of the
5835
+ // scroller's clip box at rest while keeping a clear gutter on the other
5836
+ // side. The canonical bug: the first snap panel is sized wider than the
5837
+ // scroller, so its cards end exactly at the clip edge with their rounded
5838
+ // corners cut, while every sibling panel keeps its inset. Cards that extend
5839
+ // far past the edge are deliberate peeks and stay exempt.
5840
+ function checkEdgeFlushCardsDOM() {
5841
+ const findings = [];
5842
+ const vh = window.innerHeight || 800;
5843
+ const isScroller = (s) => /(auto|scroll)/.test(s.overflowX || '') || /(auto|scroll)/.test(s.overflow || '');
5844
+
5845
+ for (const scroller of document.querySelectorAll('*')) {
5846
+ const style = getComputedStyle(scroller);
5847
+ if (!isScroller(style)) continue;
5848
+ if (scroller.scrollWidth <= scroller.clientWidth + 8) continue;
5849
+ // At rest only: a user-scrolled or snapped-forward scroller legitimately
5850
+ // shows cut cards at both edges.
5851
+ if (scroller.scrollLeft > 4) continue;
5852
+ const scRect = scroller.getBoundingClientRect();
5853
+ if (scRect.width < 120 || scRect.height < 60) continue;
5854
+ // Landing-region gate: the defect matters where the page opens.
5855
+ if (scRect.top + (window.scrollY || 0) > 2 * vh) continue;
5856
+ const contentLeft = scRect.left + scroller.clientLeft;
5857
+ const contentRight = contentLeft + scroller.clientWidth;
5858
+
5859
+ const flush = [];
5860
+ for (const card of scroller.querySelectorAll('*')) {
5861
+ if (!isRenderedForBrowserRule(card)) continue;
5862
+ // Attribute cards to their nearest scroller only (nested scrollers).
5863
+ let owner = card.parentElement;
5864
+ while (owner && owner !== scroller && !isScroller(getComputedStyle(owner))) owner = owner.parentElement;
5865
+ if (owner !== scroller) continue;
5866
+ const cs = getComputedStyle(card);
5867
+ const rect = card.getBoundingClientRect();
5868
+ if (rect.width < 80 || rect.height < 40) continue;
5869
+ const bg = parseAnyColor(cs.backgroundColor || '');
5870
+ const hasBg = !!(bg && (bg.a ?? 1) > 0.5);
5871
+ const borderSides = ['Top', 'Right', 'Bottom', 'Left']
5872
+ .filter(side => (parseFloat(cs[`border${side}Width`]) || 0) > 0).length;
5873
+ if (!hasBg && borderSides < 2) continue;
5874
+ const leftGutter = rect.left - contentLeft;
5875
+ const rightGap = contentRight - rect.right;
5876
+ // Flush right with a left gutter, or the mirror. The -24 floor keeps
5877
+ // deliberately peeking next-cards (cut mid-card) exempt.
5878
+ const flushRight = leftGutter >= 6 && rightGap < 8 && rightGap > -24;
5879
+ const flushLeft = rightGap >= 6 && leftGutter < 8 && leftGutter > -24;
5880
+ if (!flushRight && !flushLeft) continue;
5881
+ flush.push({ card, edge: flushRight ? 'right' : 'left', gap: Math.round(flushRight ? rightGap : leftGutter) });
5882
+ }
5883
+ if (flush.length === 0) continue;
5884
+ const worst = flush.reduce((a, b) => (b.gap < a.gap ? b : a));
5885
+ findings.push({
5886
+ el: scroller,
5887
+ type: 'edge-flush-cards',
5888
+ detail: `${flush.length} card${flush.length === 1 ? '' : 's'} flush against the ${worst.edge} edge of ${classSelector(scroller)} at rest (${worst.gap}px gap, e.g. ${classSelector(worst.card)})`,
5889
+ });
5890
+ }
5891
+ return findings;
5892
+ }
5893
+
5894
+ // ---------------------------------------------------------------------------
5895
+ // Text occlusion / element overlap (browser-only)
5896
+ // ---------------------------------------------------------------------------
5897
+
5898
+ // An opaque decorated box: a near-solid background fill or two-plus visible
5899
+ // borders make it hide whatever sits behind it. Gradient / image fills are
5900
+ // deliberately excluded — a scrim gradient over hero imagery is a contrast
5901
+ // layer, not an occluder, and belongs to the pixel low-contrast rule.
5902
+ function isOpaqueDecoratedBox(cs) {
5903
+ if (!cs) return false;
5904
+ const bg = parseAnyColor(cs.backgroundColor || '');
5905
+ if (bg && (bg.a ?? 1) > 0.6) return true;
5906
+ const borderSides = ['Top', 'Right', 'Bottom', 'Left'].filter((side) => {
5907
+ if ((parseFloat(cs[`border${side}Width`]) || 0) <= 0) return false;
5908
+ const bc = parseAnyColor(cs[`border${side}Color`] || '');
5909
+ return bc && (bc.a ?? 1) > 0.3;
5910
+ }).length;
5911
+ return borderSides >= 2;
5912
+ }
5913
+
5914
+ // Is this element lifted out of normal flow into a layer that can cover
5915
+ // siblings? Two normal-flow blocks stacked vertically cannot truly hide each
5916
+ // other's ink — an overlap between their rects is line-box bleed from tight
5917
+ // leading (a display headline reaching up over the line before it), not
5918
+ // occlusion. Only out-of-flow positioning (absolute / fixed / sticky) moves an
5919
+ // element off its own row onto the pixels of another; an in-place transform or
5920
+ // relative nudge on a display headline does not.
5921
+ function isLayeredElement(el) {
5922
+ for (let cur = el; cur && cur.nodeType === 1 && cur !== document.body; cur = cur.parentElement) {
5923
+ const pos = String(getComputedStyle(cur).position || 'static');
5924
+ if (pos === 'absolute' || pos === 'fixed' || pos === 'sticky') return true;
5925
+ }
5926
+ return false;
5927
+ }
5928
+
5929
+ function elementDirectText(el) {
5930
+ let t = '';
5931
+ for (const node of el.childNodes || []) {
5932
+ if (node.nodeType === 3) t += node.textContent;
5933
+ }
5934
+ return t.trim();
5935
+ }
5936
+
5937
+ // Rendered gate that, unlike isRenderedForBrowserRule, does NOT exempt
5938
+ // aria-hidden subtrees: a decorative aria-hidden box still paints on screen
5939
+ // and can still visually cover real text.
5940
+ function isPaintedForOcclusion(el) {
5941
+ for (let cur = el; cur && cur.nodeType === 1; cur = cur.parentElement) {
5942
+ const style = getComputedStyle(cur);
5943
+ const visibility = String(style.visibility || '').toLowerCase();
5944
+ if (style.display === 'none' || visibility === 'hidden' || visibility === 'collapse') return false;
5945
+ if ((parseFloat(style.opacity) || 0) <= 0.05) return false;
5946
+ if (String(style.contentVisibility || '').toLowerCase() === 'hidden') return false;
5947
+ }
5948
+ return true;
5949
+ }
5950
+
5951
+ // Detects text that is actually painted UNDER an opaque box or another text
5952
+ // run (the reader can't read it), plus two structural overlap tells the
5953
+ // elementFromPoint probe can't reach: a large headline whose edge tucks behind
5954
+ // an opaque card, and an inline element whose leaked padding-box (a common
5955
+ // class-name-collision bug) covers a sibling.
5956
+ //
5957
+ // The occlusion probe is viewport-bound: elementFromPoint only answers for the
5958
+ // scan's current viewport (scroll 0), so the ground-truth paths cover the
5959
+ // first-viewport composition where collisions matter most. The inline-leak
5960
+ // path is pure geometry and runs anywhere on the page.
5961
+ const OCCLUSION_TEXT_SKIP_TAGS = new Set(['script', 'style', 'noscript', 'template', 'title']);
5962
+
5963
+ function checkTextOcclusionDOM() {
5964
+ const findings = [];
5965
+ const seenVictims = new Set();
5966
+ const vw = window.innerWidth || 1280;
5967
+ const vh = window.innerHeight || 800;
5968
+
5969
+ const isFloated = (cs) => {
5970
+ const f = String(cs.cssFloat || cs.float || 'none').toLowerCase();
5971
+ return f === 'left' || f === 'right';
5972
+ };
5973
+ const isMarqueeish = (el, cs) => {
5974
+ if (el.tagName === 'MARQUEE') return true;
5975
+ const ident = `${el.getAttribute?.('class') || ''} ${el.getAttribute?.('id') || ''}`;
5976
+ if (/\b(marquee|ticker|scroller|carousel|conveyor)\b/i.test(ident)) return true;
5977
+ const anim = String(cs.animationName || '').toLowerCase();
5978
+ return /marquee|ticker|scroll/.test(anim);
5979
+ };
5980
+ // A fixed or sticky overlay (status bar, toolbar, sticky header) floats above
5981
+ // scrolling content by design — whatever sits under it at rest scrolls clear,
5982
+ // so it is not occluding the page.
5983
+ const isPinnedOverlay = (el) => {
5984
+ for (let cur = el; cur && cur.nodeType === 1 && cur !== document.body; cur = cur.parentElement) {
5985
+ const pos = String(getComputedStyle(cur).position || 'static');
5986
+ if (pos === 'fixed' || pos === 'sticky') return true;
5987
+ }
5988
+ return false;
5989
+ };
5990
+
5991
+ // Collect renderable text owners in / near the first viewport for the
5992
+ // elementFromPoint probe. SVG <text> counts too.
5993
+ const textEls = [];
5994
+ for (const el of document.querySelectorAll('body *')) {
5995
+ const tag = el.tagName.toLowerCase();
5996
+ if (OCCLUSION_TEXT_SKIP_TAGS.has(tag)) continue;
5997
+ const inSvg = !!el.closest('svg');
5998
+ if (inSvg && tag !== 'text') continue;
5999
+ const text = inSvg ? (el.textContent || '').trim() : elementDirectText(el);
6000
+ if (text.length < 2) continue;
6001
+ if (!isPaintedForOcclusion(el)) continue;
6002
+ let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
6003
+ if (rect.width < 6 || rect.height < 6) continue;
6004
+ // Viewport-bound probe: keep text whose box overlaps the live viewport.
6005
+ if (rect.bottom <= 0 || rect.top >= vh) continue;
6006
+ textEls.push({ el, rect, text, inSvg });
6007
+ }
6008
+
6009
+ for (const victim of textEls) {
6010
+ const { el, rect, text } = victim;
6011
+ if (seenVictims.has(el)) continue;
6012
+ const style = getComputedStyle(el);
6013
+ if (isScreenReaderOnlyTextStyle(style, { width: rect.width, height: rect.height, clientWidth: el.clientWidth, clientHeight: el.clientHeight })) continue;
6014
+
6015
+ const cols = Math.max(6, Math.min(30, Math.round(rect.width / 12)));
6016
+ const rows = Math.max(1, Math.min(4, Math.round(rect.height / 14)));
6017
+ let total = 0;
6018
+ let occluded = 0;
6019
+ let occluderEl = null;
6020
+ let occluderKind = '';
6021
+ for (let i = 0; i < cols; i++) {
6022
+ const x = rect.left + rect.width * ((i + 0.5) / cols);
6023
+ if (x < 1 || x > vw - 1) continue;
6024
+ for (let j = 0; j < rows; j++) {
6025
+ const y = rect.top + rect.height * ((j + 0.5) / rows);
6026
+ if (y < 1 || y > vh - 1) continue;
6027
+ total++;
6028
+ const top = document.elementFromPoint(x, y);
6029
+ if (!top) continue;
6030
+ // Text visible here: the probe returns the text itself, a descendant,
6031
+ // or one of its ancestors (the text's own container / background).
6032
+ if (top === el || el.contains(top) || top.contains(el)) continue;
6033
+ const topCs = getComputedStyle(top);
6034
+ if (isFloated(topCs) || isMarqueeish(top, topCs) || isPinnedOverlay(top)) continue;
6035
+ const topTag = top.tagName.toLowerCase();
6036
+ // Text sitting under a raw image/video is contrast territory (deduped
6037
+ // against the pixel low-contrast rule); leave those alone here.
6038
+ if (['img', 'video', 'canvas', 'picture'].includes(topTag)) continue;
6039
+ const topHasText = elementDirectText(top).length > 0 || !!top.closest('svg');
6040
+ if (isOpaqueDecoratedBox(topCs)) {
6041
+ occluded++;
6042
+ if (!occluderEl) { occluderEl = top; occluderKind = 'box'; }
6043
+ } else if (topHasText) {
6044
+ occluded++;
6045
+ if (!occluderEl) { occluderEl = top; occluderKind = 'text'; }
6046
+ }
6047
+ }
6048
+ }
6049
+ if (total === 0 || !occluderEl) continue;
6050
+ const occFrac = occluded / total;
6051
+ // A solid box's paint fills its rect, so box coverage is real at a lower
6052
+ // bar. Text coverage rides on elementFromPoint returning the occluder's box
6053
+ // (line box / container), which can exceed its actual glyph ink, so the
6054
+ // text bar is higher — partial overlaps below it are crowding, not burial.
6055
+ if (occFrac < (occluderKind === 'text' ? 0.45 : 0.3)) continue;
6056
+
6057
+ // (i) Substantial occlusion: a real slab of the text is behind something.
6058
+ if (occluderKind === 'text') {
6059
+ // Two SVG texts inside the same emblem (concentric arcs, monogram) are one
6060
+ // decorative unit, not a collision.
6061
+ const victimSvg = el.closest('svg');
6062
+ const occSvg = occluderEl.closest('svg');
6063
+ if (victimSvg && occSvg && victimSvg === occSvg) continue;
6064
+ // Both sides in plain flow: the overlap is line-box bleed from tight
6065
+ // leading (a big headline reaching up over its own eyebrow), not one text
6066
+ // run painted over another.
6067
+ if (!isLayeredElement(el) && !isLayeredElement(occluderEl)) continue;
6068
+ }
6069
+ seenVictims.add(el);
6070
+ findings.push({
6071
+ el,
6072
+ type: 'text-occlusion',
6073
+ detail: `${classSelector(el)} "${text.slice(0, 24)}" is ${Math.round(occFrac * 100)}% covered by ${occluderKind === 'text' ? 'overlapping text' : 'an opaque element'} (${classSelector(occluderEl)})`,
6074
+ });
6075
+ }
6076
+
6077
+ // (ii) Headline overhanging an opaque card: a display-scale line whose bulk
6078
+ // sits outside a bounded content card but whose edge clips into it. The text
6079
+ // may still paint on top and stay readable, but the two layers were dropped
6080
+ // on the same pixels — a placement collision, not a composition.
6081
+ const cards = [];
6082
+ for (const el of document.querySelectorAll('body *')) {
6083
+ if (el.closest('svg')) continue;
6084
+ if (!isPaintedForOcclusion(el)) continue;
6085
+ const cs = getComputedStyle(el);
6086
+ const bg = parseAnyColor(cs.backgroundColor || '');
6087
+ const bgImg = cs.backgroundImage || '';
6088
+ if (!bg || (bg.a ?? 1) <= 0.7) continue;
6089
+ if (bgImg && bgImg !== 'none' && /(gradient|url)\(/i.test(bgImg)) continue;
6090
+ const hasBorder = ['Top', 'Right', 'Bottom', 'Left'].some((s) => (parseFloat(cs[`border${s}Width`]) || 0) > 0);
6091
+ const hasShadow = cs.boxShadow && cs.boxShadow !== 'none';
6092
+ if (!hasBorder && !hasShadow) continue;
6093
+ if (isPinnedOverlay(el)) continue;
6094
+ let cr; try { cr = el.getBoundingClientRect(); } catch { continue; }
6095
+ if (cr.width < 100 || cr.width > 0.8 * vw || cr.height < 60) continue;
6096
+ cards.push({ el, rect: cr });
6097
+ }
6098
+ for (const victim of textEls) {
6099
+ const { el, rect, text } = victim;
6100
+ if (seenVictims.has(el)) continue;
6101
+ const style = getComputedStyle(el);
6102
+ if ((parseFloat(style.fontSize) || 16) < 40) continue;
6103
+ let lineHeight = parseFloat(style.lineHeight);
6104
+ if (!Number.isFinite(lineHeight)) lineHeight = (parseFloat(style.fontSize) || 16) * 1.2;
6105
+ const centerX = rect.left + rect.width / 2;
6106
+ for (const card of cards) {
6107
+ if (card.el === el || el.contains(card.el) || card.el.contains(el)) continue;
6108
+ const ix = Math.max(0, Math.min(rect.right, card.rect.right) - Math.max(rect.left, card.rect.left));
6109
+ const iy = Math.max(0, Math.min(rect.bottom, card.rect.bottom) - Math.max(rect.top, card.rect.top));
6110
+ if (ix < 8 || iy < 0.5 * lineHeight) continue;
6111
+ // The headline's bulk must sit outside the card — only its edge clips in.
6112
+ if (centerX >= card.rect.left && centerX <= card.rect.right) continue;
6113
+ if (ix > 0.5 * rect.width) continue;
6114
+ seenVictims.add(el);
6115
+ findings.push({
6116
+ el,
6117
+ type: 'text-occlusion',
6118
+ detail: `${classSelector(el)} "${text.slice(0, 24)}" overhangs ${classSelector(card.el)} by ${Math.round(ix)}px — the headline and the card collide`,
6119
+ });
6120
+ break;
6121
+ }
6122
+ }
6123
+
6124
+ // (iii) Inline padding leak: an inline element with an opaque background and
6125
+ // large vertical padding paints a filled block whose padding-box overflows
6126
+ // its line (inline padding reserves no vertical space), so the fill lands on
6127
+ // the content above and below instead of enclosing its own text. The
6128
+ // canonical bug is a class-name collision that hands a decorative marker a
6129
+ // payoff card's padding. The tell is a rendered height several times the line
6130
+ // height, which distinguishes the leak from a padded inline highlight.
6131
+ for (const el of document.querySelectorAll('body *')) {
6132
+ if (el.closest('svg')) continue;
6133
+ if (!isPaintedForOcclusion(el)) continue;
6134
+ const cs = getComputedStyle(el);
6135
+ if (cs.display !== 'inline') continue;
6136
+ const bg = parseAnyColor(cs.backgroundColor || '');
6137
+ if (!bg || (bg.a ?? 1) <= 0.6) continue;
6138
+ const padTop = parseFloat(cs.paddingTop) || 0;
6139
+ const padBottom = parseFloat(cs.paddingBottom) || 0;
6140
+ if (padTop + padBottom < 24) continue;
6141
+ let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
6142
+ if (rect.width < 12 || rect.height < 24) continue;
6143
+ const fontSize = parseFloat(cs.fontSize) || 16;
6144
+ let lineHeight = parseFloat(cs.lineHeight);
6145
+ if (!Number.isFinite(lineHeight)) lineHeight = fontSize * 1.4;
6146
+ // The padding box has to overflow the line by a clear margin — a padded
6147
+ // inline highlight sits at roughly one line height, the leak at several.
6148
+ if (rect.height < 2.2 * lineHeight) continue;
6149
+ if (seenVictims.has(el)) continue;
6150
+ // Name a neighbour the fill lands on, if one is nearby (paint state aside,
6151
+ // reveal-on-scroll siblings still occupy the space it covers).
6152
+ let overlaps = null;
6153
+ for (const other of el.parentElement ? el.parentElement.children : []) {
6154
+ if (other === el || el.contains(other) || other.contains(el)) continue;
6155
+ if (getComputedStyle(other).display === 'none') continue;
6156
+ const oRect = other.getBoundingClientRect();
6157
+ const ix = Math.max(0, Math.min(rect.right, oRect.right) - Math.max(rect.left, oRect.left));
6158
+ const iy = Math.max(0, Math.min(rect.bottom, oRect.bottom) - Math.max(rect.top, oRect.top));
6159
+ if (ix > 4 && iy > 4 && (other.textContent || '').trim().length > 0) { overlaps = other; break; }
6160
+ }
6161
+ seenVictims.add(el);
6162
+ findings.push({
6163
+ el,
6164
+ type: 'text-occlusion',
6165
+ detail: `${classSelector(el)} is an inline element whose opaque fill leaks ${Math.round(rect.height)}px past its line${overlaps ? ` onto ${classSelector(overlaps)}` : ''}`,
6166
+ });
6167
+ }
6168
+
6169
+ return findings;
6170
+ }
6171
+
6172
+ // ---------------------------------------------------------------------------
6173
+ // First-viewport column overflow — the stretched-hero signature (browser-only)
6174
+ // ---------------------------------------------------------------------------
6175
+
6176
+ // A multi-column composition that opens the page (grid/flex with two or more
6177
+ // side-by-side columns, each a real share of the width) where one column's
6178
+ // content runs far past the fold while its sibling fits inside a single
6179
+ // viewport. The row stretches to the tall column, so the short one floats in a
6180
+ // screen-and-a-half of dead space and the fold falls deep inside a single
6181
+ // section. Single-column pages and full-page heroes (no sibling column) are
6182
+ // exempt because there is no fitting sibling to contrast against.
6183
+ function checkFirstViewportColumnOverflowDOM() {
6184
+ const findings = [];
6185
+ const vw = window.innerWidth || 1280;
6186
+ const vh = window.innerHeight || 800;
6187
+ const isMultiCol = (s) => /(^|inline-)(grid|flex)$/.test(String(s.display || ''));
6188
+
6189
+ for (const el of document.querySelectorAll('body *')) {
6190
+ const style = getComputedStyle(el);
6191
+ if (!isMultiCol(style)) continue;
6192
+ let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
6193
+ if (rect.width < 0.5 * vw) continue;
6194
+ const pageTop = rect.top + (window.scrollY || 0);
6195
+ const pageBottom = pageTop + rect.height;
6196
+ // The fold must fall inside this container: it opens within the first
6197
+ // viewport and runs past it.
6198
+ if (pageTop >= vh * 0.9 || pageBottom <= vh) continue;
6199
+
6200
+ // Direct children that read as side-by-side columns: a real width share,
6201
+ // not full-bleed (stacked single column), sharing the container's top row.
6202
+ const cols = [];
6203
+ for (const child of el.children) {
6204
+ const cs = getComputedStyle(child);
6205
+ if (cs.display === 'none') continue;
6206
+ if (String(cs.position || '') === 'absolute' || String(cs.position || '') === 'fixed') continue;
6207
+ let cr; try { cr = child.getBoundingClientRect(); } catch { continue; }
6208
+ const wShare = cr.width / rect.width;
6209
+ if (wShare < 0.25 || wShare > 0.9) continue;
6210
+ if (cr.height < 40) continue;
6211
+ // Content extent: how far the child's own content actually reaches,
6212
+ // independent of a stretched row height.
6213
+ let contentBottom = cr.top;
6214
+ for (const d of child.querySelectorAll('*')) {
6215
+ const ds = getComputedStyle(d);
6216
+ if (ds.position === 'absolute' || ds.position === 'fixed') continue;
6217
+ if (ds.display === 'none' || ds.visibility === 'hidden') continue;
6218
+ let dr; try { dr = d.getBoundingClientRect(); } catch { continue; }
6219
+ if (dr.width > 0 && dr.height > 0) contentBottom = Math.max(contentBottom, dr.bottom);
6220
+ }
6221
+ cols.push({ child, top: cr.top, contentH: contentBottom - cr.top });
6222
+ }
6223
+ if (cols.length < 2) continue;
6224
+ // Side-by-side: the two candidate columns must share the top row.
6225
+ cols.sort((a, b) => b.contentH - a.contentH);
6226
+ const tall = cols[0];
6227
+ const shortest = cols[cols.length - 1];
6228
+ if (Math.abs(tall.top - shortest.top) > 0.25 * vh) continue;
6229
+ if (tall.contentH <= vh * 1.4) continue;
6230
+ if (shortest.contentH > vh) continue;
6231
+
6232
+ findings.push({
6233
+ el,
6234
+ type: 'first-viewport-column-overflow',
6235
+ detail: `${classSelector(el)} opens the page with one column running ${Math.round(tall.contentH / vh * 100)}% of the viewport tall while a sibling fits in ${Math.round(shortest.contentH / vh * 100)}% — the fold falls deep inside the section`,
6236
+ });
6237
+ }
6238
+ return findings;
6239
+ }
6240
+
3322
6241
  // --- cli/engine/browser/injected/index.mjs ---
3323
6242
  const IS_BROWSER = typeof window !== 'undefined';
3324
6243
 
@@ -3852,7 +6771,11 @@ if (IS_BROWSER) {
3852
6771
  function generateSelector(el) {
3853
6772
  if (el === document.body) return 'body';
3854
6773
  if (el === document.documentElement) return 'html';
3855
- if (el.id) return '#' + CSS.escape(el.id);
6774
+ // Read via getAttribute when `el.id` is not a string — a <form> with a
6775
+ // named control (e.g. <input name="id">) shadows the builtin getter and
6776
+ // returns the element, producing a garbage `#[object …]` selector (#407).
6777
+ const elId = typeof el.id === 'string' ? el.id : (el.getAttribute('id') || '');
6778
+ if (elId) return '#' + CSS.escape(elId);
3856
6779
 
3857
6780
  const parts = [];
3858
6781
  let current = el;
@@ -4544,7 +7467,11 @@ if (IS_BROWSER) {
4544
7467
  return {
4545
7468
  type: f.type || f.id,
4546
7469
  category: ap ? ap.category : 'quality',
4547
- severity: ap?.severity || 'warning',
7470
+ severity: f.severity || ap?.severity || 'warning',
7471
+ // Advisory findings (em-dash overuse, etc.) are surfaced but never
7472
+ // treated as failures; carry the flag so the overlay/extension can
7473
+ // render them with the mildest affordance and consumers can filter.
7474
+ advisory: (ap && ap.advisory === true) || f.advisory === true,
4548
7475
  detail: f.detail || f.snippet,
4549
7476
  ignoreValue: f.ignoreValue || f.value || '',
4550
7477
  name: ap ? ap.name : (f.type || f.id),
@@ -4780,16 +7707,16 @@ if (IS_BROWSER) {
4780
7707
  const _ruleOk = (id) => !_disabled.length || !_disabled.includes(id);
4781
7708
  const designSystem = browserDesignSystemConfig();
4782
7709
  const designSeen = { fonts: new Set(), colors: new Set(), radii: new Set() };
4783
- // Note: provider-gated rules (--gpt / --gemini) are NOT filtered here. In a
4784
- // real browser env (detector page, live overlay, extension) running every
4785
- // check is free, so we always surface them; the gating is purely a CLI
4786
- // output concern, applied in the Node engines' detect* return paths.
7710
+ // All deterministic rules run in the browser and extension path.
4787
7711
 
4788
7712
  for (const el of document.querySelectorAll('*')) {
4789
7713
  // Skip impeccable's own elements and any descendants (overlays, labels, banner, nav buttons)
4790
7714
  if (el.closest('.impeccable-overlay, .impeccable-label, .impeccable-banner, .impeccable-tooltip')) continue;
4791
- // Skip browser extension elements (Claude, etc.)
4792
- const elId = el.id || '';
7715
+ // Skip browser extension elements (Claude, etc.). Use getAttribute when
7716
+ // `el.id` is not a string: a <form> with a named control like
7717
+ // <input name="id"> shadows the builtin `id` getter and returns the
7718
+ // element, whose `.startsWith` throws (issue #407).
7719
+ const elId = typeof el.id === 'string' ? el.id : (el.getAttribute('id') || '');
4793
7720
  if (elId.startsWith('claude-') || elId.startsWith('cic-')) continue;
4794
7721
  // Skip the impeccable live-mode overlay (highlight, tooltip, bar, picker, toast).
4795
7722
  // These are inspector chrome, not part of the user's design.
@@ -4799,10 +7726,12 @@ if (IS_BROWSER) {
4799
7726
 
4800
7727
  const findings = [
4801
7728
  ...checkElementBordersDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
7729
+ ...checkElementPseudoStripeDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4802
7730
  ...checkElementColorsDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4803
7731
  ...checkElementMotionDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4804
7732
  ...checkElementGlowDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4805
7733
  ...checkElementAIPaletteDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
7734
+ ...checkElementRadialSpotlightDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4806
7735
  ...checkElementIconTileDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4807
7736
  ...checkElementItalicSerifDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4808
7737
  ...checkElementQualityDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
@@ -4810,6 +7739,7 @@ if (IS_BROWSER) {
4810
7739
  ...checkElementClippedOverflowDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4811
7740
  ...checkElementGptBorderShadowDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
4812
7741
  ...checkElementTextOverflowDOM(el).map(f => ({ type: f.id, detail: f.snippet })),
7742
+ ...checkElementBlinkingCursorDOM(el).map(f => ({ type: f.id, detail: f.snippet, ...(f.severity ? { severity: f.severity } : {}) })),
4813
7743
  ...checkElementDesignSystemDOM(el, designSystem, designSeen),
4814
7744
  ].filter(f => _ruleOk(f.type));
4815
7745
 
@@ -4840,7 +7770,7 @@ if (IS_BROWSER) {
4840
7770
  addBrowserFindings(groupMap, document.body, typoFindings);
4841
7771
  }
4842
7772
 
4843
- const sectionKickerFindings = checkRepeatedSectionKickersDOM()
7773
+ const sectionKickerFindings = checkKickerAboveHeadingDOM()
4844
7774
  .map(f => ({ type: f.id, detail: f.snippet }))
4845
7775
  .filter(f => _ruleOk(f.type));
4846
7776
  if (sectionKickerFindings.length > 0) {
@@ -4848,12 +7778,66 @@ if (IS_BROWSER) {
4848
7778
  addBrowserFindings(groupMap, document.body, sectionKickerFindings);
4849
7779
  }
4850
7780
 
7781
+ const numberedLabelFindings = checkNumberedSectionLabelsDOM()
7782
+ .map(f => ({ type: f.id, detail: f.snippet }))
7783
+ .filter(f => _ruleOk(f.type));
7784
+ if (numberedLabelFindings.length > 0) {
7785
+ pageLevelFindings.push(...numberedLabelFindings);
7786
+ addBrowserFindings(groupMap, document.body, numberedLabelFindings);
7787
+ }
7788
+
7789
+ const repeatedTextFindings = checkRepeatedContainerTextDOM()
7790
+ .map(f => ({ type: f.id, detail: f.snippet }))
7791
+ .filter(f => _ruleOk(f.type));
7792
+ if (repeatedTextFindings.length > 0) {
7793
+ pageLevelFindings.push(...repeatedTextFindings);
7794
+ addBrowserFindings(groupMap, document.body, repeatedTextFindings);
7795
+ }
7796
+
7797
+ // Em-dash overuse (advisory): browser parity with the static/regex path.
7798
+ // Reads rendered body text so it catches dashes written as HTML entities.
7799
+ // serializeFindings stamps the advisory flag from the registry.
7800
+ const emDashFindings = checkEmDashOveruseDOM()
7801
+ .map(f => ({ type: f.id, detail: f.snippet }))
7802
+ .filter(f => _ruleOk(f.type));
7803
+ if (emDashFindings.length > 0) {
7804
+ pageLevelFindings.push(...emDashFindings);
7805
+ addBrowserFindings(groupMap, document.body, emDashFindings);
7806
+ }
7807
+
4851
7808
  const layoutFindings = checkLayout().filter(f => _ruleOk(f.type));
4852
7809
  for (const f of layoutFindings) {
4853
7810
  const el = f.el || document.body;
4854
7811
  addBrowserFindings(groupMap, el, [{ type: f.type, detail: f.detail || f.snippet }]);
4855
7812
  }
4856
7813
 
7814
+ // Heading rhythm (browser-only: needs real layout for the gap math)
7815
+ const headingRhythmFindings = checkHeadingRhythmDOM().filter(f => _ruleOk(f.type));
7816
+ for (const f of headingRhythmFindings) {
7817
+ addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
7818
+ }
7819
+
7820
+ // Edge-flush cards in horizontal scrollers (browser-only: needs real
7821
+ // layout for the scroller clip box vs card rect math)
7822
+ const edgeFlushFindings = checkEdgeFlushCardsDOM().filter(f => _ruleOk(f.type));
7823
+ for (const f of edgeFlushFindings) {
7824
+ addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
7825
+ }
7826
+
7827
+ // Text occlusion / element overlap (browser-only: needs real layout +
7828
+ // elementFromPoint to confirm what actually paints on top)
7829
+ const occlusionFindings = checkTextOcclusionDOM().filter(f => _ruleOk(f.type));
7830
+ for (const f of occlusionFindings) {
7831
+ addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
7832
+ }
7833
+
7834
+ // First-viewport column overflow — the stretched-hero signature
7835
+ // (browser-only: needs real layout for the content-extent math)
7836
+ const colOverflowFindings = checkFirstViewportColumnOverflowDOM().filter(f => _ruleOk(f.type));
7837
+ for (const f of colOverflowFindings) {
7838
+ addBrowserFindings(groupMap, f.el || document.body, [{ type: f.type, detail: f.detail }]);
7839
+ }
7840
+
4857
7841
  // Page-level quality checks (headings, etc.)
4858
7842
  const qualityFindings = checkPageQualityDOM().filter(f => _ruleOk(f.type));
4859
7843
  if (qualityFindings.length > 0) {
@@ -4879,7 +7863,25 @@ if (IS_BROWSER) {
4879
7863
  }
4880
7864
  const htmlPatternFindings = checkHtmlPatterns(docClone.outerHTML);
4881
7865
  if (htmlPatternFindings.length > 0) {
4882
- const mapped = htmlPatternFindings.map(f => ({ type: f.id, detail: f.snippet })).filter(f => _ruleOk(f.type));
7866
+ const mapped = htmlPatternFindings.map(f => {
7867
+ const item = { type: f.id, detail: f.snippet };
7868
+ if (f.severity) {
7869
+ item.severity = f.severity;
7870
+ } else if (f.id === 'pulsing-dot' && f.selector) {
7871
+ // The string scan promotes header/nav dots on its own; with a live
7872
+ // layout also promote dots resting in the first ~900px of the page
7873
+ // (the hero region), which the source scan cannot measure.
7874
+ try {
7875
+ const dotEl = document.querySelector(f.selector);
7876
+ if (dotEl) {
7877
+ const rect = dotEl.getBoundingClientRect();
7878
+ const pageTop = rect.top + (window.scrollY || 0);
7879
+ if (pageTop <= 900) item.severity = 'error';
7880
+ }
7881
+ } catch { /* unresolvable selector: keep registry severity */ }
7882
+ }
7883
+ return item;
7884
+ }).filter(f => _ruleOk(f.type));
4883
7885
  pageLevelFindings.push(...mapped);
4884
7886
  addBrowserFindings(groupMap, document.body, mapped);
4885
7887
  }
@@ -5253,6 +8255,9 @@ if (IS_BROWSER) {
5253
8255
  window.impeccableDetectAsync = detectAsync;
5254
8256
  window.impeccableScan = scan;
5255
8257
  window.impeccableScanAsync = scanAsync;
8258
+ // Raw measurement for the URL engine's content-hidden-at-rest pass: it
8259
+ // drives a reveal sweep from Node and thresholds the result itself.
8260
+ window.impeccableMeasureHiddenText = measureHiddenTextDOM;
5256
8261
  window.impeccableCollectVisualContrastCandidates = collectVisualContrastCandidates;
5257
8262
  window.impeccableAnalyzeVisualContrast = analyzeVisualContrast;
5258
8263
  window.impeccableGetLastVisualContrastAnalyses = () => lastVisualContrastAnalyses.slice();