@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.
- package/CHANGELOG.md +255 -0
- package/README.md +15 -10
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +4 -4
- package/dist/builtin/mcp/CHANGELOG.md +18 -0
- package/dist/builtin/mcp/README.md +3 -3
- package/dist/builtin/mcp/cli.js +0 -1
- package/dist/builtin/mcp/commands.ts +7 -3
- package/dist/builtin/mcp/config.ts +0 -2
- package/dist/builtin/mcp/package.json +6 -6
- package/dist/builtin/mcp/sampling-handler.ts +1 -5
- package/dist/builtin/mcp/types.ts +0 -1
- package/dist/builtin/subagents/CHANGELOG.md +40 -0
- package/dist/builtin/subagents/README.md +10 -1
- package/dist/builtin/subagents/agents/code-simplifier.md +48 -103
- package/dist/builtin/subagents/agents/codebase-analyzer.md +28 -131
- package/dist/builtin/subagents/agents/codebase-locator.md +25 -84
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +61 -260
- package/dist/builtin/subagents/agents/codebase-pattern-finder.md +30 -208
- package/dist/builtin/subagents/agents/codebase-research-analyzer.md +29 -151
- package/dist/builtin/subagents/agents/codebase-research-locator.md +22 -119
- package/dist/builtin/subagents/agents/debugger.md +29 -65
- package/dist/builtin/subagents/agents/worker.md +24 -31
- package/dist/builtin/subagents/package.json +5 -5
- package/dist/builtin/subagents/prompts/gather-context-and-clarify.md +18 -11
- package/dist/builtin/subagents/prompts/parallel-cleanup.md +34 -33
- package/dist/builtin/subagents/prompts/parallel-context-build.md +22 -36
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +20 -52
- package/dist/builtin/subagents/prompts/parallel-research.md +17 -41
- package/dist/builtin/subagents/prompts/parallel-review.md +23 -28
- package/dist/builtin/subagents/prompts/review-loop.md +18 -27
- package/dist/builtin/subagents/skills/subagent/SKILL.md +6 -6
- package/dist/builtin/subagents/src/agents/agent-discovery.ts +26 -15
- package/dist/builtin/subagents/src/agents/agent-loaders.ts +48 -34
- package/dist/builtin/subagents/src/agents/agent-management-helpers.ts +80 -31
- package/dist/builtin/subagents/src/agents/agent-management.ts +194 -55
- package/dist/builtin/subagents/src/agents/agent-overrides.ts +85 -32
- package/dist/builtin/subagents/src/agents/agent-paths.ts +4 -1
- package/dist/builtin/subagents/src/agents/agent-selection.ts +1 -1
- package/dist/builtin/subagents/src/agents/agent-serializer.ts +1 -4
- package/dist/builtin/subagents/src/agents/agents.ts +9 -9
- package/dist/builtin/subagents/src/agents/chain-serializer.ts +9 -5
- package/dist/builtin/subagents/src/agents/identity.ts +12 -3
- package/dist/builtin/subagents/src/agents/skills-paths.ts +64 -24
- package/dist/builtin/subagents/src/agents/skills.ts +29 -26
- package/dist/builtin/subagents/src/extension/doctor.ts +31 -20
- package/dist/builtin/subagents/src/extension/fanout-child.ts +27 -12
- package/dist/builtin/subagents/src/extension/index.ts +119 -43
- package/dist/builtin/subagents/src/extension/prompt-guidance.ts +4 -2
- package/dist/builtin/subagents/src/extension/schemas.ts +304 -177
- package/dist/builtin/subagents/src/extension/startup-maintenance.ts +137 -73
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +49 -21
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +76 -38
- package/dist/builtin/subagents/src/runs/background/async-event-journal.ts +12 -6
- package/dist/builtin/subagents/src/runs/background/async-execution-chain.ts +183 -57
- package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +15 -18
- package/dist/builtin/subagents/src/runs/background/async-execution-single.ts +67 -26
- package/dist/builtin/subagents/src/runs/background/async-execution-types.ts +8 -8
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +1 -1
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +86 -32
- package/dist/builtin/subagents/src/runs/background/async-resume.ts +93 -36
- package/dist/builtin/subagents/src/runs/background/async-status.ts +82 -27
- package/dist/builtin/subagents/src/runs/background/completion-claims.ts +12 -5
- package/dist/builtin/subagents/src/runs/background/completion-dedupe.ts +3 -1
- package/dist/builtin/subagents/src/runs/background/completion-notification.ts +3 -1
- package/dist/builtin/subagents/src/runs/background/notify.ts +38 -18
- package/dist/builtin/subagents/src/runs/background/parallel-groups.ts +28 -14
- package/dist/builtin/subagents/src/runs/background/result-delivery-processor.ts +147 -79
- package/dist/builtin/subagents/src/runs/background/result-file-claims.ts +20 -5
- package/dist/builtin/subagents/src/runs/background/result-quarantine.ts +7 -2
- package/dist/builtin/subagents/src/runs/background/result-status.ts +11 -5
- package/dist/builtin/subagents/src/runs/background/result-watcher-data.ts +18 -5
- package/dist/builtin/subagents/src/runs/background/result-watcher.ts +104 -35
- package/dist/builtin/subagents/src/runs/background/run-id-resolver.ts +28 -7
- package/dist/builtin/subagents/src/runs/background/run-status.ts +120 -32
- package/dist/builtin/subagents/src/runs/background/stale-run-reconciler.ts +98 -39
- package/dist/builtin/subagents/src/runs/background/subagent-runner-dynamic.ts +185 -37
- package/dist/builtin/subagents/src/runs/background/subagent-runner-finalize.ts +23 -9
- package/dist/builtin/subagents/src/runs/background/subagent-runner-output.ts +6 -2
- package/dist/builtin/subagents/src/runs/background/subagent-runner-parallel-helpers.ts +34 -18
- package/dist/builtin/subagents/src/runs/background/subagent-runner-parallel.ts +175 -26
- package/dist/builtin/subagents/src/runs/background/subagent-runner-sequential.ts +68 -23
- package/dist/builtin/subagents/src/runs/background/subagent-runner-state.ts +200 -49
- package/dist/builtin/subagents/src/runs/background/subagent-runner-step.ts +82 -49
- package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +33 -11
- package/dist/builtin/subagents/src/runs/background/subagent-runner-types.ts +16 -6
- package/dist/builtin/subagents/src/runs/background/subagent-runner-utils.ts +2 -3
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +11 -8
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-details.ts +5 -2
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-dynamic-step.ts +115 -31
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-runner.ts +71 -54
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-parallel-step.ts +104 -37
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-sequential-step.ts +102 -61
- package/dist/builtin/subagents/src/runs/foreground/chain-execution-types.ts +24 -10
- package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +9 -6
- package/dist/builtin/subagents/src/runs/foreground/detached-cleanup-barrier.ts +22 -22
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt-control.ts +45 -40
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt-finalize.ts +6 -5
- package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +118 -43
- package/dist/builtin/subagents/src/runs/foreground/execution-detach-reservations.ts +17 -7
- package/dist/builtin/subagents/src/runs/foreground/execution-detach-route.ts +5 -3
- package/dist/builtin/subagents/src/runs/foreground/execution-intercom-detach.ts +39 -35
- package/dist/builtin/subagents/src/runs/foreground/execution-run-sync.ts +81 -42
- package/dist/builtin/subagents/src/runs/foreground/execution-structured-retries.ts +27 -21
- package/dist/builtin/subagents/src/runs/foreground/execution-updates.ts +2 -5
- package/dist/builtin/subagents/src/runs/foreground/execution-utils.ts +9 -4
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +69 -25
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-chain.ts +36 -19
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +95 -62
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +30 -9
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +106 -96
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +68 -36
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-resume.ts +262 -83
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +6 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +67 -41
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +129 -44
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +20 -3
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-worktree.ts +8 -6
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +65 -47
- package/dist/builtin/subagents/src/runs/shared/chain-outputs.ts +37 -12
- package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +120 -34
- package/dist/builtin/subagents/src/runs/shared/final-drain.ts +12 -7
- package/dist/builtin/subagents/src/runs/shared/intercom-group.ts +3 -2
- package/dist/builtin/subagents/src/runs/shared/long-running-guard.ts +8 -3
- package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +42 -20
- package/dist/builtin/subagents/src/runs/shared/model-candidate-filter.ts +8 -4
- package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +103 -38
- package/dist/builtin/subagents/src/runs/shared/nested-events-control.ts +30 -20
- package/dist/builtin/subagents/src/runs/shared/nested-events-core.ts +45 -20
- package/dist/builtin/subagents/src/runs/shared/nested-events-projection.ts +57 -23
- package/dist/builtin/subagents/src/runs/shared/nested-events-registry.ts +77 -29
- package/dist/builtin/subagents/src/runs/shared/nested-events-sanitize.ts +99 -42
- package/dist/builtin/subagents/src/runs/shared/nested-events.ts +32 -26
- package/dist/builtin/subagents/src/runs/shared/nested-path.ts +22 -17
- package/dist/builtin/subagents/src/runs/shared/nested-render.ts +31 -10
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +10 -8
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +59 -33
- package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +17 -13
- package/dist/builtin/subagents/src/runs/shared/run-history.ts +13 -6
- package/dist/builtin/subagents/src/runs/shared/single-output.ts +11 -6
- package/dist/builtin/subagents/src/runs/shared/spawn-env.ts +21 -0
- package/dist/builtin/subagents/src/runs/shared/structured-output.ts +33 -19
- package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +59 -48
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +18 -11
- package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +41 -11
- package/dist/builtin/subagents/src/runs/shared/worktree-post-create.ts +46 -10
- package/dist/builtin/subagents/src/runs/shared/worktree-root.ts +21 -5
- package/dist/builtin/subagents/src/runs/shared/worktree.ts +89 -34
- package/dist/builtin/subagents/src/shared/artifacts.ts +137 -7
- package/dist/builtin/subagents/src/shared/event-jsonl-writer.ts +49 -13
- package/dist/builtin/subagents/src/shared/fast-mode.ts +13 -6
- package/dist/builtin/subagents/src/shared/fork-context.ts +7 -4
- package/dist/builtin/subagents/src/shared/formatters.ts +14 -9
- package/dist/builtin/subagents/src/shared/jsonl-writer.ts +2 -1
- package/dist/builtin/subagents/src/shared/model-info.ts +10 -3
- package/dist/builtin/subagents/src/shared/post-exit-stdio-guard.ts +7 -6
- package/dist/builtin/subagents/src/shared/session-tokens.ts +2 -1
- package/dist/builtin/subagents/src/shared/settings.ts +43 -43
- package/dist/builtin/subagents/src/shared/status-format.ts +10 -2
- package/dist/builtin/subagents/src/shared/types-async.ts +22 -19
- package/dist/builtin/subagents/src/shared/types-config.ts +1 -1
- package/dist/builtin/subagents/src/shared/types-depth.ts +23 -11
- package/dist/builtin/subagents/src/shared/types-results.ts +49 -6
- package/dist/builtin/subagents/src/shared/types-runtime.ts +16 -17
- package/dist/builtin/subagents/src/shared/types.ts +2 -2
- package/dist/builtin/subagents/src/shared/utils.ts +34 -20
- package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +44 -47
- package/dist/builtin/subagents/src/slash/saved-chain-mapping.ts +5 -7
- package/dist/builtin/subagents/src/slash/slash-bridge.ts +3 -3
- package/dist/builtin/subagents/src/slash/slash-commands.ts +78 -52
- package/dist/builtin/subagents/src/slash/slash-live-state.ts +59 -32
- package/dist/builtin/subagents/src/tui/render-chain-graph.ts +120 -32
- package/dist/builtin/subagents/src/tui/render-event-formatting.ts +85 -22
- package/dist/builtin/subagents/src/tui/render-helpers.ts +16 -6
- package/dist/builtin/subagents/src/tui/render-layout.ts +8 -3
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +125 -43
- package/dist/builtin/subagents/src/tui/render-result.ts +128 -85
- package/dist/builtin/subagents/src/tui/render-stable-output.ts +17 -13
- package/dist/builtin/subagents/src/tui/render-status-progress.ts +52 -20
- package/dist/builtin/subagents/src/tui/render-widget-graph.ts +130 -28
- package/dist/builtin/subagents/src/tui/render-widget.ts +54 -27
- package/dist/builtin/subagents/src/tui/render.ts +10 -3
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/workflows/CHANGELOG.md +127 -0
- package/dist/builtin/workflows/README.md +83 -70
- package/dist/builtin/workflows/ambient.d.ts +0 -5
- package/dist/builtin/workflows/builtin/adversarial-verification-prompts.ts +7 -4
- package/dist/builtin/workflows/builtin/adversarial-verification-runner.ts +1 -1
- package/dist/builtin/workflows/builtin/adversarial-verification.ts +2 -1
- package/dist/builtin/workflows/builtin/classify-and-act-prompts.ts +5 -2
- package/dist/builtin/workflows/builtin/classify-and-act-runner.ts +5 -7
- package/dist/builtin/workflows/builtin/classify-and-act.ts +3 -2
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize-prompts.ts +6 -3
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize-runner.ts +5 -5
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize.ts +3 -2
- package/dist/builtin/workflows/builtin/generate-and-filter-prompts.ts +7 -4
- package/dist/builtin/workflows/builtin/generate-and-filter-runner.ts +6 -3
- package/dist/builtin/workflows/builtin/generate-and-filter.ts +3 -2
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +1 -1
- package/dist/builtin/workflows/builtin/goal-models.ts +39 -39
- package/dist/builtin/workflows/builtin/goal-orchestrator-prompts.ts +94 -0
- package/dist/builtin/workflows/builtin/goal-prompts.ts +70 -285
- package/dist/builtin/workflows/builtin/goal-reducer.ts +1 -1
- package/dist/builtin/workflows/builtin/goal-runner.ts +72 -116
- package/dist/builtin/workflows/builtin/goal.ts +12 -11
- package/dist/builtin/workflows/builtin/index.d.ts +17 -112
- package/dist/builtin/workflows/builtin/index.ts +1 -2
- package/dist/builtin/workflows/builtin/loop-until-done-prompts.ts +20 -12
- package/dist/builtin/workflows/builtin/loop-until-done-runner.ts +3 -0
- package/dist/builtin/workflows/builtin/loop-until-done.ts +3 -2
- package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +149 -88
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +102 -78
- package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +164 -56
- package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +16 -21
- package/dist/builtin/workflows/builtin/open-claude-design.ts +2 -1
- package/dist/builtin/workflows/builtin/ralph-core.ts +61 -57
- package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +41 -45
- package/dist/builtin/workflows/builtin/ralph-models.ts +37 -47
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +37 -117
- package/dist/builtin/workflows/builtin/ralph-runner.ts +83 -108
- package/dist/builtin/workflows/builtin/ralph.ts +6 -5
- package/dist/builtin/workflows/builtin/shared-prompts.ts +117 -70
- package/dist/builtin/workflows/builtin/steering-context.ts +51 -0
- package/dist/builtin/workflows/builtin/tournament-prompts.ts +21 -12
- package/dist/builtin/workflows/builtin/tournament-runner.ts +3 -0
- package/dist/builtin/workflows/builtin/tournament.ts +3 -2
- package/dist/builtin/workflows/package.json +4 -4
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +33 -129
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_asset_producer.toml +11 -3
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_documenter.toml +26 -0
- package/dist/builtin/workflows/skills/impeccable/agents/impeccable_finish_reviewer.toml +35 -0
- package/dist/builtin/workflows/skills/impeccable/reference/android.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/animate.md +72 -189
- package/dist/builtin/workflows/skills/impeccable/reference/audit.md +10 -9
- package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +19 -108
- package/dist/builtin/workflows/skills/impeccable/reference/clarify.md +59 -253
- package/dist/builtin/workflows/skills/impeccable/reference/colorize.md +51 -222
- package/dist/builtin/workflows/skills/impeccable/reference/craft-floor.md +45 -0
- package/dist/builtin/workflows/skills/impeccable/reference/craft.md +3 -121
- package/dist/builtin/workflows/skills/impeccable/reference/critique.md +28 -20
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/asset-producer.md +97 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/documenter.md +23 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/finish-reviewer.md +32 -0
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/dist/builtin/workflows/skills/impeccable/reference/delight.md +47 -279
- package/dist/builtin/workflows/skills/impeccable/reference/distill.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/doctor.md +53 -0
- package/dist/builtin/workflows/skills/impeccable/reference/document.md +60 -73
- package/dist/builtin/workflows/skills/impeccable/reference/harden.md +1 -12
- package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +23 -12
- package/dist/builtin/workflows/skills/impeccable/reference/init.md +64 -163
- package/dist/builtin/workflows/skills/impeccable/reference/ios.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/layout.md +52 -153
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +47 -36
- package/dist/builtin/workflows/skills/impeccable/reference/new-work.md +105 -0
- package/dist/builtin/workflows/skills/impeccable/reference/{product.md → operate.md} +6 -5
- package/dist/builtin/workflows/skills/impeccable/reference/optimize.md +4 -4
- package/dist/builtin/workflows/skills/impeccable/reference/overdrive.md +1 -4
- package/dist/builtin/workflows/skills/impeccable/reference/polish.md +68 -212
- package/dist/builtin/workflows/skills/impeccable/reference/quieter.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/routing.md +18 -0
- package/dist/builtin/workflows/skills/impeccable/reference/shape.md +38 -144
- package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +48 -269
- package/dist/builtin/workflows/skills/impeccable/reference/visualize.md +38 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/command-metadata.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/concept-seed.mjs +584 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +117 -9
- package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +486 -59
- package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +16 -45
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +96 -10
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +143 -26
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +181 -12
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +3187 -182
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +102 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +282 -70
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +183 -15
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +22 -7
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/findings.mjs +7 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +16 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +155 -42
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +2988 -155
- package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/constants.mjs +11 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/doctor.mjs +336 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/generate-image.mjs +232 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +92 -30
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +414 -120
- package/dist/builtin/workflows/skills/impeccable/scripts/hook.mjs +25 -8
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/artifact-schema.mjs +93 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/composition-catalog.mjs +165 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/concept-catalog.mjs +329 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +20 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +16 -8
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/provider.mjs +1 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-deep.mjs +455 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness-notice.mjs +169 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/staleness.mjs +457 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/surface-briefs.mjs +151 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-slug.mjs +33 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/lib/template-extensions.mjs +146 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/completion.mjs +10 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live/event-validation.mjs +15 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/generation-preflight.mjs +149 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/poll-lanes.mjs +14 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/session-store.mjs +109 -32
- package/dist/builtin/workflows/skills/impeccable/scripts/live/source-lock.mjs +105 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/source-search.mjs +105 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +8 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/live/tanstack-adapter.mjs +280 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +212 -69
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +5 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-session.js +7 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +565 -115
- package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +3 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +198 -14
- package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +24 -6
- package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +11 -3
- package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +44 -14
- package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +405 -34
- package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +9 -5
- package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +81 -67
- package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +7 -2
- package/dist/builtin/workflows/skills/impeccable/scripts/palette.mjs +76 -81
- package/dist/builtin/workflows/skills/impeccable/scripts/pin.mjs +4 -4
- package/dist/builtin/workflows/skills/impeccable/scripts/serve-question.mjs +890 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/surface-brief.mjs +74 -0
- package/dist/builtin/workflows/skills/prompt-engineer/SKILL.md +57 -252
- package/dist/builtin/workflows/skills/prompt-engineer/references/advanced_patterns.md +70 -226
- package/dist/builtin/workflows/skills/prompt-engineer/references/core_prompting.md +64 -103
- package/dist/builtin/workflows/skills/prompt-engineer/references/quality_improvement.md +81 -155
- package/dist/builtin/workflows/src/authoring/typebox-defaults.d.ts +5 -5
- package/dist/builtin/workflows/src/authoring/typebox-defaults.ts +160 -197
- package/dist/builtin/workflows/src/authoring/workflow.ts +137 -132
- package/dist/builtin/workflows/src/authoring.d.ts +7 -3
- package/dist/builtin/workflows/src/durable/backend.ts +512 -385
- package/dist/builtin/workflows/src/durable/boundary-lifecycle.ts +172 -0
- package/dist/builtin/workflows/src/durable/boundary-topology.ts +475 -0
- package/dist/builtin/workflows/src/durable/child-invocation.ts +14 -0
- package/dist/builtin/workflows/src/durable/child-primitive.ts +115 -74
- package/dist/builtin/workflows/src/durable/completed-catalog-stage-groups.ts +290 -0
- package/dist/builtin/workflows/src/durable/completed-catalog.ts +419 -189
- package/dist/builtin/workflows/src/durable/completed-inspection.ts +118 -105
- package/dist/builtin/workflows/src/durable/completed-subtree.ts +35 -0
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +484 -414
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +172 -0
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +183 -137
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +415 -189
- package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +128 -100
- package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +75 -68
- package/dist/builtin/workflows/src/durable/dbos-metadata.ts +138 -101
- package/dist/builtin/workflows/src/durable/dbos-prompt-reservations.ts +316 -259
- package/dist/builtin/workflows/src/durable/dbos-sdk-handle.ts +138 -117
- package/dist/builtin/workflows/src/durable/dbos-status-transition.ts +48 -43
- package/dist/builtin/workflows/src/durable/dbos-tombstone.ts +14 -14
- package/dist/builtin/workflows/src/durable/durable-hash.ts +9 -11
- package/dist/builtin/workflows/src/durable/factory.ts +70 -16
- package/dist/builtin/workflows/src/durable/format-version.ts +1 -1
- package/dist/builtin/workflows/src/durable/index.ts +69 -68
- package/dist/builtin/workflows/src/durable/local-command.ts +50 -37
- package/dist/builtin/workflows/src/durable/prompt-reservation-state.ts +156 -149
- package/dist/builtin/workflows/src/durable/prompt-reservations.ts +16 -16
- package/dist/builtin/workflows/src/durable/resume-catalog.ts +11 -11
- package/dist/builtin/workflows/src/durable/resume-eligibility.ts +27 -25
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +261 -222
- package/dist/builtin/workflows/src/durable/retention-policy.ts +31 -30
- package/dist/builtin/workflows/src/durable/run-timing.ts +42 -41
- package/dist/builtin/workflows/src/durable/scoped-backend.ts +261 -193
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +466 -370
- package/dist/builtin/workflows/src/durable/stage-topology-validation.ts +247 -0
- package/dist/builtin/workflows/src/durable/stage-topology.ts +53 -0
- package/dist/builtin/workflows/src/durable/tool-failure-checkpoint.ts +49 -0
- package/dist/builtin/workflows/src/durable/tool-outcome.ts +130 -0
- package/dist/builtin/workflows/src/durable/tool-primitive.ts +642 -137
- package/dist/builtin/workflows/src/durable/types.ts +243 -132
- package/dist/builtin/workflows/src/durable/ui-primitive.ts +243 -164
- package/dist/builtin/workflows/src/durable/workflow-child-result.ts +61 -0
- package/dist/builtin/workflows/src/durable/workflow-status-transition.ts +16 -15
- package/dist/builtin/workflows/src/engine/graph-inference.ts +76 -76
- package/dist/builtin/workflows/src/engine/options.ts +35 -35
- package/dist/builtin/workflows/src/engine/primitives/chain.ts +27 -22
- package/dist/builtin/workflows/src/engine/primitives/exit.ts +1 -1
- package/dist/builtin/workflows/src/engine/primitives/parallel.ts +64 -52
- package/dist/builtin/workflows/src/engine/primitives/task.ts +110 -95
- package/dist/builtin/workflows/src/engine/primitives/ui.ts +46 -50
- package/dist/builtin/workflows/src/engine/primitives/workflow.ts +194 -151
- package/dist/builtin/workflows/src/engine/replay.ts +6 -6
- package/dist/builtin/workflows/src/engine/run-durable-admission.ts +50 -0
- package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +64 -37
- package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +30 -24
- package/dist/builtin/workflows/src/engine/run-durable-topology.ts +288 -0
- package/dist/builtin/workflows/src/engine/run-returned-status.ts +60 -57
- package/dist/builtin/workflows/src/engine/run-terminal-event.ts +39 -0
- package/dist/builtin/workflows/src/engine/run-terminal-failure.ts +53 -0
- package/dist/builtin/workflows/src/engine/run-tool-admission-boundary.ts +99 -0
- package/dist/builtin/workflows/src/engine/run-tool-control-registry.ts +179 -0
- package/dist/builtin/workflows/src/engine/run-tool-execution-tracker.ts +164 -0
- package/dist/builtin/workflows/src/engine/run-tool-node-lifecycle.ts +152 -0
- package/dist/builtin/workflows/src/engine/run.ts +680 -477
- package/dist/builtin/workflows/src/engine/runtime.ts +160 -140
- package/dist/builtin/workflows/src/engine/workflow-activity.ts +7 -3
- package/dist/builtin/workflows/src/engine/workflow-tool-abort.ts +92 -0
- package/dist/builtin/workflows/src/extension/atomic-stage-session.ts +123 -120
- package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +108 -112
- package/dist/builtin/workflows/src/extension/companions.ts +116 -116
- package/dist/builtin/workflows/src/extension/completed-stage-intercom-ask.ts +123 -111
- package/dist/builtin/workflows/src/extension/config-file-loader.ts +153 -148
- package/dist/builtin/workflows/src/extension/config-loader.ts +264 -283
- package/dist/builtin/workflows/src/extension/discovery-loaders.ts +81 -84
- package/dist/builtin/workflows/src/extension/discovery.ts +267 -272
- package/dist/builtin/workflows/src/extension/dispatcher.ts +214 -217
- package/dist/builtin/workflows/src/extension/extension-factory.ts +103 -93
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +135 -107
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +414 -409
- package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +270 -261
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +74625 -0
- package/dist/builtin/workflows/src/extension/index.ts +37 -37
- package/dist/builtin/workflows/src/extension/lifecycle-notification-delivery.ts +64 -64
- package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +386 -369
- package/dist/builtin/workflows/src/extension/mcp.ts +35 -35
- package/dist/builtin/workflows/src/extension/postmortem-deps.ts +31 -35
- package/dist/builtin/workflows/src/extension/public-types.ts +162 -185
- package/dist/builtin/workflows/src/extension/render-call.ts +85 -84
- package/dist/builtin/workflows/src/extension/render-component.ts +9 -11
- package/dist/builtin/workflows/src/extension/render-result.ts +453 -348
- package/dist/builtin/workflows/src/extension/renderers.ts +35 -35
- package/dist/builtin/workflows/src/extension/runtime-active-block-claim.ts +37 -37
- package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +146 -138
- package/dist/builtin/workflows/src/extension/runtime.ts +346 -258
- package/dist/builtin/workflows/src/extension/status-writer.ts +95 -95
- package/dist/builtin/workflows/src/extension/ui-surface.ts +224 -218
- package/dist/builtin/workflows/src/extension/wiring.ts +307 -281
- package/dist/builtin/workflows/src/extension/workflow-command-completions.ts +109 -108
- package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +257 -224
- package/dist/builtin/workflows/src/extension/workflow-command-surfaces.ts +84 -83
- package/dist/builtin/workflows/src/extension/workflow-command-utils.ts +126 -132
- package/dist/builtin/workflows/src/extension/workflow-durable-resume-command.ts +213 -199
- package/dist/builtin/workflows/src/extension/workflow-model-catalog.ts +24 -0
- package/dist/builtin/workflows/src/extension/workflow-module-loader.ts +110 -95
- package/dist/builtin/workflows/src/extension/workflow-policy.ts +7 -9
- package/dist/builtin/workflows/src/extension/workflow-ports.ts +30 -34
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +41 -17
- package/dist/builtin/workflows/src/extension/workflow-reload-coordinator.ts +46 -46
- package/dist/builtin/workflows/src/extension/workflow-reload-report.ts +28 -25
- package/dist/builtin/workflows/src/extension/workflow-resume-picker-rows.ts +48 -45
- package/dist/builtin/workflows/src/extension/workflow-resume-shadow.ts +28 -31
- package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +539 -453
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +149 -91
- package/dist/builtin/workflows/src/extension/workflow-stage-results.ts +182 -194
- package/dist/builtin/workflows/src/extension/workflow-status-summary.ts +154 -116
- package/dist/builtin/workflows/src/extension/workflow-targets.ts +159 -125
- package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +204 -216
- package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +520 -294
- package/dist/builtin/workflows/src/extension/workflow-tool-inspection.ts +146 -150
- package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +43 -46
- package/dist/builtin/workflows/src/extension/workflow-tool-send.ts +354 -206
- package/dist/builtin/workflows/src/extension/workflow-tool.ts +123 -117
- package/dist/builtin/workflows/src/intercom/intercom-bridge.ts +17 -20
- package/dist/builtin/workflows/src/intercom/intercom-routing.ts +62 -62
- package/dist/builtin/workflows/src/intercom/result-intercom.ts +123 -123
- package/dist/builtin/workflows/src/runs/background/cancellation-registry.ts +72 -59
- package/dist/builtin/workflows/src/runs/background/durable-resume-transition.ts +45 -45
- package/dist/builtin/workflows/src/runs/background/job-tracker.ts +69 -34
- package/dist/builtin/workflows/src/runs/background/quit-tool-node.ts +71 -0
- package/dist/builtin/workflows/src/runs/background/quit.ts +359 -138
- package/dist/builtin/workflows/src/runs/background/resume-acknowledgements.ts +70 -69
- package/dist/builtin/workflows/src/runs/background/run-inspect.ts +85 -74
- package/dist/builtin/workflows/src/runs/background/runner.ts +122 -121
- package/dist/builtin/workflows/src/runs/background/startup-admission.ts +56 -60
- package/dist/builtin/workflows/src/runs/background/status.ts +324 -284
- package/dist/builtin/workflows/src/runs/background/workflow-lifecycle-aggregate.ts +24 -26
- package/dist/builtin/workflows/src/runs/foreground/executor-abort.ts +311 -309
- package/dist/builtin/workflows/src/runs/foreground/executor-child-boundary.ts +229 -199
- package/dist/builtin/workflows/src/runs/foreground/executor-child-helpers.ts +56 -51
- package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +168 -152
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +255 -217
- package/dist/builtin/workflows/src/runs/foreground/executor-exit-manager.ts +178 -173
- package/dist/builtin/workflows/src/runs/foreground/executor-hil.ts +273 -269
- package/dist/builtin/workflows/src/runs/foreground/executor-inputs.ts +59 -56
- package/dist/builtin/workflows/src/runs/foreground/executor-lifecycle.ts +375 -351
- package/dist/builtin/workflows/src/runs/foreground/executor-outputs.ts +123 -127
- package/dist/builtin/workflows/src/runs/foreground/executor-prompt-nodes.ts +360 -295
- package/dist/builtin/workflows/src/runs/foreground/executor-queued-user-message.ts +47 -47
- package/dist/builtin/workflows/src/runs/foreground/executor-run-finalizers.ts +125 -104
- package/dist/builtin/workflows/src/runs/foreground/executor-scheduler.ts +189 -189
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +294 -242
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +141 -127
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +219 -179
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +406 -354
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +143 -126
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +50 -47
- package/dist/builtin/workflows/src/runs/foreground/executor-task-context.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-task-prompts.ts +148 -153
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +157 -110
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +10 -10
- package/dist/builtin/workflows/src/runs/foreground/postmortem-stage-chat.ts +176 -100
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +363 -257
- package/dist/builtin/workflows/src/runs/foreground/stage-delivery-activity.ts +86 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-queued-user-messages.ts +95 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-candidate.ts +8 -8
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +233 -221
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +630 -494
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-message-admission.ts +182 -181
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-messages.ts +80 -80
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +78 -74
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-output.ts +102 -104
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-pause.ts +200 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-replacement.ts +31 -32
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +115 -98
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-session-options.ts +29 -42
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-session.ts +44 -52
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-structured-output.ts +63 -58
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +161 -124
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-unresolved-overflow.ts +27 -23
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +10 -10
- package/dist/builtin/workflows/src/runs/foreground/stage-tool-execution-buffer.ts +33 -31
- package/dist/builtin/workflows/src/runs/shared/concurrency.ts +53 -53
- package/dist/builtin/workflows/src/runs/shared/graph-inference.ts +1 -1
- package/dist/builtin/workflows/src/runs/shared/model-fallback-candidates.ts +257 -385
- package/dist/builtin/workflows/src/runs/shared/model-fallback-failures.ts +378 -363
- package/dist/builtin/workflows/src/runs/shared/prompt-callsite.ts +55 -55
- package/dist/builtin/workflows/src/runs/shared/validate-inputs.ts +75 -84
- package/dist/builtin/workflows/src/runs/shared/worktree-cache-lifecycle.ts +1 -3
- package/dist/builtin/workflows/src/runs/shared/worktree-cwd.ts +96 -83
- package/dist/builtin/workflows/src/runs/shared/worktree-diff.ts +9 -2
- package/dist/builtin/workflows/src/runs/shared/worktree-git-runner.ts +21 -6
- package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +60 -30
- package/dist/builtin/workflows/src/runs/shared/worktree-post-create.ts +12 -5
- package/dist/builtin/workflows/src/runs/shared/worktree-setup.ts +51 -30
- package/dist/builtin/workflows/src/runs/shared/worktree.ts +4 -4
- package/dist/builtin/workflows/src/sdk-surface.ts +38 -19
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +17 -16
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +299 -280
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +76 -10
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +311 -219
- package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +255 -156
- package/dist/builtin/workflows/src/shared/intercom-group.ts +33 -26
- package/dist/builtin/workflows/src/shared/persistence-compaction-policy.ts +30 -30
- package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +435 -379
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +187 -181
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +211 -207
- package/dist/builtin/workflows/src/shared/prompt-callsite-context.ts +3 -3
- package/dist/builtin/workflows/src/shared/render-inputs-schema.ts +87 -94
- package/dist/builtin/workflows/src/shared/resume-continuation.ts +1 -1
- package/dist/builtin/workflows/src/shared/returned-run-status.ts +91 -77
- package/dist/builtin/workflows/src/shared/run-visibility.ts +2 -2
- package/dist/builtin/workflows/src/shared/schema-introspection.ts +50 -60
- package/dist/builtin/workflows/src/shared/serializable.ts +72 -86
- package/dist/builtin/workflows/src/shared/session-transcript.ts +44 -39
- package/dist/builtin/workflows/src/shared/stage-prompt.ts +183 -202
- package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +270 -274
- package/dist/builtin/workflows/src/shared/store-factory.ts +9 -7
- package/dist/builtin/workflows/src/shared/store-internal.ts +172 -168
- package/dist/builtin/workflows/src/shared/store-prompt-methods.ts +196 -200
- package/dist/builtin/workflows/src/shared/store-public-types.ts +208 -196
- package/dist/builtin/workflows/src/shared/store-run-methods.ts +238 -222
- package/dist/builtin/workflows/src/shared/store-stage-methods.ts +284 -283
- package/dist/builtin/workflows/src/shared/store-tool-node-methods.ts +53 -0
- package/dist/builtin/workflows/src/shared/store-types.ts +269 -235
- package/dist/builtin/workflows/src/shared/store.ts +7 -7
- package/dist/builtin/workflows/src/shared/timing.ts +36 -34
- package/dist/builtin/workflows/src/shared/types.ts +257 -217
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +11 -4
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +81 -58
- package/dist/builtin/workflows/src/shared/workflow-failures-classifier.ts +247 -221
- package/dist/builtin/workflows/src/shared/workflow-failures-contract.ts +45 -46
- package/dist/builtin/workflows/src/shared/workflow-failures-decisions.ts +330 -351
- package/dist/builtin/workflows/src/shared/workflow-failures-signals.ts +212 -157
- package/dist/builtin/workflows/src/shared/workflow-failures.ts +10 -10
- package/dist/builtin/workflows/src/shared/workflow-run-ownership.ts +49 -0
- package/dist/builtin/workflows/src/tui/chat-surface-message.ts +157 -184
- package/dist/builtin/workflows/src/tui/chat-surface.ts +332 -355
- package/dist/builtin/workflows/src/tui/color-utils.ts +21 -30
- package/dist/builtin/workflows/src/tui/connectors.ts +57 -61
- package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +168 -192
- package/dist/builtin/workflows/src/tui/edge.ts +10 -9
- package/dist/builtin/workflows/src/tui/graph-canvas.ts +131 -134
- package/dist/builtin/workflows/src/tui/graph-theme.ts +139 -148
- package/dist/builtin/workflows/src/tui/graph-view-constants.ts +6 -6
- package/dist/builtin/workflows/src/tui/graph-view-graph-render.ts +146 -179
- package/dist/builtin/workflows/src/tui/graph-view-input.ts +295 -329
- package/dist/builtin/workflows/src/tui/graph-view-render-helpers.ts +291 -320
- package/dist/builtin/workflows/src/tui/graph-view-render.ts +171 -190
- package/dist/builtin/workflows/src/tui/graph-view-state.ts +319 -287
- package/dist/builtin/workflows/src/tui/graph-view-types.ts +65 -58
- package/dist/builtin/workflows/src/tui/header.ts +140 -156
- package/dist/builtin/workflows/src/tui/host-input-form.ts +47 -45
- package/dist/builtin/workflows/src/tui/inline-form-card.ts +277 -266
- package/dist/builtin/workflows/src/tui/inline-form-editor-text.ts +61 -62
- package/dist/builtin/workflows/src/tui/inline-form-editor.ts +407 -412
- package/dist/builtin/workflows/src/tui/inline-form-overlay.ts +203 -214
- package/dist/builtin/workflows/src/tui/inline-form-store.ts +24 -24
- package/dist/builtin/workflows/src/tui/inputs-overlay.ts +116 -129
- package/dist/builtin/workflows/src/tui/inputs-picker-editing.ts +111 -115
- package/dist/builtin/workflows/src/tui/inputs-picker-input.ts +255 -269
- package/dist/builtin/workflows/src/tui/inputs-picker-render.ts +248 -236
- package/dist/builtin/workflows/src/tui/inputs-picker-types.ts +111 -140
- package/dist/builtin/workflows/src/tui/inputs-picker.ts +12 -12
- package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +49 -57
- package/dist/builtin/workflows/src/tui/layout.ts +124 -133
- package/dist/builtin/workflows/src/tui/mouse-input.ts +45 -47
- package/dist/builtin/workflows/src/tui/node-card.ts +230 -218
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +381 -398
- package/dist/builtin/workflows/src/tui/overlay-terminal-modes.ts +20 -19
- package/dist/builtin/workflows/src/tui/prompt-card-input.ts +183 -194
- package/dist/builtin/workflows/src/tui/prompt-card-render.ts +243 -302
- package/dist/builtin/workflows/src/tui/prompt-card-select.ts +76 -98
- package/dist/builtin/workflows/src/tui/prompt-card-state.ts +40 -40
- package/dist/builtin/workflows/src/tui/prompt-card-text.ts +76 -83
- package/dist/builtin/workflows/src/tui/prompt-card.ts +3 -3
- package/dist/builtin/workflows/src/tui/renderers.ts +6 -6
- package/dist/builtin/workflows/src/tui/run-detail.ts +300 -256
- package/dist/builtin/workflows/src/tui/session-list.ts +19 -18
- package/dist/builtin/workflows/src/tui/session-overlays.ts +82 -87
- package/dist/builtin/workflows/src/tui/session-picker.ts +258 -275
- package/dist/builtin/workflows/src/tui/stage-chat-composer-drafts.ts +11 -11
- package/dist/builtin/workflows/src/tui/stage-chat-layout.ts +55 -62
- package/dist/builtin/workflows/src/tui/stage-chat-view-archive-history.ts +220 -263
- package/dist/builtin/workflows/src/tui/stage-chat-view-custom-ui.ts +91 -102
- package/dist/builtin/workflows/src/tui/stage-chat-view-delivery-activity.ts +113 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +154 -208
- package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +174 -208
- package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +53 -23
- package/dist/builtin/workflows/src/tui/stage-chat-view-pending-tools.ts +2 -4
- package/dist/builtin/workflows/src/tui/stage-chat-view-render-helpers.ts +85 -88
- package/dist/builtin/workflows/src/tui/stage-chat-view-render-settings.ts +39 -17
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +435 -409
- package/dist/builtin/workflows/src/tui/stage-chat-view-status.ts +24 -25
- package/dist/builtin/workflows/src/tui/stage-chat-view-transcript.ts +181 -197
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +125 -128
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +210 -205
- package/dist/builtin/workflows/src/tui/status-helpers.ts +65 -69
- package/dist/builtin/workflows/src/tui/status-list.ts +316 -294
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +213 -214
- package/dist/builtin/workflows/src/tui/submit-pane.ts +114 -130
- package/dist/builtin/workflows/src/tui/switcher.ts +120 -128
- package/dist/builtin/workflows/src/tui/text-helpers.ts +136 -145
- package/dist/builtin/workflows/src/tui/toast.ts +68 -67
- package/dist/builtin/workflows/src/tui/widget.ts +265 -285
- package/dist/builtin/workflows/src/tui/workflow-attach-pane-handle.ts +12 -17
- package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +81 -91
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +468 -479
- package/dist/builtin/workflows/src/tui/workflow-list.ts +130 -145
- package/dist/builtin/workflows/src/tui/workflow-notice-card.ts +127 -144
- package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +217 -217
- package/dist/builtin/workflows/src/tui/workflow-status.ts +7 -0
- package/dist/builtin/workflows/src/workflows/identity.ts +12 -12
- package/dist/builtin/workflows/src/workflows/registry.ts +66 -68
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/internal-intercom-broker.d.ts.map +1 -1
- package/dist/bun/internal-intercom-broker.js +1 -1
- package/dist/bun/internal-intercom-broker.js.map +1 -1
- package/dist/bun/register-bedrock.js +1 -1
- package/dist/bun/register-bedrock.js.map +1 -1
- package/dist/cli/args.d.ts +8 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +28 -22
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +4 -2
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +2 -2
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/credential-print.d.ts +170 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +513 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/list-models.d.ts +2 -2
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +6 -7
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/project-trust.d.ts +1 -1
- package/dist/cli/project-trust.d.ts.map +1 -1
- package/dist/cli/project-trust.js.map +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +2 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli/startup-ui.d.ts +3 -0
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +58 -2
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/config-command-parser.d.ts +9 -0
- package/dist/config-command-parser.d.ts.map +1 -0
- package/dist/config-command-parser.js +22 -0
- package/dist/config-command-parser.js.map +1 -0
- package/dist/config-self-update.d.ts +6 -2
- package/dist/config-self-update.d.ts.map +1 -1
- package/dist/config-self-update.js +23 -13
- package/dist/config-self-update.js.map +1 -1
- package/dist/config.d.ts +5 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +36 -14
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-accessors.d.ts.map +1 -1
- package/dist/core/agent-session-accessors.js +125 -24
- package/dist/core/agent-session-accessors.js.map +1 -1
- package/dist/core/agent-session-auto-compaction.d.ts +4 -3
- package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-auto-compaction.js +59 -64
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-bash.d.ts +10 -7
- package/dist/core/agent-session-bash.d.ts.map +1 -1
- package/dist/core/agent-session-bash.js +34 -13
- package/dist/core/agent-session-bash.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts +11 -1
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +138 -27
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-custom-message-commit.d.ts +14 -0
- package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -0
- package/dist/core/agent-session-custom-message-commit.js +145 -0
- package/dist/core/agent-session-custom-message-commit.js.map +1 -0
- package/dist/core/agent-session-delivery-forwarding.d.ts +9 -0
- package/dist/core/agent-session-delivery-forwarding.d.ts.map +1 -0
- package/dist/core/agent-session-delivery-forwarding.js +36 -0
- package/dist/core/agent-session-delivery-forwarding.js.map +1 -0
- package/dist/core/agent-session-events.d.ts +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +58 -19
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-export.d.ts +1 -1
- package/dist/core/agent-session-export.d.ts.map +1 -1
- package/dist/core/agent-session-export.js +32 -25
- package/dist/core/agent-session-export.js.map +1 -1
- package/dist/core/agent-session-extension-bindings.d.ts +2 -6
- package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
- package/dist/core/agent-session-extension-bindings.js +38 -18
- package/dist/core/agent-session-extension-bindings.js.map +1 -1
- package/dist/core/agent-session-message-queue.d.ts +7 -4
- package/dist/core/agent-session-message-queue.d.ts.map +1 -1
- package/dist/core/agent-session-message-queue.js +101 -98
- package/dist/core/agent-session-message-queue.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +52 -45
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-models.d.ts +3 -39
- package/dist/core/agent-session-models.d.ts.map +1 -1
- package/dist/core/agent-session-models.js +22 -160
- package/dist/core/agent-session-models.js.map +1 -1
- package/dist/core/agent-session-persistent-custom-messages.d.ts +47 -0
- package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -0
- package/dist/core/agent-session-persistent-custom-messages.js +269 -0
- package/dist/core/agent-session-persistent-custom-messages.js.map +1 -0
- package/dist/core/agent-session-post-tool-compaction.d.ts +1 -1
- package/dist/core/agent-session-post-tool-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-post-tool-compaction.js +34 -11
- package/dist/core/agent-session-post-tool-compaction.js.map +1 -1
- package/dist/core/agent-session-prompt.d.ts +2 -0
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +52 -25
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-queue-pause.d.ts +10 -0
- package/dist/core/agent-session-queue-pause.d.ts.map +1 -0
- package/dist/core/agent-session-queue-pause.js +87 -0
- package/dist/core/agent-session-queue-pause.js.map +1 -0
- package/dist/core/agent-session-retry.d.ts +0 -11
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +44 -63
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-runtime-auth.d.ts +6 -0
- package/dist/core/agent-session-runtime-auth.d.ts.map +1 -0
- package/dist/core/agent-session-runtime-auth.js +18 -0
- package/dist/core/agent-session-runtime-auth.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts +36 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +62 -6
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +4 -9
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +25 -25
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-state.d.ts +1 -1
- package/dist/core/agent-session-state.d.ts.map +1 -1
- package/dist/core/agent-session-state.js +3 -0
- package/dist/core/agent-session-state.js.map +1 -1
- package/dist/core/agent-session-tool-hooks.d.ts.map +1 -1
- package/dist/core/agent-session-tool-hooks.js +8 -7
- package/dist/core/agent-session-tool-hooks.js.map +1 -1
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +6 -20
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/agent-session-transfer.d.ts +4 -0
- package/dist/core/agent-session-transfer.d.ts.map +1 -0
- package/dist/core/agent-session-transfer.js +67 -0
- package/dist/core/agent-session-transfer.js.map +1 -0
- package/dist/core/agent-session-tree.d.ts +1 -1
- package/dist/core/agent-session-tree.d.ts.map +1 -1
- package/dist/core/agent-session-tree.js +13 -3
- package/dist/core/agent-session-tree.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +28 -14
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js +4 -4
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/agent-session.d.ts +27 -13
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +17 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/anthropic-thinking-guard.d.ts.map +1 -1
- package/dist/core/anthropic-thinking-guard.js +4 -2
- package/dist/core/anthropic-thinking-guard.js.map +1 -1
- package/dist/core/async/format.d.ts +1 -1
- package/dist/core/async/format.d.ts.map +1 -1
- package/dist/core/async/format.js +3 -1
- package/dist/core/async/format.js.map +1 -1
- package/dist/core/async/job-manager.d.ts.map +1 -1
- package/dist/core/async/job-manager.js +20 -5
- package/dist/core/async/job-manager.js.map +1 -1
- package/dist/core/async/session-manager.d.ts.map +1 -1
- package/dist/core/async/session-manager.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +34 -35
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/auth-storage-backends.d.ts +5 -0
- package/dist/core/auth-storage-backends.d.ts.map +1 -1
- package/dist/core/auth-storage-backends.js +77 -7
- package/dist/core/auth-storage-backends.js.map +1 -1
- package/dist/core/auth-storage.d.ts +13 -136
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +68 -358
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts +5 -3
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +5 -4
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/builtin-packages.d.ts.map +1 -1
- package/dist/core/builtin-packages.js +1 -10
- package/dist/core/builtin-packages.js.map +1 -1
- package/dist/core/cache-stats.d.ts +25 -0
- package/dist/core/cache-stats.d.ts.map +1 -0
- package/dist/core/cache-stats.js +70 -0
- package/dist/core/cache-stats.js.map +1 -0
- package/dist/core/callback-activity.d.ts.map +1 -1
- package/dist/core/callback-activity.js.map +1 -1
- package/dist/core/codex-errors.d.ts +3 -0
- package/dist/core/codex-errors.d.ts.map +1 -0
- package/dist/core/codex-errors.js +12 -0
- package/dist/core/codex-errors.js.map +1 -0
- package/dist/core/codex-fast-mode.d.ts.map +1 -1
- package/dist/core/codex-fast-mode.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +15 -5
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +23 -12
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction-boundary.d.ts +18 -1
- package/dist/core/compaction/compaction-boundary.d.ts.map +1 -1
- package/dist/core/compaction/compaction-boundary.js +12 -8
- package/dist/core/compaction/compaction-boundary.js.map +1 -1
- package/dist/core/compaction/compaction-runner.d.ts +63 -6
- package/dist/core/compaction/compaction-runner.d.ts.map +1 -1
- package/dist/core/compaction/compaction-runner.js +244 -17
- package/dist/core/compaction/compaction-runner.js.map +1 -1
- package/dist/core/compaction/compaction-types.d.ts +53 -2
- package/dist/core/compaction/compaction-types.d.ts.map +1 -1
- package/dist/core/compaction/compaction-types.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +5 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/deleted-ranges.d.ts.map +1 -1
- package/dist/core/compaction/deleted-ranges.js.map +1 -1
- package/dist/core/compaction/fallback-planner.d.ts +64 -0
- package/dist/core/compaction/fallback-planner.d.ts.map +1 -0
- package/dist/core/compaction/fallback-planner.js +75 -0
- package/dist/core/compaction/fallback-planner.js.map +1 -0
- package/dist/core/compaction/index.d.ts +7 -4
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +7 -4
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/planner-outcome.d.ts +86 -0
- package/dist/core/compaction/planner-outcome.d.ts.map +1 -0
- package/dist/core/compaction/planner-outcome.js +122 -0
- package/dist/core/compaction/planner-outcome.js.map +1 -0
- package/dist/core/compaction/range-planner-diagnostics.d.ts +58 -6
- package/dist/core/compaction/range-planner-diagnostics.d.ts.map +1 -1
- package/dist/core/compaction/range-planner-diagnostics.js +71 -31
- package/dist/core/compaction/range-planner-diagnostics.js.map +1 -1
- package/dist/core/compaction/range-planner.d.ts +39 -8
- package/dist/core/compaction/range-planner.d.ts.map +1 -1
- package/dist/core/compaction/range-planner.js +140 -51
- package/dist/core/compaction/range-planner.js.map +1 -1
- package/dist/core/compaction/region-trimming.d.ts +34 -0
- package/dist/core/compaction/region-trimming.d.ts.map +1 -0
- package/dist/core/compaction/region-trimming.js +73 -0
- package/dist/core/compaction/region-trimming.js.map +1 -0
- package/dist/core/compaction/transcript-serialization.d.ts +8 -1
- package/dist/core/compaction/transcript-serialization.d.ts.map +1 -1
- package/dist/core/compaction/transcript-serialization.js +78 -27
- package/dist/core/compaction/transcript-serialization.js.map +1 -1
- package/dist/core/context-tool-pairing.d.ts +19 -0
- package/dist/core/context-tool-pairing.d.ts.map +1 -0
- package/dist/core/context-tool-pairing.js +50 -0
- package/dist/core/context-tool-pairing.js.map +1 -0
- package/dist/core/copilot-env-routing.d.ts +36 -0
- package/dist/core/copilot-env-routing.d.ts.map +1 -0
- package/dist/core/copilot-env-routing.js +94 -0
- package/dist/core/copilot-env-routing.js.map +1 -0
- package/dist/core/diagnostics.d.ts +8 -0
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/diagnostics.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +5 -0
- package/dist/core/exec.js.map +1 -1
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +2 -1
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/agent-events.d.ts +10 -0
- package/dist/core/extensions/agent-events.d.ts.map +1 -1
- package/dist/core/extensions/agent-events.js.map +1 -1
- package/dist/core/extensions/api-types.d.ts +9 -3
- package/dist/core/extensions/api-types.d.ts.map +1 -1
- package/dist/core/extensions/api-types.js.map +1 -1
- package/dist/core/extensions/context-types.d.ts +10 -1
- package/dist/core/extensions/context-types.d.ts.map +1 -1
- package/dist/core/extensions/context-types.js.map +1 -1
- package/dist/core/extensions/event-types.d.ts +2 -2
- package/dist/core/extensions/event-types.d.ts.map +1 -1
- package/dist/core/extensions/event-types.js.map +1 -1
- package/dist/core/extensions/index.d.ts +7 -5
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +2 -2
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader-api.d.ts +1 -1
- package/dist/core/extensions/loader-api.d.ts.map +1 -1
- package/dist/core/extensions/loader-api.js +63 -29
- package/dist/core/extensions/loader-api.js.map +1 -1
- package/dist/core/extensions/loader-core.d.ts.map +1 -1
- package/dist/core/extensions/loader-core.js +3 -2
- package/dist/core/extensions/loader-core.js.map +1 -1
- package/dist/core/extensions/loader-discovery.d.ts.map +1 -1
- package/dist/core/extensions/loader-discovery.js.map +1 -1
- package/dist/core/extensions/loader-resources.d.ts.map +1 -1
- package/dist/core/extensions/loader-resources.js.map +1 -1
- package/dist/core/extensions/loader-runtime.d.ts +2 -4
- package/dist/core/extensions/loader-runtime.d.ts.map +1 -1
- package/dist/core/extensions/loader-runtime.js +214 -13
- package/dist/core/extensions/loader-runtime.js.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.d.ts +17 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +288 -19
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/message-types.d.ts +12 -4
- package/dist/core/extensions/message-types.d.ts.map +1 -1
- package/dist/core/extensions/message-types.js.map +1 -1
- package/dist/core/extensions/provider-types.d.ts +16 -5
- package/dist/core/extensions/provider-types.d.ts.map +1 -1
- package/dist/core/extensions/provider-types.js.map +1 -1
- package/dist/core/extensions/reactive-widget.d.ts.map +1 -1
- package/dist/core/extensions/reactive-widget.js.map +1 -1
- package/dist/core/extensions/runner-context.d.ts +29 -1
- package/dist/core/extensions/runner-context.d.ts.map +1 -1
- package/dist/core/extensions/runner-context.js +56 -0
- package/dist/core/extensions/runner-context.js.map +1 -1
- package/dist/core/extensions/runner-events.d.ts.map +1 -1
- package/dist/core/extensions/runner-events.js.map +1 -1
- package/dist/core/extensions/runner-registries.d.ts +2 -1
- package/dist/core/extensions/runner-registries.d.ts.map +1 -1
- package/dist/core/extensions/runner-registries.js +8 -0
- package/dist/core/extensions/runner-registries.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +7 -2
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +65 -23
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/runtime-types.d.ts +35 -2
- package/dist/core/extensions/runtime-types.d.ts.map +1 -1
- package/dist/core/extensions/runtime-types.js.map +1 -1
- package/dist/core/extensions/session-events.d.ts.map +1 -1
- package/dist/core/extensions/session-events.js.map +1 -1
- package/dist/core/extensions/tool-types.d.ts +7 -3
- package/dist/core/extensions/tool-types.d.ts.map +1 -1
- package/dist/core/extensions/tool-types.js.map +1 -1
- package/dist/core/extensions/types.d.ts +15 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/ui-types.d.ts +34 -6
- package/dist/core/extensions/ui-types.d.ts.map +1 -1
- package/dist/core/extensions/ui-types.js.map +1 -1
- package/dist/core/fallback-models.d.ts +37 -0
- package/dist/core/fallback-models.d.ts.map +1 -0
- package/dist/core/fallback-models.js +57 -0
- package/dist/core/fallback-models.js.map +1 -0
- package/dist/core/flattened-tool-arguments.d.ts +4 -7
- package/dist/core/flattened-tool-arguments.d.ts.map +1 -1
- package/dist/core/flattened-tool-arguments.js +4 -7
- package/dist/core/flattened-tool-arguments.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +5 -3
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +1 -0
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +42 -8
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/keybindings.d.ts +7 -2
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +3 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/messages.d.ts +24 -4
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +64 -13
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-capabilities.d.ts +18 -0
- package/dist/core/model-capabilities.d.ts.map +1 -0
- package/dist/core/model-capabilities.js +22 -0
- package/dist/core/model-capabilities.js.map +1 -0
- package/dist/core/model-config.d.ts +542 -0
- package/dist/core/model-config.d.ts.map +1 -0
- package/dist/core/{model-registry-schemas.js → model-config.js} +92 -32
- package/dist/core/model-config.js.map +1 -0
- package/dist/core/model-refresh-timeout.d.ts +9 -0
- package/dist/core/model-refresh-timeout.d.ts.map +1 -0
- package/dist/core/model-refresh-timeout.js +9 -0
- package/dist/core/model-refresh-timeout.js.map +1 -0
- package/dist/core/model-registry.d.ts +26 -94
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +65 -382
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver-cli.d.ts +2 -2
- package/dist/core/model-resolver-cli.d.ts.map +1 -1
- package/dist/core/model-resolver-cli.js +2 -2
- package/dist/core/model-resolver-cli.js.map +1 -1
- package/dist/core/model-resolver-defaults.d.ts.map +1 -1
- package/dist/core/model-resolver-defaults.js +5 -1
- package/dist/core/model-resolver-defaults.js.map +1 -1
- package/dist/core/model-resolver-initial.d.ts +4 -4
- package/dist/core/model-resolver-initial.d.ts.map +1 -1
- package/dist/core/model-resolver-initial.js +26 -17
- package/dist/core/model-resolver-initial.js.map +1 -1
- package/dist/core/model-resolver-patterns.d.ts.map +1 -1
- package/dist/core/model-resolver-patterns.js +0 -2
- package/dist/core/model-resolver-patterns.js.map +1 -1
- package/dist/core/model-resolver-scope.d.ts +5 -3
- package/dist/core/model-resolver-scope.d.ts.map +1 -1
- package/dist/core/model-resolver-scope.js +32 -8
- package/dist/core/model-resolver-scope.js.map +1 -1
- package/dist/core/model-resolver-types.d.ts +2 -0
- package/dist/core/model-resolver-types.d.ts.map +1 -1
- package/dist/core/model-resolver-types.js.map +1 -1
- package/dist/core/model-resolver.d.ts +3 -3
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime-auth.d.ts +7 -0
- package/dist/core/model-runtime-auth.d.ts.map +1 -0
- package/dist/core/model-runtime-auth.js +17 -0
- package/dist/core/model-runtime-auth.js.map +1 -0
- package/dist/core/model-runtime-providers.d.ts +6 -0
- package/dist/core/model-runtime-providers.d.ts.map +1 -0
- package/dist/core/model-runtime-providers.js +22 -0
- package/dist/core/model-runtime-providers.js.map +1 -0
- package/dist/core/model-runtime-restoration.d.ts +6 -0
- package/dist/core/model-runtime-restoration.d.ts.map +1 -0
- package/dist/core/model-runtime-restoration.js +21 -0
- package/dist/core/model-runtime-restoration.js.map +1 -0
- package/dist/core/model-runtime-snapshot.d.ts +19 -0
- package/dist/core/model-runtime-snapshot.d.ts.map +1 -0
- package/dist/core/model-runtime-snapshot.js +111 -0
- package/dist/core/model-runtime-snapshot.js.map +1 -0
- package/dist/core/model-runtime-streaming.d.ts +17 -0
- package/dist/core/model-runtime-streaming.d.ts.map +1 -0
- package/dist/core/model-runtime-streaming.js +66 -0
- package/dist/core/model-runtime-streaming.js.map +1 -0
- package/dist/core/model-runtime-types.d.ts +21 -0
- package/dist/core/model-runtime-types.d.ts.map +1 -0
- package/dist/core/model-runtime-types.js +2 -0
- package/dist/core/model-runtime-types.js.map +1 -0
- package/dist/core/model-runtime.d.ts +79 -0
- package/dist/core/model-runtime.d.ts.map +1 -0
- package/dist/core/model-runtime.js +417 -0
- package/dist/core/model-runtime.js.map +1 -0
- package/dist/core/models-store.d.ts +2 -10
- package/dist/core/models-store.d.ts.map +1 -1
- package/dist/core/models-store.js +2 -29
- package/dist/core/models-store.js.map +1 -1
- package/dist/core/oauth-login.d.ts +24 -0
- package/dist/core/oauth-login.d.ts.map +1 -0
- package/dist/core/oauth-login.js +92 -0
- package/dist/core/oauth-login.js.map +1 -0
- package/dist/core/oauth-provider-metadata.d.ts +5 -0
- package/dist/core/oauth-provider-metadata.d.ts.map +1 -0
- package/dist/core/oauth-provider-metadata.js +33 -0
- package/dist/core/oauth-provider-metadata.js.map +1 -0
- package/dist/core/openai-responses-payload-sanitizer.js.map +1 -1
- package/dist/core/output-guard.d.ts +44 -0
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +63 -0
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/package-manager-auto-resources.d.ts.map +1 -1
- package/dist/core/package-manager-auto-resources.js +18 -5
- package/dist/core/package-manager-auto-resources.js.map +1 -1
- package/dist/core/package-manager-git.d.ts.map +1 -1
- package/dist/core/package-manager-git.js +27 -11
- package/dist/core/package-manager-git.js.map +1 -1
- package/dist/core/package-manager-npm.d.ts.map +1 -1
- package/dist/core/package-manager-npm.js +4 -2
- package/dist/core/package-manager-npm.js.map +1 -1
- package/dist/core/package-manager-operations.d.ts.map +1 -1
- package/dist/core/package-manager-operations.js +2 -2
- package/dist/core/package-manager-operations.js.map +1 -1
- package/dist/core/package-manager-paths.d.ts.map +1 -1
- package/dist/core/package-manager-paths.js.map +1 -1
- package/dist/core/package-manager-resolver.d.ts.map +1 -1
- package/dist/core/package-manager-resolver.js +71 -26
- package/dist/core/package-manager-resolver.js.map +1 -1
- package/dist/core/package-manager-resource-collector.d.ts.map +1 -1
- package/dist/core/package-manager-resource-collector.js +13 -7
- package/dist/core/package-manager-resource-collector.js.map +1 -1
- package/dist/core/package-manager-resource-files.d.ts.map +1 -1
- package/dist/core/package-manager-resource-files.js +4 -3
- package/dist/core/package-manager-resource-files.js.map +1 -1
- package/dist/core/package-manager-resource-patterns.d.ts +1 -0
- package/dist/core/package-manager-resource-patterns.d.ts.map +1 -1
- package/dist/core/package-manager-resource-patterns.js +15 -0
- package/dist/core/package-manager-resource-patterns.js.map +1 -1
- package/dist/core/package-manager-settings.d.ts.map +1 -1
- package/dist/core/package-manager-settings.js +3 -2
- package/dist/core/package-manager-settings.js.map +1 -1
- package/dist/core/package-manager-source.d.ts.map +1 -1
- package/dist/core/package-manager-source.js +16 -9
- package/dist/core/package-manager-source.js.map +1 -1
- package/dist/core/package-manager-types.d.ts +3 -0
- package/dist/core/package-manager-types.d.ts.map +1 -1
- package/dist/core/package-manager-types.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +2 -3
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates-async.d.ts.map +1 -1
- package/dist/core/prompt-templates-async.js +15 -7
- package/dist/core/prompt-templates-async.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +1 -0
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +4 -4
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/provider-composer-internal.d.ts +53 -0
- package/dist/core/provider-composer-internal.d.ts.map +1 -0
- package/dist/core/provider-composer-internal.js +273 -0
- package/dist/core/provider-composer-internal.js.map +1 -0
- package/dist/core/provider-composer.d.ts +16 -0
- package/dist/core/provider-composer.d.ts.map +1 -0
- package/dist/core/provider-composer.js +108 -0
- package/dist/core/provider-composer.js.map +1 -0
- package/dist/core/provider-context-usage.d.ts.map +1 -1
- package/dist/core/provider-context-usage.js +2 -1
- package/dist/core/provider-context-usage.js.map +1 -1
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +4 -0
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +82 -105
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts +8 -7
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +26 -23
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader-assets.d.ts.map +1 -1
- package/dist/core/resource-loader-assets.js +58 -8
- package/dist/core/resource-loader-assets.js.map +1 -1
- package/dist/core/resource-loader-context-files.d.ts +6 -0
- package/dist/core/resource-loader-context-files.d.ts.map +1 -1
- package/dist/core/resource-loader-context-files.js +85 -4
- package/dist/core/resource-loader-context-files.js.map +1 -1
- package/dist/core/resource-loader-core.d.ts +13 -3
- package/dist/core/resource-loader-core.d.ts.map +1 -1
- package/dist/core/resource-loader-core.js +39 -19
- package/dist/core/resource-loader-core.js.map +1 -1
- package/dist/core/resource-loader-discovery.d.ts.map +1 -1
- package/dist/core/resource-loader-discovery.js.map +1 -1
- package/dist/core/resource-loader-extensions.d.ts +1 -0
- package/dist/core/resource-loader-extensions.d.ts.map +1 -1
- package/dist/core/resource-loader-extensions.js +140 -12
- package/dist/core/resource-loader-extensions.js.map +1 -1
- package/dist/core/resource-loader-helpers.d.ts.map +1 -1
- package/dist/core/resource-loader-helpers.js +1 -0
- package/dist/core/resource-loader-helpers.js.map +1 -1
- package/dist/core/resource-loader-internals.d.ts +14 -7
- package/dist/core/resource-loader-internals.d.ts.map +1 -1
- package/dist/core/resource-loader-internals.js.map +1 -1
- package/dist/core/resource-loader-package-resources.d.ts.map +1 -1
- package/dist/core/resource-loader-package-resources.js +15 -2
- package/dist/core/resource-loader-package-resources.js.map +1 -1
- package/dist/core/resource-loader-paths.js.map +1 -1
- package/dist/core/resource-loader-reload.d.ts.map +1 -1
- package/dist/core/resource-loader-reload.js +29 -13
- package/dist/core/resource-loader-reload.js.map +1 -1
- package/dist/core/resource-loader-source-info.d.ts +1 -1
- package/dist/core/resource-loader-source-info.d.ts.map +1 -1
- package/dist/core/resource-loader-source-info.js +7 -4
- package/dist/core/resource-loader-source-info.js.map +1 -1
- package/dist/core/resource-loader-types.d.ts +12 -6
- package/dist/core/resource-loader-types.d.ts.map +1 -1
- package/dist/core/resource-loader-types.js.map +1 -1
- package/dist/core/resource-loader.d.ts +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +1 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-credentials.d.ts +16 -0
- package/dist/core/runtime-credentials.d.ts.map +1 -0
- package/dist/core/runtime-credentials.js +42 -0
- package/dist/core/runtime-credentials.js.map +1 -0
- package/dist/core/sdk-exports.d.ts +2 -2
- package/dist/core/sdk-exports.d.ts.map +1 -1
- package/dist/core/sdk-exports.js +2 -2
- package/dist/core/sdk-exports.js.map +1 -1
- package/dist/core/sdk-types.d.ts +16 -18
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +61 -132
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-archive.d.ts.map +1 -1
- package/dist/core/session-manager-archive.js +5 -2
- package/dist/core/session-manager-archive.js.map +1 -1
- package/dist/core/session-manager-classification.js +3 -3
- package/dist/core/session-manager-classification.js.map +1 -1
- package/dist/core/session-manager-core.d.ts +5 -4
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +29 -33
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-entries.d.ts +5 -8
- package/dist/core/session-manager-entries.d.ts.map +1 -1
- package/dist/core/session-manager-entries.js +4 -9
- package/dist/core/session-manager-entries.js.map +1 -1
- package/dist/core/session-manager-history.d.ts +8 -3
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +90 -27
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/session-manager-list.d.ts.map +1 -1
- package/dist/core/session-manager-list.js +1 -1
- package/dist/core/session-manager-list.js.map +1 -1
- package/dist/core/session-manager-migrations.js.map +1 -1
- package/dist/core/session-manager-storage.d.ts +6 -0
- package/dist/core/session-manager-storage.d.ts.map +1 -1
- package/dist/core/session-manager-storage.js +38 -5
- package/dist/core/session-manager-storage.js.map +1 -1
- package/dist/core/session-manager-types.d.ts +10 -8
- package/dist/core/session-manager-types.d.ts.map +1 -1
- package/dist/core/session-manager-types.js.map +1 -1
- package/dist/core/session-manager-validation.d.ts.map +1 -1
- package/dist/core/session-manager-validation.js +3 -16
- package/dist/core/session-manager-validation.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -4
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +2 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts +7 -4
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js +35 -50
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/core/settings-manager-core.d.ts +2 -4
- package/dist/core/settings-manager-core.d.ts.map +1 -1
- package/dist/core/settings-manager-core.js +5 -51
- package/dist/core/settings-manager-core.js.map +1 -1
- package/dist/core/settings-manager-resource-accessors.d.ts +3 -0
- package/dist/core/settings-manager-resource-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-resource-accessors.js +16 -0
- package/dist/core/settings-manager-resource-accessors.js.map +1 -1
- package/dist/core/settings-manager-ui-accessors.js +2 -1
- package/dist/core/settings-manager-ui-accessors.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-storage.d.ts +3 -1
- package/dist/core/settings-storage.d.ts.map +1 -1
- package/dist/core/settings-storage.js +23 -1
- package/dist/core/settings-storage.js.map +1 -1
- package/dist/core/settings-types.d.ts +10 -6
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/skills-async.d.ts.map +1 -1
- package/dist/core/skills-async.js +18 -8
- package/dist/core/skills-async.js.map +1 -1
- package/dist/core/slash-commands.d.ts +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +127 -24
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/source-info.d.ts +3 -1
- package/dist/core/source-info.d.ts.map +1 -1
- package/dist/core/source-info.js +2 -0
- package/dist/core/source-info.js.map +1 -1
- package/dist/core/summarization-retry.d.ts +11 -0
- package/dist/core/summarization-retry.d.ts.map +1 -0
- package/dist/core/summarization-retry.js +21 -0
- package/dist/core/summarization-retry.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +15 -18
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/thinking-blocks.d.ts.map +1 -1
- package/dist/core/thinking-blocks.js +2 -2
- package/dist/core/thinking-blocks.js.map +1 -1
- package/dist/core/tools/artifact-protocol.d.ts.map +1 -1
- package/dist/core/tools/artifact-protocol.js.map +1 -1
- package/dist/core/tools/artifacts.d.ts.map +1 -1
- package/dist/core/tools/artifacts.js.map +1 -1
- package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/ask-user-question.js +1 -1
- package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/build-questionnaire.d.ts +1 -1
- package/dist/core/tools/ask-user-question/state/build-questionnaire.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/build-questionnaire.js +1 -1
- package/dist/core/tools/ask-user-question/state/build-questionnaire.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js +2 -4
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/body-residual-spacer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/body-residual-spacer.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/multi-select-view.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.d.ts +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js +1 -3
- package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/submit-picker.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/tab-bar.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/tab-bar.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +1 -1
- package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/dialog-builder.js +1 -1
- package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.js +2 -4
- package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/tab-content-strategy.d.ts +1 -1
- package/dist/core/tools/ask-user-question/view/tab-content-strategy.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/tab-content-strategy.js +2 -9
- package/dist/core/tools/ask-user-question/view/tab-content-strategy.js.map +1 -1
- package/dist/core/tools/bash-async-execution.d.ts.map +1 -1
- package/dist/core/tools/bash-async-execution.js +11 -4
- package/dist/core/tools/bash-async-execution.js.map +1 -1
- package/dist/core/tools/bash-async-jobs.d.ts.map +1 -1
- package/dist/core/tools/bash-async-jobs.js +14 -2
- package/dist/core/tools/bash-async-jobs.js.map +1 -1
- package/dist/core/tools/bash-async-output.d.ts.map +1 -1
- package/dist/core/tools/bash-async-output.js +6 -2
- package/dist/core/tools/bash-async-output.js.map +1 -1
- package/dist/core/tools/bash-interceptor.d.ts.map +1 -1
- package/dist/core/tools/bash-interceptor.js +20 -4
- package/dist/core/tools/bash-interceptor.js.map +1 -1
- package/dist/core/tools/bash-leading-cd.d.ts.map +1 -1
- package/dist/core/tools/bash-leading-cd.js +9 -3
- package/dist/core/tools/bash-leading-cd.js.map +1 -1
- package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
- package/dist/core/tools/bash-pty-native.js.map +1 -1
- package/dist/core/tools/bash-session-environment.d.ts +13 -0
- package/dist/core/tools/bash-session-environment.d.ts.map +1 -0
- package/dist/core/tools/bash-session-environment.js +47 -0
- package/dist/core/tools/bash-session-environment.js.map +1 -0
- package/dist/core/tools/bash.d.ts +4 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +134 -30
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/block-resolver.d.ts.map +1 -1
- package/dist/core/tools/block-resolver.js.map +1 -1
- package/dist/core/tools/conflict-registry.d.ts.map +1 -1
- package/dist/core/tools/conflict-registry.js.map +1 -1
- package/dist/core/tools/directory-tree.d.ts.map +1 -1
- package/dist/core/tools/directory-tree.js +20 -4
- package/dist/core/tools/directory-tree.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +37 -13
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/fetch-url.d.ts.map +1 -1
- package/dist/core/tools/fetch-url.js +127 -41
- package/dist/core/tools/fetch-url.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +176 -46
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +43 -10
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/hashline-engine/apply.d.ts.map +1 -1
- package/dist/core/tools/hashline-engine/apply.js +6 -6
- package/dist/core/tools/hashline-engine/apply.js.map +1 -1
- package/dist/core/tools/hashline-engine/block.js +1 -1
- package/dist/core/tools/hashline-engine/block.js.map +1 -1
- package/dist/core/tools/hashline-engine/format.d.ts.map +1 -1
- package/dist/core/tools/hashline-engine/format.js +1 -1
- package/dist/core/tools/hashline-engine/format.js.map +1 -1
- package/dist/core/tools/hashline-engine/input.d.ts.map +1 -1
- package/dist/core/tools/hashline-engine/input.js +7 -7
- package/dist/core/tools/hashline-engine/input.js.map +1 -1
- package/dist/core/tools/hashline-engine/parser.js +1 -1
- package/dist/core/tools/hashline-engine/parser.js.map +1 -1
- package/dist/core/tools/hashline-engine/patcher.js +5 -5
- package/dist/core/tools/hashline-engine/patcher.js.map +1 -1
- package/dist/core/tools/hashline-engine/prefixes.js +4 -4
- package/dist/core/tools/hashline-engine/prefixes.js.map +1 -1
- package/dist/core/tools/hashline-engine/recovery.d.ts.map +1 -1
- package/dist/core/tools/hashline-engine/recovery.js +4 -2
- package/dist/core/tools/hashline-engine/recovery.js.map +1 -1
- package/dist/core/tools/hashline-engine/snapshots.js +5 -5
- package/dist/core/tools/hashline-engine/snapshots.js.map +1 -1
- package/dist/core/tools/hashline.d.ts.map +1 -1
- package/dist/core/tools/hashline.js +24 -13
- package/dist/core/tools/hashline.js.map +1 -1
- package/dist/core/tools/index.d.ts +6 -6
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +6 -6
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +2 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/notebook.d.ts.map +1 -1
- package/dist/core/tools/notebook.js +13 -3
- package/dist/core/tools/notebook.js.map +1 -1
- package/dist/core/tools/read-document-extract.d.ts.map +1 -1
- package/dist/core/tools/read-document-extract.js +30 -10
- package/dist/core/tools/read-document-extract.js.map +1 -1
- package/dist/core/tools/read-selectors.d.ts.map +1 -1
- package/dist/core/tools/read-selectors.js +59 -15
- package/dist/core/tools/read-selectors.js.map +1 -1
- package/dist/core/tools/read-url.d.ts.map +1 -1
- package/dist/core/tools/read-url.js +41 -7
- package/dist/core/tools/read-url.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +307 -72
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/resource-selectors.d.ts +4 -0
- package/dist/core/tools/resource-selectors.d.ts.map +1 -1
- package/dist/core/tools/resource-selectors.js +324 -84
- package/dist/core/tools/resource-selectors.js.map +1 -1
- package/dist/core/tools/search-details.d.ts.map +1 -1
- package/dist/core/tools/search-details.js +13 -1
- package/dist/core/tools/search-details.js.map +1 -1
- package/dist/core/tools/search-line-ranges.d.ts.map +1 -1
- package/dist/core/tools/search-line-ranges.js +5 -2
- package/dist/core/tools/search-line-ranges.js.map +1 -1
- package/dist/core/tools/search-native.d.ts.map +1 -1
- package/dist/core/tools/search-native.js.map +1 -1
- package/dist/core/tools/search.d.ts.map +1 -1
- package/dist/core/tools/search.js +244 -90
- package/dist/core/tools/search.js.map +1 -1
- package/dist/core/tools/structured-output.d.ts +1 -1
- package/dist/core/tools/structured-output.d.ts.map +1 -1
- package/dist/core/tools/structured-output.js.map +1 -1
- package/dist/core/tools/todos-execute.d.ts.map +1 -1
- package/dist/core/tools/todos-execute.js +4 -4
- package/dist/core/tools/todos-execute.js.map +1 -1
- package/dist/core/tools/todos-locks.d.ts.map +1 -1
- package/dist/core/tools/todos-locks.js.map +1 -1
- package/dist/core/tools/todos-mutations.js +1 -1
- package/dist/core/tools/todos-mutations.js.map +1 -1
- package/dist/core/tools/todos-paths.js +2 -2
- package/dist/core/tools/todos-paths.js.map +1 -1
- package/dist/core/tools/todos-render.d.ts.map +1 -1
- package/dist/core/tools/todos-render.js +2 -2
- package/dist/core/tools/todos-render.js.map +1 -1
- package/dist/core/tools/todos-storage.d.ts.map +1 -1
- package/dist/core/tools/todos-storage.js +1 -2
- package/dist/core/tools/todos-storage.js.map +1 -1
- package/dist/core/tools/todos-types.d.ts.map +1 -1
- package/dist/core/tools/todos-types.js +1 -11
- package/dist/core/tools/todos-types.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts +3 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +9 -5
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/url-ip-guards.d.ts.map +1 -1
- package/dist/core/tools/url-ip-guards.js +27 -17
- package/dist/core/tools/url-ip-guards.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +88 -29
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/usage-totals.d.ts +18 -0
- package/dist/core/usage-totals.d.ts.map +1 -0
- package/dist/core/usage-totals.js +43 -0
- package/dist/core/usage-totals.js.map +1 -0
- package/dist/core/workflow-stage-admission.d.ts.map +1 -1
- package/dist/core/workflow-stage-admission.js +8 -2
- package/dist/core/workflow-stage-admission.js.map +1 -1
- package/dist/extensions/index.d.ts +3 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +5 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/llama/client.d.ts +61 -0
- package/dist/extensions/llama/client.d.ts.map +1 -0
- package/dist/extensions/llama/client.js +300 -0
- package/dist/extensions/llama/client.js.map +1 -0
- package/dist/extensions/llama/huggingface-ui.d.ts +33 -0
- package/dist/extensions/llama/huggingface-ui.d.ts.map +1 -0
- package/dist/extensions/llama/huggingface-ui.js +166 -0
- package/dist/extensions/llama/huggingface-ui.js.map +1 -0
- package/dist/extensions/llama/huggingface.d.ts +23 -0
- package/dist/extensions/llama/huggingface.d.ts.map +1 -0
- package/dist/extensions/llama/huggingface.js +139 -0
- package/dist/extensions/llama/huggingface.js.map +1 -0
- package/dist/extensions/llama/index.d.ts +3 -0
- package/dist/extensions/llama/index.d.ts.map +1 -0
- package/dist/extensions/llama/index.js +208 -0
- package/dist/extensions/llama/index.js.map +1 -0
- package/dist/extensions/llama/provider.d.ts +10 -0
- package/dist/extensions/llama/provider.d.ts.map +1 -0
- package/dist/extensions/llama/provider.js +107 -0
- package/dist/extensions/llama/provider.js.map +1 -0
- package/dist/extensions/llama/ui.d.ts +42 -0
- package/dist/extensions/llama/ui.d.ts.map +1 -0
- package/dist/extensions/llama/ui.js +237 -0
- package/dist/extensions/llama/ui.js.map +1 -0
- package/dist/index-extensions.d.ts +2 -2
- package/dist/index-extensions.d.ts.map +1 -1
- package/dist/index-extensions.js +1 -1
- package/dist/index-extensions.js.map +1 -1
- package/dist/index.d.ts +22 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -27
- package/dist/index.js.map +1 -1
- package/dist/main-app-mode.d.ts +1 -0
- package/dist/main-app-mode.d.ts.map +1 -1
- package/dist/main-app-mode.js +3 -0
- package/dist/main-app-mode.js.map +1 -1
- package/dist/main-deferred-startup.d.ts +1 -1
- package/dist/main-deferred-startup.d.ts.map +1 -1
- package/dist/main-deferred-startup.js +16 -15
- package/dist/main-deferred-startup.js.map +1 -1
- package/dist/main-early-input.js.map +1 -1
- package/dist/main-first-time-setup.d.ts +6 -0
- package/dist/main-first-time-setup.d.ts.map +1 -0
- package/dist/main-first-time-setup.js +10 -0
- package/dist/main-first-time-setup.js.map +1 -0
- package/dist/main-runtime-api-key.d.ts +6 -0
- package/dist/main-runtime-api-key.d.ts.map +1 -0
- package/dist/main-runtime-api-key.js +6 -0
- package/dist/main-runtime-api-key.js.map +1 -0
- package/dist/main-session-options.d.ts +2 -2
- package/dist/main-session-options.d.ts.map +1 -1
- package/dist/main-session-options.js +3 -7
- package/dist/main-session-options.js.map +1 -1
- package/dist/main-session.d.ts.map +1 -1
- package/dist/main-session.js +5 -4
- package/dist/main-session.js.map +1 -1
- package/dist/main-types.d.ts +2 -2
- package/dist/main-types.d.ts.map +1 -1
- package/dist/main-types.js.map +1 -1
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +173 -69
- package/dist/main.js.map +1 -1
- package/dist/migrations-config-values.d.ts.map +1 -1
- package/dist/migrations-config-values.js +3 -1
- package/dist/migrations-config-values.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js.map +1 -1
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/chat-input-actions.d.ts +1 -1
- package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -1
- package/dist/modes/interactive/chat-input-actions.js +30 -103
- package/dist/modes/interactive/chat-input-actions.js.map +1 -1
- package/dist/modes/interactive/components/atomic-banner.d.ts +9 -8
- package/dist/modes/interactive/components/atomic-banner.d.ts.map +1 -1
- package/dist/modes/interactive/components/atomic-banner.js +68 -22
- package/dist/modes/interactive/components/atomic-banner.js.map +1 -1
- package/dist/modes/interactive/components/atomic-working-status.d.ts +52 -0
- package/dist/modes/interactive/components/atomic-working-status.d.ts.map +1 -0
- package/dist/modes/interactive/components/atomic-working-status.js +215 -0
- package/dist/modes/interactive/components/atomic-working-status.js.map +1 -0
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js +7 -6
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-actions.d.ts +3 -2
- package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-actions.js +93 -17
- package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-editor.d.ts +4 -4
- package/dist/modes/interactive/components/chat-session-host-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-editor.js +12 -7
- package/dist/modes/interactive/components/chat-session-host-editor.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.js +84 -15
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.d.ts +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.js +49 -14
- package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +36 -2
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.js +129 -25
- package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.d.ts +11 -2
- package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.js +5 -0
- package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-types.d.ts +19 -2
- package/dist/modes/interactive/components/chat-session-host-types.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-types.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.d.ts +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-utils.js +3 -8
- package/dist/modes/interactive/components/chat-session-host-utils.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts +17 -4
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +26 -3
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.d.ts +1 -1
- package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.js +5 -12
- package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
- package/dist/modes/interactive/components/compaction-boundary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-boundary-message.js +20 -8
- package/dist/modes/interactive/components/compaction-boundary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector-list.d.ts +6 -6
- package/dist/modes/interactive/components/config-selector-list.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector-list.js +66 -125
- package/dist/modes/interactive/components/config-selector-list.js.map +1 -1
- package/dist/modes/interactive/components/config-selector-project-scope.d.ts +4 -0
- package/dist/modes/interactive/components/config-selector-project-scope.d.ts.map +1 -0
- package/dist/modes/interactive/components/config-selector-project-scope.js +59 -0
- package/dist/modes/interactive/components/config-selector-project-scope.js.map +1 -0
- package/dist/modes/interactive/components/config-selector.d.ts +8 -5
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +46 -13
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
- package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +11 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +21 -5
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts +16 -0
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-entry.js +45 -0
- package/dist/modes/interactive/components/custom-entry.js.map +1 -0
- package/dist/modes/interactive/components/custom-message.d.ts +3 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +10 -5
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/diff.js +2 -2
- package/dist/modes/interactive/components/diff.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +3 -2
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +14 -43
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +24 -0
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -0
- package/dist/modes/interactive/components/first-time-setup.js +80 -0
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +7 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +68 -39
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/host-input-form-mount.d.ts.map +1 -1
- package/dist/modes/interactive/components/host-input-form-mount.js +4 -2
- package/dist/modes/interactive/components/host-input-form-mount.js.map +1 -1
- package/dist/modes/interactive/components/host-input-form.d.ts +2 -0
- package/dist/modes/interactive/components/host-input-form.d.ts.map +1 -1
- package/dist/modes/interactive/components/host-input-form.js +61 -24
- package/dist/modes/interactive/components/host-input-form.js.map +1 -1
- package/dist/modes/interactive/components/host-session-picker.d.ts.map +1 -1
- package/dist/modes/interactive/components/host-session-picker.js +8 -3
- package/dist/modes/interactive/components/host-session-picker.js.map +1 -1
- package/dist/modes/interactive/components/idle-status.d.ts +8 -0
- package/dist/modes/interactive/components/idle-status.d.ts.map +1 -0
- package/dist/modes/interactive/components/idle-status.js +13 -0
- package/dist/modes/interactive/components/idle-status.js.map +1 -0
- package/dist/modes/interactive/components/index.d.ts +8 -5
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +5 -2
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +5 -5
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +15 -9
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +4 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +70 -72
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +4 -3
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +19 -18
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-list.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector-list.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-handlers.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-handlers.js +6 -0
- package/dist/modes/interactive/components/settings-selector-handlers.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.js +14 -0
- package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-submenus.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-submenus.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-types.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector-types.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-types.js.map +1 -1
- package/dist/modes/interactive/components/startup-identity.d.ts +22 -0
- package/dist/modes/interactive/components/startup-identity.d.ts.map +1 -0
- package/dist/modes/interactive/components/startup-identity.js +58 -0
- package/dist/modes/interactive/components/startup-identity.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector-content.js +0 -6
- package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector-model.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector-model.js +1 -10
- package/dist/modes/interactive/components/tree-selector-model.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector-viewport.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector-viewport.js.map +1 -1
- package/dist/modes/interactive/components/working-status.d.ts +5 -11
- package/dist/modes/interactive/components/working-status.d.ts.map +1 -1
- package/dist/modes/interactive/components/working-status.js +14 -15
- package/dist/modes/interactive/components/working-status.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts +23 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +120 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/interactive-agent-events.js +73 -28
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-auth-login.js +36 -28
- package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
- package/dist/modes/interactive/interactive-auth-routing.d.ts +4 -1
- package/dist/modes/interactive/interactive-auth-routing.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-auth-routing.js +92 -65
- package/dist/modes/interactive/interactive-auth-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-autocomplete.js +24 -14
- package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/dist/modes/interactive/interactive-bash-compact.d.ts +2 -1
- package/dist/modes/interactive/interactive-bash-compact.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-bash-compact.js +26 -8
- package/dist/modes/interactive/interactive-bash-compact.js.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.js +61 -27
- package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-editor-actions.js +9 -13
- package/dist/modes/interactive/interactive-editor-actions.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-context.js +4 -4
- package/dist/modes/interactive/interactive-extension-context.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-custom-ui.js +15 -8
- package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-dialogs.js +51 -25
- package/dist/modes/interactive/interactive-extension-dialogs.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-runtime.js +15 -6
- package/dist/modes/interactive/interactive-extension-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-extension-widgets.js +1 -1
- package/dist/modes/interactive/interactive-extension-widgets.js.map +1 -1
- package/dist/modes/interactive/interactive-global-clear.d.ts +55 -3
- package/dist/modes/interactive/interactive-global-clear.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-global-clear.js +61 -3
- package/dist/modes/interactive/interactive-global-clear.js.map +1 -1
- package/dist/modes/interactive/interactive-hotkeys-debug.js +2 -3
- package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
- package/dist/modes/interactive/interactive-initial-session-binding.d.ts +4 -0
- package/dist/modes/interactive/interactive-initial-session-binding.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-initial-session-binding.js +23 -0
- package/dist/modes/interactive/interactive-initial-session-binding.js.map +1 -0
- package/dist/modes/interactive/interactive-input-handling.d.ts +2 -1
- package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.js +82 -69
- package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
- package/dist/modes/interactive/interactive-key-identity.d.ts +22 -0
- package/dist/modes/interactive/interactive-key-identity.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-key-identity.js +29 -0
- package/dist/modes/interactive/interactive-key-identity.js.map +1 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts +18 -13
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +26 -18
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +20 -22
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js +16 -18
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-helpers.d.ts +3 -2
- package/dist/modes/interactive/interactive-mode-helpers.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-helpers.js +8 -18
- package/dist/modes/interactive/interactive-mode-helpers.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +28 -17
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js +1 -0
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.d.ts +2 -2
- package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-model-catalog-startup.d.ts +12 -0
- package/dist/modes/interactive/interactive-model-catalog-startup.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-model-catalog-startup.js +22 -0
- package/dist/modes/interactive/interactive-model-catalog-startup.js.map +1 -0
- package/dist/modes/interactive/interactive-model-routing.js +41 -111
- package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-onboarding.js +7 -7
- package/dist/modes/interactive/interactive-onboarding.js.map +1 -1
- package/dist/modes/interactive/interactive-pause.d.ts +4 -0
- package/dist/modes/interactive/interactive-pause.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-pause.js +8 -0
- package/dist/modes/interactive/interactive-pause.js.map +1 -0
- package/dist/modes/interactive/interactive-process-lifecycle.js +43 -12
- package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
- package/dist/modes/interactive/interactive-prompt-restore.d.ts +58 -0
- package/dist/modes/interactive/interactive-prompt-restore.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-prompt-restore.js +87 -0
- package/dist/modes/interactive/interactive-prompt-restore.js.map +1 -0
- package/dist/modes/interactive/interactive-prompt-turn.d.ts +2 -0
- package/dist/modes/interactive/interactive-prompt-turn.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-prompt-turn.js +56 -0
- package/dist/modes/interactive/interactive-prompt-turn.js.map +1 -0
- package/dist/modes/interactive/interactive-queueing.js +6 -9
- package/dist/modes/interactive/interactive-queueing.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +128 -48
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/interactive/interactive-resource-disclosure.js +1 -3
- package/dist/modes/interactive/interactive-resource-disclosure.js.map +1 -1
- package/dist/modes/interactive/interactive-resource-paths.js +17 -21
- package/dist/modes/interactive/interactive-resource-paths.js.map +1 -1
- package/dist/modes/interactive/interactive-resource-rendering.js +110 -12
- package/dist/modes/interactive/interactive-resource-rendering.js.map +1 -1
- package/dist/modes/interactive/interactive-selectors.js +13 -5
- package/dist/modes/interactive/interactive-selectors.js.map +1 -1
- package/dist/modes/interactive/interactive-session-routing.js +19 -4
- package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.js +12 -1
- package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-slash-commands.js +23 -4
- package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
- package/dist/modes/interactive/interactive-startup-chat-container.d.ts +20 -0
- package/dist/modes/interactive/interactive-startup-chat-container.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-startup-chat-container.js +26 -0
- package/dist/modes/interactive/interactive-startup-chat-container.js.map +1 -0
- package/dist/modes/interactive/interactive-startup.d.ts +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +37 -37
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-submission.d.ts +24 -0
- package/dist/modes/interactive/interactive-submission.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-submission.js +10 -0
- package/dist/modes/interactive/interactive-submission.js.map +1 -0
- package/dist/modes/interactive/interactive-summarization-retry-events.d.ts +8 -0
- package/dist/modes/interactive/interactive-summarization-retry-events.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-summarization-retry-events.js +48 -0
- package/dist/modes/interactive/interactive-summarization-retry-events.js.map +1 -0
- package/dist/modes/interactive/login-provider-options.d.ts +21 -0
- package/dist/modes/interactive/login-provider-options.d.ts.map +1 -0
- package/dist/modes/interactive/login-provider-options.js +50 -0
- package/dist/modes/interactive/login-provider-options.js.map +1 -0
- package/dist/modes/interactive/theme/catppuccin-frappe.json +1 -1
- package/dist/modes/interactive/theme/catppuccin-latte.json +1 -1
- package/dist/modes/interactive/theme/catppuccin-macchiato.json +1 -1
- package/dist/modes/interactive/theme/catppuccin-mocha.json +9 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/global-theme.d.ts +1 -1
- package/dist/modes/interactive/theme/global-theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/global-theme.js.map +1 -1
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/modes/interactive/theme/theme-class.d.ts +4 -0
- package/dist/modes/interactive/theme/theme-class.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-class.js +7 -0
- package/dist/modes/interactive/theme/theme-class.js.map +1 -1
- package/dist/modes/interactive/theme/theme-loading.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-loading.js +5 -1
- package/dist/modes/interactive/theme/theme-loading.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.d.ts +32 -0
- package/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.js +8 -0
- package/dist/modes/interactive/theme/theme-schema.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +15 -2
- package/dist/modes/interactive/theme/theme.d.ts +2 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +2 -2
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/whimsical-messages.d.ts +1 -0
- package/dist/modes/interactive/whimsical-messages.d.ts.map +1 -1
- package/dist/modes/interactive/whimsical-messages.js +2 -2
- package/dist/modes/interactive/whimsical-messages.js.map +1 -1
- package/dist/modes/interactive-engine/activity-watchdog.d.ts +16 -6
- package/dist/modes/interactive-engine/activity-watchdog.d.ts.map +1 -1
- package/dist/modes/interactive-engine/activity-watchdog.js +12 -6
- package/dist/modes/interactive-engine/activity-watchdog.js.map +1 -1
- package/dist/modes/interactive-engine/create-isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/create-isolated-runtime.js +8 -5
- package/dist/modes/interactive-engine/create-isolated-runtime.js.map +1 -1
- package/dist/modes/interactive-engine/engine-args.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-args.js +0 -1
- package/dist/modes/interactive-engine/engine-args.js.map +1 -1
- package/dist/modes/interactive-engine/engine-child-liveness.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-child-liveness.js +6 -4
- package/dist/modes/interactive-engine/engine-child-liveness.js.map +1 -1
- package/dist/modes/interactive-engine/engine-custom-ui.d.ts +3 -2
- package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-custom-ui.js +37 -28
- package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
- package/dist/modes/interactive-engine/engine-diagnostic-view.d.ts +16 -0
- package/dist/modes/interactive-engine/engine-diagnostic-view.d.ts.map +1 -0
- package/dist/modes/interactive-engine/engine-diagnostic-view.js +19 -0
- package/dist/modes/interactive-engine/engine-diagnostic-view.js.map +1 -0
- package/dist/modes/interactive-engine/engine-dialog-host.d.ts +33 -0
- package/dist/modes/interactive-engine/engine-dialog-host.d.ts.map +1 -0
- package/dist/modes/interactive-engine/engine-dialog-host.js +129 -0
- package/dist/modes/interactive-engine/engine-dialog-host.js.map +1 -0
- package/dist/modes/interactive-engine/engine-generation.d.ts +29 -0
- package/dist/modes/interactive-engine/engine-generation.d.ts.map +1 -0
- package/dist/modes/interactive-engine/engine-generation.js +11 -0
- package/dist/modes/interactive-engine/engine-generation.js.map +1 -0
- package/dist/modes/interactive-engine/engine-health.d.ts +107 -0
- package/dist/modes/interactive-engine/engine-health.d.ts.map +1 -0
- package/dist/modes/interactive-engine/engine-health.js +250 -0
- package/dist/modes/interactive-engine/engine-health.js.map +1 -0
- package/dist/modes/interactive-engine/engine-input-form.d.ts +1 -1
- package/dist/modes/interactive-engine/engine-input-form.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-input-form.js +21 -4
- package/dist/modes/interactive-engine/engine-input-form.js.map +1 -1
- package/dist/modes/interactive-engine/engine-monitor.d.ts +8 -1
- package/dist/modes/interactive-engine/engine-monitor.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-monitor.js +19 -16
- package/dist/modes/interactive-engine/engine-monitor.js.map +1 -1
- package/dist/modes/interactive-engine/engine-render-service.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-render-service.js +19 -24
- package/dist/modes/interactive-engine/engine-render-service.js.map +1 -1
- package/dist/modes/interactive-engine/engine-session-picker.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-session-picker.js +4 -2
- package/dist/modes/interactive-engine/engine-session-picker.js.map +1 -1
- package/dist/modes/interactive-engine/extension-ui-bridge.d.ts +18 -3
- package/dist/modes/interactive-engine/extension-ui-bridge.d.ts.map +1 -1
- package/dist/modes/interactive-engine/extension-ui-bridge.js +41 -45
- package/dist/modes/interactive-engine/extension-ui-bridge.js.map +1 -1
- package/dist/modes/interactive-engine/input-form-host.d.ts +1 -0
- package/dist/modes/interactive-engine/input-form-host.d.ts.map +1 -1
- package/dist/modes/interactive-engine/input-form-host.js +18 -3
- package/dist/modes/interactive-engine/input-form-host.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-auth.d.ts +10 -0
- package/dist/modes/interactive-engine/isolated-auth.d.ts.map +1 -0
- package/dist/modes/interactive-engine/isolated-auth.js +15 -0
- package/dist/modes/interactive-engine/isolated-auth.js.map +1 -0
- package/dist/modes/interactive-engine/isolated-runtime.d.ts +44 -11
- package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +173 -106
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/interactive-engine/protocol.d.ts +26 -2
- package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
- package/dist/modes/interactive-engine/protocol.js +192 -63
- package/dist/modes/interactive-engine/protocol.js.map +1 -1
- package/dist/modes/interactive-engine/remote-component.d.ts +38 -0
- package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
- package/dist/modes/interactive-engine/remote-component.js +111 -22
- package/dist/modes/interactive-engine/remote-component.js.map +1 -1
- package/dist/modes/interactive-engine/remote-input-ownership.d.ts +48 -0
- package/dist/modes/interactive-engine/remote-input-ownership.d.ts.map +1 -0
- package/dist/modes/interactive-engine/remote-input-ownership.js +42 -0
- package/dist/modes/interactive-engine/remote-input-ownership.js.map +1 -0
- package/dist/modes/interactive-engine/remote-model-catalog.d.ts +17 -0
- package/dist/modes/interactive-engine/remote-model-catalog.d.ts.map +1 -0
- package/dist/modes/interactive-engine/remote-model-catalog.js +80 -0
- package/dist/modes/interactive-engine/remote-model-catalog.js.map +1 -0
- package/dist/modes/interactive-engine/remote-queue-pause.d.ts +13 -0
- package/dist/modes/interactive-engine/remote-queue-pause.d.ts.map +1 -0
- package/dist/modes/interactive-engine/remote-queue-pause.js +38 -0
- package/dist/modes/interactive-engine/remote-queue-pause.js.map +1 -0
- package/dist/modes/interactive-engine/remote-renderer.d.ts +3 -1
- package/dist/modes/interactive-engine/remote-renderer.d.ts.map +1 -1
- package/dist/modes/interactive-engine/remote-renderer.js +41 -10
- package/dist/modes/interactive-engine/remote-renderer.js.map +1 -1
- package/dist/modes/interactive-engine/session-picker-host.d.ts +1 -0
- package/dist/modes/interactive-engine/session-picker-host.d.ts.map +1 -1
- package/dist/modes/interactive-engine/session-picker-host.js +4 -0
- package/dist/modes/interactive-engine/session-picker-host.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +5 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/jsonl.d.ts +9 -7
- package/dist/modes/rpc/jsonl.d.ts.map +1 -1
- package/dist/modes/rpc/jsonl.js +23 -24
- package/dist/modes/rpc/jsonl.js.map +1 -1
- package/dist/modes/rpc/queued-writer.d.ts +33 -0
- package/dist/modes/rpc/queued-writer.d.ts.map +1 -0
- package/dist/modes/rpc/queued-writer.js +104 -0
- package/dist/modes/rpc/queued-writer.js.map +1 -0
- package/dist/modes/rpc/rpc-bash-request-owners.d.ts +24 -0
- package/dist/modes/rpc/rpc-bash-request-owners.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-bash-request-owners.js +45 -0
- package/dist/modes/rpc/rpc-bash-request-owners.js.map +1 -0
- package/dist/modes/rpc/rpc-client-api.d.ts +17 -4
- package/dist/modes/rpc/rpc-client-api.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-api.js +92 -25
- package/dist/modes/rpc/rpc-client-api.js.map +1 -1
- package/dist/modes/rpc/rpc-client-process.d.ts +11 -4
- package/dist/modes/rpc/rpc-client-process.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-process.js +77 -29
- package/dist/modes/rpc/rpc-client-process.js.map +1 -1
- package/dist/modes/rpc/rpc-client-waits.d.ts +17 -1
- package/dist/modes/rpc/rpc-client-waits.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-waits.js +25 -0
- package/dist/modes/rpc/rpc-client-waits.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +68 -9
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +205 -158
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.d.ts +10 -3
- package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.js +165 -43
- package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
- package/dist/modes/rpc/rpc-command-timeouts.d.ts +30 -0
- package/dist/modes/rpc/rpc-command-timeouts.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-command-timeouts.js +52 -0
- package/dist/modes/rpc/rpc-command-timeouts.js.map +1 -0
- package/dist/modes/rpc/rpc-event-buffer.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-event-buffer.js +3 -1
- package/dist/modes/rpc/rpc-event-buffer.js.map +1 -1
- package/dist/modes/rpc/rpc-extension-ui.d.ts +4 -2
- package/dist/modes/rpc/rpc-extension-ui.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-extension-ui.js +50 -17
- package/dist/modes/rpc/rpc-extension-ui.js.map +1 -1
- package/dist/modes/rpc/rpc-generation-buffer.d.ts +21 -0
- package/dist/modes/rpc/rpc-generation-buffer.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-generation-buffer.js +35 -0
- package/dist/modes/rpc/rpc-generation-buffer.js.map +1 -0
- package/dist/modes/rpc/rpc-input-scheduler.d.ts +3 -3
- package/dist/modes/rpc/rpc-input-scheduler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.js +19 -8
- package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
- package/dist/modes/rpc/rpc-input.d.ts +7 -1
- package/dist/modes/rpc/rpc-input.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input.js +14 -4
- package/dist/modes/rpc/rpc-input.js.map +1 -1
- package/dist/modes/rpc/rpc-keybindings-reload.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-keybindings-reload.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +23 -23
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-model-fallback-prompt.d.ts +4 -0
- package/dist/modes/rpc/rpc-model-fallback-prompt.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-model-fallback-prompt.js +11 -0
- package/dist/modes/rpc/rpc-model-fallback-prompt.js.map +1 -0
- package/dist/modes/rpc/rpc-oauth-client.d.ts +21 -0
- package/dist/modes/rpc/rpc-oauth-client.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-oauth-client.js +81 -0
- package/dist/modes/rpc/rpc-oauth-client.js.map +1 -0
- package/dist/modes/rpc/rpc-oauth-interaction.d.ts +9 -0
- package/dist/modes/rpc/rpc-oauth-interaction.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-oauth-interaction.js +49 -0
- package/dist/modes/rpc/rpc-oauth-interaction.js.map +1 -0
- package/dist/modes/rpc/rpc-output-buffer.d.ts +2 -1
- package/dist/modes/rpc/rpc-output-buffer.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-output-buffer.js +7 -40
- package/dist/modes/rpc/rpc-output-buffer.js.map +1 -1
- package/dist/modes/rpc/rpc-pending-requests.d.ts +37 -0
- package/dist/modes/rpc/rpc-pending-requests.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-pending-requests.js +57 -0
- package/dist/modes/rpc/rpc-pending-requests.js.map +1 -0
- package/dist/modes/rpc/rpc-provider-auth.d.ts +22 -0
- package/dist/modes/rpc/rpc-provider-auth.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-provider-auth.js +117 -0
- package/dist/modes/rpc/rpc-provider-auth.js.map +1 -0
- package/dist/modes/rpc/rpc-responses.d.ts +0 -1
- package/dist/modes/rpc/rpc-responses.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-responses.js +0 -14
- package/dist/modes/rpc/rpc-responses.js.map +1 -1
- package/dist/modes/rpc/rpc-session-binding.d.ts +2 -1
- package/dist/modes/rpc/rpc-session-binding.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-session-binding.js +71 -43
- package/dist/modes/rpc/rpc-session-binding.js.map +1 -1
- package/dist/modes/rpc/rpc-terminal-drain.d.ts +52 -0
- package/dist/modes/rpc/rpc-terminal-drain.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-terminal-drain.js +85 -0
- package/dist/modes/rpc/rpc-terminal-drain.js.map +1 -0
- package/dist/modes/rpc/rpc-transport-error.d.ts +49 -0
- package/dist/modes/rpc/rpc-transport-error.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-transport-error.js +78 -0
- package/dist/modes/rpc/rpc-transport-error.js.map +1 -0
- package/dist/modes/rpc/rpc-types.d.ts +188 -25
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli-parser.js.map +1 -1
- package/dist/package-manager-cli.d.ts +3 -3
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +66 -53
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/rpc-entry.js +6 -2
- package/dist/rpc-entry.js.map +1 -1
- package/dist/self-update-plan.d.ts +12 -0
- package/dist/self-update-plan.d.ts.map +1 -0
- package/dist/self-update-plan.js +42 -0
- package/dist/self-update-plan.js.map +1 -0
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +3 -0
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +31 -8
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/compile-cache.d.ts +8 -0
- package/dist/utils/compile-cache.d.ts.map +1 -0
- package/dist/utils/compile-cache.js +31 -0
- package/dist/utils/compile-cache.js.map +1 -0
- package/dist/utils/fs-watch.d.ts.map +1 -1
- package/dist/utils/fs-watch.js +1 -3
- package/dist/utils/fs-watch.js.map +1 -1
- package/dist/utils/git.js +1 -1
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/interactive-engine-bootstrap.d.ts +67 -0
- package/dist/utils/interactive-engine-bootstrap.d.ts.map +1 -0
- package/dist/utils/interactive-engine-bootstrap.js +117 -0
- package/dist/utils/interactive-engine-bootstrap.js.map +1 -0
- package/dist/utils/interactive-engine-env.d.ts +41 -0
- package/dist/utils/interactive-engine-env.d.ts.map +1 -0
- package/dist/utils/interactive-engine-env.js +67 -0
- package/dist/utils/interactive-engine-env.js.map +1 -0
- package/dist/utils/markit.d.ts.map +1 -1
- package/dist/utils/markit.js +15 -5
- package/dist/utils/markit.js.map +1 -1
- package/dist/utils/shell.js +2 -2
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/split-launcher.d.ts.map +1 -1
- package/dist/utils/split-launcher.js +1 -2
- package/dist/utils/split-launcher.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +8 -34
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +1 -0
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +7 -5
- package/dist/utils/version-check.js.map +1 -1
- package/docs/changelog.mdx +24 -2
- package/docs/compaction.md +101 -18
- package/docs/custom-provider.md +40 -8
- package/docs/development.md +14 -15
- package/docs/docs.json +12 -2
- package/docs/environment-variables.md +37 -0
- package/docs/extensions.md +166 -18
- package/docs/index.md +1 -0
- package/docs/intercom.md +1 -1
- package/docs/json.md +11 -5
- package/docs/keybindings.md +19 -2
- package/docs/llama-cpp.md +72 -0
- package/docs/models/artificial-analysis-index.md +82 -0
- package/docs/models/model-selection.md +63 -0
- package/docs/models/pareto-efficiency.md +64 -0
- package/docs/models.md +41 -50
- package/docs/packages.md +4 -2
- package/docs/prompt-templates.md +1 -0
- package/docs/providers.md +89 -23
- package/docs/quickstart.md +21 -17
- package/docs/rpc.md +97 -68
- package/docs/sdk.md +68 -71
- package/docs/security.md +15 -0
- package/docs/session-format.md +6 -15
- package/docs/sessions.md +1 -1
- package/docs/settings.md +29 -18
- package/docs/skills.md +8 -0
- package/docs/subagents.md +24 -8
- package/docs/themes.md +10 -1
- package/docs/tui.md +14 -7
- package/docs/usage.md +71 -2
- package/docs/windows.md +1 -1
- package/docs/workflows.md +1253 -534
- package/examples/extensions/README.md +2 -2
- package/examples/extensions/border-status-editor.ts +1 -6
- package/examples/extensions/custom-compaction.ts +7 -2
- package/examples/extensions/custom-footer.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/index.ts +14 -2
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +1 -1
- package/examples/extensions/gondolin/index.ts +1 -1
- package/examples/extensions/handoff.ts +9 -2
- package/examples/extensions/hello.ts +1 -1
- package/examples/extensions/message-renderer.ts +2 -2
- package/examples/extensions/minimal-mode.ts +6 -6
- package/examples/extensions/overlay-qa-focus-components.ts +0 -1
- package/examples/extensions/overlay-qa-streaming-input-components.ts +2 -2
- package/examples/extensions/overlay-qa-tests.ts +16 -14
- package/examples/extensions/overlay-qa-toggle-passive-components.ts +3 -5
- package/examples/extensions/overlay-test.ts +3 -6
- package/examples/extensions/plan-mode/index.ts +1 -1
- package/examples/extensions/preset.ts +1 -1
- package/examples/extensions/qna.ts +1 -1
- package/examples/extensions/sandbox/package-lock.json +3 -3
- package/examples/extensions/structured-output.ts +9 -9
- package/examples/extensions/subagent/display.ts +115 -127
- package/examples/extensions/subagent/index.ts +310 -349
- package/examples/extensions/subagent/render.ts +244 -345
- package/examples/extensions/subagent/runner.ts +223 -230
- package/examples/extensions/subagent/schemas.ts +39 -46
- package/examples/extensions/subagent/types.ts +22 -22
- package/examples/extensions/summarize.ts +4 -1
- package/examples/extensions/tic-tac-toe.ts +4 -4
- package/examples/extensions/todo.ts +1 -1
- package/examples/extensions/tool-override.ts +1 -1
- package/examples/extensions/trigger-compact.ts +4 -1
- package/examples/extensions/working-indicator.ts +3 -2
- package/examples/rpc-extension-ui-components.ts +1 -2
- package/examples/sdk/02-custom-model.ts +8 -10
- package/examples/sdk/03-custom-prompt.ts +1 -6
- package/examples/sdk/06-extensions.ts +1 -6
- package/examples/sdk/07-context-files.ts +1 -6
- package/examples/sdk/09-api-keys-and-oauth.ts +17 -23
- package/examples/sdk/12-full-control.ts +10 -12
- package/examples/sdk/README.md +17 -21
- package/npm-shrinkwrap.json +305 -314
- package/package.json +17 -15
- package/dist/builtin/cursor/CHANGELOG.md +0 -275
- package/dist/builtin/cursor/LICENSE +0 -26
- package/dist/builtin/cursor/README.md +0 -24
- package/dist/builtin/cursor/index.ts +0 -9
- package/dist/builtin/cursor/package.json +0 -47
- package/dist/builtin/cursor/src/auth.ts +0 -352
- package/dist/builtin/cursor/src/catalog-cache.ts +0 -155
- package/dist/builtin/cursor/src/config.ts +0 -123
- package/dist/builtin/cursor/src/conversation-state.ts +0 -135
- package/dist/builtin/cursor/src/cursor-models-raw.json +0 -412
- package/dist/builtin/cursor/src/model-mapper.ts +0 -369
- package/dist/builtin/cursor/src/model-reference.ts +0 -282
- package/dist/builtin/cursor/src/models.ts +0 -54
- package/dist/builtin/cursor/src/native-loader.ts +0 -71
- package/dist/builtin/cursor/src/proto/README.md +0 -34
- package/dist/builtin/cursor/src/proto/agent_pb.ts +0 -15294
- package/dist/builtin/cursor/src/proto/protobuf-codec-base64.ts +0 -22
- package/dist/builtin/cursor/src/proto/protobuf-codec-json.ts +0 -44
- package/dist/builtin/cursor/src/proto/protobuf-codec-request.ts +0 -311
- package/dist/builtin/cursor/src/proto/protobuf-codec-wire.ts +0 -248
- package/dist/builtin/cursor/src/proto/protobuf-codec.ts +0 -200
- package/dist/builtin/cursor/src/provider.ts +0 -301
- package/dist/builtin/cursor/src/stream.ts +0 -494
- package/dist/builtin/cursor/src/transport-errors.ts +0 -74
- package/dist/builtin/cursor/src/transport-frame.ts +0 -56
- package/dist/builtin/cursor/src/transport-http2.ts +0 -122
- package/dist/builtin/cursor/src/transport-native-client.ts +0 -161
- package/dist/builtin/cursor/src/transport-run-stream.ts +0 -188
- package/dist/builtin/cursor/src/transport-timeouts.ts +0 -87
- package/dist/builtin/cursor/src/transport-types.ts +0 -143
- package/dist/builtin/cursor/src/transport.ts +0 -26
- package/dist/builtin/workflows/builtin/deep-research-codebase-runner.ts +0 -492
- package/dist/builtin/workflows/builtin/deep-research-codebase-utils.ts +0 -383
- package/dist/builtin/workflows/builtin/deep-research-codebase.d.ts +0 -35
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +0 -47
- package/dist/builtin/workflows/skills/impeccable/reference/brand.md +0 -108
- package/dist/builtin/workflows/skills/impeccable/reference/codex.md +0 -105
- package/dist/builtin/workflows/skills/impeccable/reference/interaction-design.md +0 -189
- package/dist/builtin/workflows/skills/impeccable/scripts/hook-before-edit.mjs +0 -516
- package/dist/core/context-window.d.ts +0 -54
- package/dist/core/context-window.d.ts.map +0 -1
- package/dist/core/context-window.js +0 -110
- package/dist/core/context-window.js.map +0 -1
- package/dist/core/copilot-anthropic-sse-repair.d.ts +0 -23
- package/dist/core/copilot-anthropic-sse-repair.d.ts.map +0 -1
- package/dist/core/copilot-anthropic-sse-repair.js +0 -340
- package/dist/core/copilot-anthropic-sse-repair.js.map +0 -1
- package/dist/core/copilot-errors.d.ts +0 -9
- package/dist/core/copilot-errors.d.ts.map +0 -1
- package/dist/core/copilot-errors.js +0 -32
- package/dist/core/copilot-errors.js.map +0 -1
- package/dist/core/copilot-gemini-payload-sanitizer.d.ts +0 -72
- package/dist/core/copilot-gemini-payload-sanitizer.d.ts.map +0 -1
- package/dist/core/copilot-gemini-payload-sanitizer.js +0 -296
- package/dist/core/copilot-gemini-payload-sanitizer.js.map +0 -1
- package/dist/core/copilot-gemini-reasoning.d.ts +0 -126
- package/dist/core/copilot-gemini-reasoning.d.ts.map +0 -1
- package/dist/core/copilot-gemini-reasoning.js +0 -265
- package/dist/core/copilot-gemini-reasoning.js.map +0 -1
- package/dist/core/copilot-gemini-tool-arguments.d.ts +0 -42
- package/dist/core/copilot-gemini-tool-arguments.d.ts.map +0 -1
- package/dist/core/copilot-gemini-tool-arguments.js +0 -159
- package/dist/core/copilot-gemini-tool-arguments.js.map +0 -1
- package/dist/core/copilot-hosts.d.ts +0 -12
- package/dist/core/copilot-hosts.d.ts.map +0 -1
- package/dist/core/copilot-hosts.js +0 -33
- package/dist/core/copilot-hosts.js.map +0 -1
- package/dist/core/copilot-model-catalog.d.ts +0 -114
- package/dist/core/copilot-model-catalog.d.ts.map +0 -1
- package/dist/core/copilot-model-catalog.js +0 -392
- package/dist/core/copilot-model-catalog.js.map +0 -1
- package/dist/core/copilot-model-static-fallbacks.d.ts +0 -43
- package/dist/core/copilot-model-static-fallbacks.d.ts.map +0 -1
- package/dist/core/copilot-model-static-fallbacks.js +0 -50
- package/dist/core/copilot-model-static-fallbacks.js.map +0 -1
- package/dist/core/copilot-model-synthesis.d.ts +0 -10
- package/dist/core/copilot-model-synthesis.d.ts.map +0 -1
- package/dist/core/copilot-model-synthesis.js +0 -91
- package/dist/core/copilot-model-synthesis.js.map +0 -1
- package/dist/core/model-registry-auth.d.ts +0 -8
- package/dist/core/model-registry-auth.d.ts.map +0 -1
- package/dist/core/model-registry-auth.js +0 -83
- package/dist/core/model-registry-auth.js.map +0 -1
- package/dist/core/model-registry-builtins.d.ts +0 -10
- package/dist/core/model-registry-builtins.d.ts.map +0 -1
- package/dist/core/model-registry-builtins.js +0 -171
- package/dist/core/model-registry-builtins.js.map +0 -1
- package/dist/core/model-registry-custom-loader.d.ts +0 -3
- package/dist/core/model-registry-custom-loader.d.ts.map +0 -1
- package/dist/core/model-registry-custom-loader.js +0 -254
- package/dist/core/model-registry-custom-loader.js.map +0 -1
- package/dist/core/model-registry-dynamic.d.ts +0 -7
- package/dist/core/model-registry-dynamic.d.ts.map +0 -1
- package/dist/core/model-registry-dynamic.js +0 -201
- package/dist/core/model-registry-dynamic.js.map +0 -1
- package/dist/core/model-registry-loader.d.ts +0 -5
- package/dist/core/model-registry-loader.d.ts.map +0 -1
- package/dist/core/model-registry-loader.js +0 -28
- package/dist/core/model-registry-loader.js.map +0 -1
- package/dist/core/model-registry-schemas.d.ts +0 -1273
- package/dist/core/model-registry-schemas.d.ts.map +0 -1
- package/dist/core/model-registry-schemas.js.map +0 -1
- package/dist/core/model-registry-types.d.ts +0 -82
- package/dist/core/model-registry-types.d.ts.map +0 -1
- package/dist/core/model-registry-types.js +0 -2
- package/dist/core/model-registry-types.js.map +0 -1
- package/dist/core/oauth-compat.d.ts +0 -17
- package/dist/core/oauth-compat.d.ts.map +0 -1
- package/dist/core/oauth-compat.js +0 -2
- package/dist/core/oauth-compat.js.map +0 -1
- package/dist/core/oauth-provider-bridge.d.ts +0 -40
- package/dist/core/oauth-provider-bridge.d.ts.map +0 -1
- package/dist/core/oauth-provider-bridge.js +0 -169
- package/dist/core/oauth-provider-bridge.js.map +0 -1
- package/dist/modes/interactive/components/context-window-selector.d.ts +0 -53
- package/dist/modes/interactive/components/context-window-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/context-window-selector.js +0 -136
- package/dist/modes/interactive/components/context-window-selector.js.map +0 -1
- package/dist/modes/rpc/bounded-writer.d.ts +0 -27
- package/dist/modes/rpc/bounded-writer.d.ts.map +0 -1
- package/dist/modes/rpc/bounded-writer.js +0 -103
- package/dist/modes/rpc/bounded-writer.js.map +0 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BORDER_SAFE_TAGS,
|
|
3
|
+
EM_DASH_CHARS_PER_DASH,
|
|
4
|
+
EM_DASH_FLOOR,
|
|
3
5
|
GENERIC_FONTS,
|
|
4
6
|
KNOWN_SERIF_FONTS,
|
|
5
7
|
OVERUSED_FONTS,
|
|
@@ -25,8 +27,15 @@ const DETECTOR_IS_BROWSER = typeof window !== 'undefined';
|
|
|
25
27
|
|
|
26
28
|
// ─── Section 3: Pure Detection ──────────────────────────────────────────────
|
|
27
29
|
|
|
28
|
-
function checkBorders(tag, widths, colors, radius) {
|
|
29
|
-
|
|
30
|
+
function checkBorders(tag, widths, colors, radius, opts = {}) {
|
|
31
|
+
// Badge-shaped <span>s (own visible background) are a real stripe target
|
|
32
|
+
// for the top/bottom variant — the inline-tag exemption exists to quiet
|
|
33
|
+
// text-level borders, not chips. They skip the left/right arms below.
|
|
34
|
+
const spanBadge = tag === 'span' && !!opts.badgeLike;
|
|
35
|
+
if (BORDER_SAFE_TAGS.has(tag) && !spanBadge) return [];
|
|
36
|
+
// A live status/alert region wears a colored single-edge border as a
|
|
37
|
+
// severity accent (toast, snackbar, callout), not as the side-tab tell.
|
|
38
|
+
if (opts.statusContext) return [];
|
|
30
39
|
const findings = [];
|
|
31
40
|
const sides = ['Top', 'Right', 'Bottom', 'Left'];
|
|
32
41
|
|
|
@@ -42,10 +51,20 @@ function checkBorders(tag, widths, colors, radius) {
|
|
|
42
51
|
const isSide = side === 'Left' || side === 'Right';
|
|
43
52
|
|
|
44
53
|
if (isSide) {
|
|
54
|
+
if (spanBadge) continue;
|
|
45
55
|
if (radius > 0) findings.push({ id: 'side-tab', snippet: `border-${sn}: ${w}px + border-radius: ${radius}px` });
|
|
46
56
|
else if (w >= 3) findings.push({ id: 'side-tab', snippet: `border-${sn}: ${w}px` });
|
|
47
57
|
} else {
|
|
48
58
|
if (radius > 0 && w >= 2) findings.push({ id: 'border-accent-on-rounded', snippet: `border-${sn}: ${w}px + border-radius: ${radius}px` });
|
|
59
|
+
// Horizontal variant of the side-tab stripe: a thick chromatic accent
|
|
60
|
+
// riding the top or bottom edge of a card/badge/container. Same
|
|
61
|
+
// dominant-edge + chroma gates as left/right, 3-12px band. Selected-
|
|
62
|
+
// tab underlines are exempt via opts.tabContext (adapters look for
|
|
63
|
+
// tablist/nav/tab ancestors and aria-selected); links, buttons,
|
|
64
|
+
// table cells, and <hr> never reach here (BORDER_SAFE_TAGS).
|
|
65
|
+
else if (!opts.tabContext && w >= 3 && w <= 12) {
|
|
66
|
+
findings.push({ id: 'side-tab', snippet: `border-${sn}: ${w}px` });
|
|
67
|
+
}
|
|
49
68
|
}
|
|
50
69
|
}
|
|
51
70
|
|
|
@@ -67,23 +86,45 @@ function isEmojiOnlyText(text) {
|
|
|
67
86
|
function checkColors(opts) {
|
|
68
87
|
const { tag, textColor, bgColor, effectiveBg, effectiveBgStops, fontSize, fontWeight, hasDirectText, isEmojiOnly, bgClip, bgImage, classList } = opts;
|
|
69
88
|
if (SAFE_TAGS.has(tag)) {
|
|
70
|
-
// Exception for
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// contrast on its own surface is a real,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
// Exception for elements styled as controls or chips. SAFE_TAGS exists to
|
|
90
|
+
// suppress contrast noise on inline links and unstyled spans, where the
|
|
91
|
+
// element has no own background and the contrast against the ancestor
|
|
92
|
+
// surface is already the intended visual. When the element paints its own
|
|
93
|
+
// opaque background under direct text, it is a styled button, chip, or
|
|
94
|
+
// badge regardless of tag, and contrast on its own surface is a real,
|
|
95
|
+
// frequent bug worth flagging. (The shipped miss: a <span> severity chip
|
|
96
|
+
// whose white text lost a specificity fight and rendered muted-on-red at
|
|
97
|
+
// 1.2:1; the old a/button-only exception never looked at it.) The 9px
|
|
98
|
+
// font floor keeps sub-text decorations out.
|
|
99
|
+
const isStyledControl = hasDirectText
|
|
100
|
+
&& ((bgColor && bgColor.a > 0.5)
|
|
101
|
+
// A gradient painted on the element itself is an own surface the
|
|
102
|
+
// same way a solid background is. Without this branch a nav CTA
|
|
103
|
+
// built as `<a>` with `background: linear-gradient(…)` and a text
|
|
104
|
+
// color that fails against every stop sails through on the
|
|
105
|
+
// SAFE_TAGS suppression (the shipped escape).
|
|
106
|
+
|| (bgImage && /gradient/i.test(bgImage)))
|
|
107
|
+
&& fontSize >= 9;
|
|
108
|
+
if (!isStyledControl) return [];
|
|
80
109
|
}
|
|
81
110
|
const findings = [];
|
|
82
111
|
|
|
83
112
|
if (hasDirectText && textColor && !isEmojiOnly) {
|
|
113
|
+
// Gradient-clipped text (`background-clip: text`, typically with a
|
|
114
|
+
// transparent text-fill) paints its glyphs *with* the element's own
|
|
115
|
+
// gradient. The `color` value the cascade still reports is never painted,
|
|
116
|
+
// and the gradient is the fill, not a backdrop — so measuring `color`
|
|
117
|
+
// against that gradient (which resolveGradientStops picks up as the
|
|
118
|
+
// element's own background-image) is a guaranteed false positive
|
|
119
|
+
// (issue #409 Case A). Skip the backdrop-contrast checks; the gradient-text
|
|
120
|
+
// rule below still flags the pattern itself. Skipping a rule beats a false
|
|
121
|
+
// positive here — the true painted contrast can't be measured from `color`.
|
|
122
|
+
const isGradientClippedText = bgClip === 'text';
|
|
84
123
|
// Run background-dependent checks against either a solid bg or, if the
|
|
85
124
|
// ancestor is a gradient, against every gradient stop (use the worst case).
|
|
86
|
-
const bgs =
|
|
125
|
+
const bgs = isGradientClippedText
|
|
126
|
+
? null
|
|
127
|
+
: (effectiveBg ? [effectiveBg] : (effectiveBgStops && effectiveBgStops.length ? effectiveBgStops : null));
|
|
87
128
|
if (bgs) {
|
|
88
129
|
// Gray on colored background — flag if every stop is chromatic
|
|
89
130
|
const textLum = relativeLuminance(textColor);
|
|
@@ -113,7 +154,11 @@ function checkColors(opts) {
|
|
|
113
154
|
// like `text-paper/60` on `bg-ink` sections are the FP pattern.
|
|
114
155
|
const isAlphaFallbackFP = !DETECTOR_IS_BROWSER && !effectiveBg && (textColor.a != null && textColor.a < 1);
|
|
115
156
|
if (!isAlphaFallbackFP) {
|
|
116
|
-
|
|
157
|
+
// Near-threshold ratios (e.g. 4.497) would round to the threshold
|
|
158
|
+
// itself at one decimal and read as "4.5 needs 4.5" — show two
|
|
159
|
+
// decimals there so the finding stays legible.
|
|
160
|
+
const ratioLabel = ratio.toFixed(1) === threshold.toFixed(1) ? ratio.toFixed(2) : ratio.toFixed(1);
|
|
161
|
+
findings.push({ id: 'low-contrast', snippet: `${ratioLabel}:1 (need ${threshold}:1) — text ${colorToHex(textColor)} on ${colorToHex(bgs[worstIdx])}` });
|
|
117
162
|
}
|
|
118
163
|
}
|
|
119
164
|
}
|
|
@@ -159,6 +204,27 @@ function checkColors(opts) {
|
|
|
159
204
|
return findings;
|
|
160
205
|
}
|
|
161
206
|
|
|
207
|
+
// WCAG contrast for the :hover state of an element whose hover rules change
|
|
208
|
+
// its text color and/or background. The classic miss: a nav CTA whose
|
|
209
|
+
// author-intended hover pair passes AA, but a broader selector (e.g.
|
|
210
|
+
// `.nav-links a:hover`) wins the specificity fight and swaps in a color
|
|
211
|
+
// that fails. Only fires on elements that present as styled controls —
|
|
212
|
+
// direct text plus an opaque-ish own background in either state — so plain
|
|
213
|
+
// inline links keep the same suppression they get in checkColors.
|
|
214
|
+
function checkHoverContrast(opts) {
|
|
215
|
+
const { tag, textColor, bg, ownBgAlpha, fontSize, fontWeight, hasDirectText, isEmojiOnly } = opts;
|
|
216
|
+
if (!hasDirectText || isEmojiOnly || !textColor || !bg) return [];
|
|
217
|
+
if (SAFE_TAGS.has(tag) && !(ownBgAlpha != null && ownBgAlpha > 0.5)) return [];
|
|
218
|
+
const ratio = contrastRatio(textColor, bg);
|
|
219
|
+
const isLargeText = fontSize >= WCAG_LARGE_TEXT_PX || (fontSize >= WCAG_LARGE_BOLD_TEXT_PX && fontWeight >= 700);
|
|
220
|
+
const threshold = isLargeText ? 3.0 : 4.5;
|
|
221
|
+
if (ratio >= threshold) return [];
|
|
222
|
+
return [{
|
|
223
|
+
id: 'low-contrast',
|
|
224
|
+
snippet: `:hover state ${ratio.toFixed(1)}:1 (need ${threshold}:1) — text ${colorToHex(textColor)} on ${colorToHex(bg)}`,
|
|
225
|
+
}];
|
|
226
|
+
}
|
|
227
|
+
|
|
162
228
|
function isCardLikeFromProps(hasShadow, hasBorder, hasRadius, hasBg) {
|
|
163
229
|
if (!hasShadow && !hasBorder) return false;
|
|
164
230
|
return hasRadius || hasBg;
|
|
@@ -304,26 +370,57 @@ function isAccentColor(cssColor) {
|
|
|
304
370
|
return false;
|
|
305
371
|
}
|
|
306
372
|
|
|
373
|
+
function resolveHeroHeadingSizePx(value) {
|
|
374
|
+
const input = String(value || '').trim().toLowerCase();
|
|
375
|
+
if (!input) return 0;
|
|
376
|
+
|
|
377
|
+
const simpleLengthPx = (token) => {
|
|
378
|
+
const match = /^(-?\d*\.?\d+)\s*(px|rem|em|%)?$/.exec(String(token || '').trim());
|
|
379
|
+
if (!match) return null;
|
|
380
|
+
const amount = Number(match[1]);
|
|
381
|
+
if (!Number.isFinite(amount)) return null;
|
|
382
|
+
if (match[2] === 'rem' || match[2] === 'em') return amount * 16;
|
|
383
|
+
if (match[2] === '%') return amount * 0.16;
|
|
384
|
+
return amount;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const direct = simpleLengthPx(input);
|
|
388
|
+
if (direct !== null) return direct;
|
|
389
|
+
|
|
390
|
+
// Static CSS engines cannot resolve viewport units, but clamp's min/max
|
|
391
|
+
// bounds still tell us whether the heading can ever reach hero scale.
|
|
392
|
+
const clamp = /^clamp\((.*)\)$/.exec(input);
|
|
393
|
+
if (clamp) {
|
|
394
|
+
const parts = clamp[1].split(',');
|
|
395
|
+
if (parts.length === 3) {
|
|
396
|
+
const bounds = [simpleLengthPx(parts[0]), simpleLengthPx(parts[2])]
|
|
397
|
+
.filter((candidate) => candidate !== null);
|
|
398
|
+
if (bounds.length > 0) return Math.max(...bounds);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
307
405
|
// Sibling-relationship rule. Anchor on a hero-scale h1, look at the
|
|
308
406
|
// previousElementSibling, and gate on EITHER the classic tracked-
|
|
309
407
|
// uppercase eyebrow OR the modern accent-colored bold eyebrow.
|
|
310
408
|
function checkHeroEyebrow(opts) {
|
|
311
409
|
const {
|
|
312
410
|
headingTag, headingText, headingFontSize,
|
|
411
|
+
headingInApplicationContext,
|
|
313
412
|
siblingTag, siblingText, siblingTextTransform,
|
|
314
413
|
siblingFontSize, siblingLetterSpacing,
|
|
315
414
|
siblingFontWeight, siblingColor,
|
|
415
|
+
siblingHasAccentDashPseudo,
|
|
316
416
|
} = opts;
|
|
317
417
|
if (headingTag !== 'h1') return [];
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
// tracked-caps) are tight enough to avoid false positives on non-
|
|
325
|
-
// hero h1s — a tiny tan label directly above any h1 is the
|
|
326
|
-
// antipattern regardless of how big the h1 ends up.
|
|
418
|
+
// This is specifically a marketing-hero cliché, not a ban on compact
|
|
419
|
+
// context labels in product UI (for example, a station name inside a tab
|
|
420
|
+
// panel). Browser-computed sizes are reliable; the static adapter also
|
|
421
|
+
// resolves ordinary px/rem/em and clamp() bounds before reaching here.
|
|
422
|
+
if (headingInApplicationContext) return [];
|
|
423
|
+
if (!(headingFontSize >= 48)) return [];
|
|
327
424
|
if (!siblingTag) return [];
|
|
328
425
|
// An h2 above an h1 is a different anti-pattern (heading hierarchy / dual
|
|
329
426
|
// headings) — never an eyebrow.
|
|
@@ -344,23 +441,31 @@ function checkHeroEyebrow(opts) {
|
|
|
344
441
|
const weight = Number(siblingFontWeight) || 400;
|
|
345
442
|
const isAccentBold = weight >= 700 && isAccentColor(siblingColor || '');
|
|
346
443
|
|
|
347
|
-
|
|
444
|
+
// Branch C: dash-prefix eyebrow — sentence case, low tracking, regular
|
|
445
|
+
// weight, but announced by a short chromatic ::before/::after bar
|
|
446
|
+
// (the kicker dash). Same label-above-headline pattern, third styling.
|
|
447
|
+
const isDashPrefixed = !!siblingHasAccentDashPseudo;
|
|
448
|
+
|
|
449
|
+
if (!isClassicTracked && !isAccentBold && !isDashPrefixed) return [];
|
|
348
450
|
|
|
349
451
|
const headingTextSnippet = (headingText || '').trim().slice(0, 60);
|
|
350
452
|
const eyebrowSnippet = text.slice(0, 40);
|
|
351
|
-
const style = isClassicTracked ? 'tracked-caps' : 'accent-bold';
|
|
453
|
+
const style = isClassicTracked ? 'tracked-caps' : isAccentBold ? 'accent-bold' : 'dash-prefix';
|
|
352
454
|
return [{
|
|
353
455
|
id: 'hero-eyebrow-chip',
|
|
354
456
|
snippet: `eyebrow chip (${style}) "${eyebrowSnippet}" above ${headingTag} "${headingTextSnippet}"`,
|
|
355
457
|
}];
|
|
356
458
|
}
|
|
357
459
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
460
|
+
// Outright ban: one kicker is one too many, so every collected candidate is
|
|
461
|
+
// a finding. The judgment lives in the candidate gate (isKickerCandidate) and
|
|
462
|
+
// the collector's context skips, not in a repetition count.
|
|
463
|
+
function checkKickerAboveHeading(opts) {
|
|
464
|
+
const { candidates } = opts;
|
|
465
|
+
if (!Array.isArray(candidates)) return [];
|
|
361
466
|
return candidates.map(candidate => ({
|
|
362
|
-
id: '
|
|
363
|
-
snippet: `
|
|
467
|
+
id: 'kicker-above-heading',
|
|
468
|
+
snippet: `kicker "${candidate.kickerText}" above ${candidate.headingTag} "${candidate.headingText}"`,
|
|
364
469
|
}));
|
|
365
470
|
}
|
|
366
471
|
|
|
@@ -409,38 +514,882 @@ function checkMotion(opts) {
|
|
|
409
514
|
return findings;
|
|
410
515
|
}
|
|
411
516
|
|
|
517
|
+
// Locate the color token in a single shadow layer. Returns
|
|
518
|
+
// { color, start, end } where color is the parsed {r,g,b,a} (null when the
|
|
519
|
+
// token exists but can't be parsed — e.g. an unresolved var() or an exotic
|
|
520
|
+
// color space), or null when no color token is present at all. Handles both
|
|
521
|
+
// serialization orders: computed style puts the color first
|
|
522
|
+
// ("rgb(…) 0px 0px 20px"), authored CSS usually puts it last
|
|
523
|
+
// ("0 0 20px #3b82f6").
|
|
524
|
+
function findShadowColor(layer) {
|
|
525
|
+
const fn = layer.match(/(?:rgba?|hsla?|hwb|oklch|oklab|lch|lab|color)\([^)]*\)/i);
|
|
526
|
+
if (fn) return { color: parseAnyColor(fn[0]), start: fn.index, end: fn.index + fn[0].length };
|
|
527
|
+
const hex = layer.match(/#[0-9a-fA-F]{3,8}\b/);
|
|
528
|
+
if (hex) return { color: parseAnyColor(hex[0]), start: hex.index, end: hex.index + hex[0].length };
|
|
529
|
+
const wordRe = /[a-zA-Z][a-zA-Z]*/g;
|
|
530
|
+
let m;
|
|
531
|
+
while ((m = wordRe.exec(layer)) !== null) {
|
|
532
|
+
const named = CSS_NAMED_COLORS[m[0].toLowerCase()];
|
|
533
|
+
if (named) return { color: { ...named, a: 1 }, start: m.index, end: m.index + m[0].length };
|
|
534
|
+
}
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Extract the length values of a shadow layer in declaration order, with the
|
|
539
|
+
// color token removed so its components aren't misread as lengths. Handles
|
|
540
|
+
// computed-style px values AND authored unitless zeros ("0 0 20px"); rem/em
|
|
541
|
+
// approximate at 16px. Result order is offset-x, offset-y, blur, [spread].
|
|
542
|
+
function extractShadowLengths(layer, colorStart, colorEnd) {
|
|
543
|
+
const stripped = colorStart != null
|
|
544
|
+
? layer.slice(0, colorStart) + ' ' + layer.slice(colorEnd)
|
|
545
|
+
: layer;
|
|
546
|
+
const vals = [];
|
|
547
|
+
const re = /(-?\d*\.?\d+)(px|rem|em)?/g;
|
|
548
|
+
let m;
|
|
549
|
+
while ((m = re.exec(stripped)) !== null) {
|
|
550
|
+
let v = parseFloat(m[1]);
|
|
551
|
+
if (m[2] === 'rem' || m[2] === 'em') v *= 16;
|
|
552
|
+
vals.push(v);
|
|
553
|
+
}
|
|
554
|
+
return vals;
|
|
555
|
+
}
|
|
556
|
+
|
|
412
557
|
function checkGlow(opts) {
|
|
413
|
-
const { boxShadow, effectiveBg } = opts;
|
|
414
|
-
|
|
415
|
-
|
|
558
|
+
const { boxShadow, textShadow, effectiveBg } = opts;
|
|
559
|
+
const onDarkBg = effectiveBg ? relativeLuminance(effectiveBg) < 0.1 : false;
|
|
560
|
+
|
|
561
|
+
// Scan one shadow list. Two glow tells, in any color format:
|
|
562
|
+
// 1. Zero-offset chromatic halo (0 0 Npx <color>) — slop on ANY
|
|
563
|
+
// background; the light radiates evenly outward, which is never how
|
|
564
|
+
// real elevation shadows behave. Achromatic zero-offset shadows stay
|
|
565
|
+
// legal (soft ambient elevation), as do focus rings (blur 0).
|
|
566
|
+
// 2. Any chromatic shadow with real blur on a dark background — the
|
|
567
|
+
// classic dark-mode glow accent.
|
|
568
|
+
const scan = (value, prop) => {
|
|
569
|
+
if (!value || value === 'none') return null;
|
|
570
|
+
// Split multiple shadows (commas not inside parentheses)
|
|
571
|
+
for (const layer of value.split(/,(?![^(]*\))/)) {
|
|
572
|
+
const colorInfo = findShadowColor(layer);
|
|
573
|
+
// No color token, or one we can't resolve (unresolved var(), exotic
|
|
574
|
+
// color space): don't guess — skip rather than false-positive.
|
|
575
|
+
if (!colorInfo || !colorInfo.color) continue;
|
|
576
|
+
const color = colorInfo.color;
|
|
577
|
+
if (!hasChroma(color, 30)) continue;
|
|
578
|
+
const vals = extractShadowLengths(layer, colorInfo.start, colorInfo.end);
|
|
579
|
+
// Third value is blur (offset-x, offset-y, blur, [spread])
|
|
580
|
+
if (vals.length < 3 || vals[2] <= 4) continue;
|
|
581
|
+
if (vals[0] === 0 && vals[1] === 0) {
|
|
582
|
+
return { id: 'dark-glow', snippet: `Zero-offset ${prop} glow (${colorToHex(color)})` };
|
|
583
|
+
}
|
|
584
|
+
if (onDarkBg) {
|
|
585
|
+
return { id: 'dark-glow', snippet: `Colored ${prop} glow (${colorToHex(color)}) on dark background` };
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const found = scan(boxShadow, 'box-shadow') || scan(textShadow, 'text-shadow');
|
|
592
|
+
return found ? [found] : [];
|
|
593
|
+
}
|
|
416
594
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
595
|
+
// Collect CSS custom property declarations from raw stylesheet/HTML text.
|
|
596
|
+
// First declaration wins (:root declarations usually come first); good
|
|
597
|
+
// enough for the single-level var() resolution the text engines need.
|
|
598
|
+
function collectCssCustomProps(content) {
|
|
599
|
+
const map = new Map();
|
|
600
|
+
const re = /(--[\w-]+)\s*:\s*([^;{}]+)/g;
|
|
601
|
+
let m;
|
|
602
|
+
while ((m = re.exec(content)) !== null) {
|
|
603
|
+
if (!map.has(m[1])) map.set(m[1], m[2].trim());
|
|
604
|
+
}
|
|
605
|
+
return map;
|
|
606
|
+
}
|
|
420
607
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
608
|
+
// Text-level glow scan shared by the regex engine and the page-level HTML
|
|
609
|
+
// pattern pass. Resolves single-level var() refs against custom properties
|
|
610
|
+
// collected from the same text, then applies the same two glow tells as
|
|
611
|
+
// checkGlow: zero-offset chromatic halo (any background) and chromatic
|
|
612
|
+
// blurred shadow when the page has a dark background. Returns
|
|
613
|
+
// [{ index, snippet }] — index is the offset of the shadow declaration.
|
|
614
|
+
// Dark-page heuristic for raw CSS/HTML text: dark hex/rgb literals, Tailwind
|
|
615
|
+
// dark bg utilities, or a ROOT-scoped (body/html/:root or <body style>)
|
|
616
|
+
// background that resolves — via var() — to a dark color. The var/modern-
|
|
617
|
+
// color extension is deliberately root-scoped: a light page with one dark
|
|
618
|
+
// accent chip must not turn every tinted drop shadow into a "dark page"
|
|
619
|
+
// signal. Shared by the glow and radial-halo text scanners.
|
|
620
|
+
function cssTextHasDarkRootBg(content, customProps) {
|
|
621
|
+
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;
|
|
622
|
+
const twDarkBg = /\bbg-(?:gray|slate|zinc|neutral|stone)-(?:9\d{2}|800)\b/;
|
|
623
|
+
if (darkBgRe.test(content) || twDarkBg.test(content)) return true;
|
|
624
|
+
const rootScopes = [];
|
|
625
|
+
const blockRe = /(?:^|[}\s,;>])(?:body|html|:root)\s*(?:,[^{]*)?\{([^}]*)\}/gi;
|
|
626
|
+
let sm;
|
|
627
|
+
while ((sm = blockRe.exec(content)) !== null) rootScopes.push(sm[1]);
|
|
628
|
+
const inlineBody = content.match(/<body[^>]*\bstyle\s*=\s*"([^"]*)"/i);
|
|
629
|
+
if (inlineBody) rootScopes.push(inlineBody[1]);
|
|
630
|
+
for (const scope of rootScopes) {
|
|
631
|
+
const bgRe = /background(?:-color)?\s*:\s*([^;{}]+)/gi;
|
|
632
|
+
let bm;
|
|
633
|
+
while ((bm = bgRe.exec(scope)) !== null) {
|
|
634
|
+
const c = parseAnyColor(resolveVarRefs(bm[1].trim(), customProps));
|
|
635
|
+
if (c && (c.a ?? 1) > 0.5 && relativeLuminance(c) < 0.1) return true;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
428
640
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const pxVals = [...beforeColor.matchAll(/([\d.]+)px/g), ...afterColor.matchAll(/([\d.]+)px/g)]
|
|
433
|
-
.map(m => parseFloat(m[1]));
|
|
641
|
+
function scanCssTextForGlow(content) {
|
|
642
|
+
const customProps = collectCssCustomProps(content);
|
|
643
|
+
const hasDarkBg = cssTextHasDarkRootBg(content, customProps);
|
|
434
644
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
645
|
+
const results = [];
|
|
646
|
+
const shadowRe = /\b(box-shadow|text-shadow)\s*:\s*([^;{}]+)/gi;
|
|
647
|
+
let m;
|
|
648
|
+
while ((m = shadowRe.exec(content)) !== null) {
|
|
649
|
+
const prop = m[1].toLowerCase();
|
|
650
|
+
const value = resolveVarRefs(m[2].trim(), customProps);
|
|
651
|
+
for (const layer of value.split(/,(?![^(]*\))/)) {
|
|
652
|
+
const colorInfo = findShadowColor(layer);
|
|
653
|
+
if (!colorInfo || !colorInfo.color || !hasChroma(colorInfo.color, 30)) continue;
|
|
654
|
+
const vals = extractShadowLengths(layer, colorInfo.start, colorInfo.end);
|
|
655
|
+
if (vals.length < 3 || vals[2] <= 4) continue;
|
|
656
|
+
const zeroOffset = vals[0] === 0 && vals[1] === 0;
|
|
657
|
+
if (!zeroOffset && !hasDarkBg) continue;
|
|
658
|
+
results.push({
|
|
659
|
+
index: m.index,
|
|
660
|
+
snippet: zeroOffset
|
|
661
|
+
? `Zero-offset ${prop} glow (${colorToHex(colorInfo.color)})`
|
|
662
|
+
: `Colored ${prop} glow (${colorToHex(colorInfo.color)}) on dark page`,
|
|
663
|
+
});
|
|
664
|
+
break; // one finding per declaration
|
|
438
665
|
}
|
|
439
666
|
}
|
|
667
|
+
return results;
|
|
668
|
+
}
|
|
440
669
|
|
|
670
|
+
// Decorative grid or line-field backgrounds drawn with hairline
|
|
671
|
+
// linear-gradient layers tiled by a fixed pixel cell. Shared by the HTML
|
|
672
|
+
// pattern pass and the regex source engine so standalone CSS, component
|
|
673
|
+
// styles, and inline styles receive the same coverage. Both signals must
|
|
674
|
+
// co-occur in one declaration block; unrelated rules must not add up across
|
|
675
|
+
// the file. Returns [{ index, snippet }], capped at one finding per source to
|
|
676
|
+
// match the page-level HTML check's existing behavior.
|
|
677
|
+
function scanCssTextForGridBackground(content) {
|
|
678
|
+
const hairlineRe = /\b\d{1,3}px\s*,\s*transparent\s+\d{1,3}px/gi;
|
|
679
|
+
const invertedHairlineRe = /transparent\s+calc\(100%\s*-\s*\d{1,3}px\)/gi;
|
|
680
|
+
const sizeDeclPxRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\b/i;
|
|
681
|
+
const sizeDeclPxPairRe = /background-size\s*:[^;{}"']*\b\d{1,3}px\s+\d{1,3}px/i;
|
|
682
|
+
const shorthandPxAnyRe = /\/\s*\d{1,3}px\b/;
|
|
683
|
+
const shorthandPxPairRe = /\/\s*\d{1,3}px\s+\d{1,3}px/;
|
|
684
|
+
const bgDeclRe = /\bbackground(?:-image)?\s*:\s*([^;{}"']*)/gi;
|
|
685
|
+
const blockRe = /\{([^{}]*)\}|style\s*=\s*"([^"]*)"|style\s*=\s*'([^']*)'/gi;
|
|
686
|
+
let blk;
|
|
687
|
+
while ((blk = blockRe.exec(content)) !== null) {
|
|
688
|
+
const block = blk[1] || blk[2] || blk[3] || '';
|
|
689
|
+
let hairlineCount = 0;
|
|
690
|
+
let bgJoined = '';
|
|
691
|
+
let bm;
|
|
692
|
+
bgDeclRe.lastIndex = 0;
|
|
693
|
+
while ((bm = bgDeclRe.exec(block)) !== null) {
|
|
694
|
+
hairlineCount += (bm[1].match(hairlineRe) || []).length;
|
|
695
|
+
hairlineCount += (bm[1].match(invertedHairlineRe) || []).length;
|
|
696
|
+
bgJoined += `${bm[1]};`;
|
|
697
|
+
}
|
|
698
|
+
if (hairlineCount === 0) continue;
|
|
699
|
+
const hasPxCell = sizeDeclPxRe.test(block) || shorthandPxAnyRe.test(bgJoined);
|
|
700
|
+
const hasPxPairCell = sizeDeclPxPairRe.test(block) || shorthandPxPairRe.test(bgJoined);
|
|
701
|
+
if ((hairlineCount >= 2 && hasPxCell) || hasPxPairCell) {
|
|
702
|
+
return [{
|
|
703
|
+
index: blk.index,
|
|
704
|
+
snippet: hairlineCount >= 2
|
|
705
|
+
? 'two-axis grid-line gradient background'
|
|
706
|
+
: 'px-tiled hairline line-field background',
|
|
707
|
+
}];
|
|
708
|
+
}
|
|
709
|
+
}
|
|
441
710
|
return [];
|
|
442
711
|
}
|
|
443
712
|
|
|
713
|
+
// Decorative chromatic halo drawn as a radial-gradient background on a dark
|
|
714
|
+
// page: a saturated center stop dissolving to transparent. The gradient
|
|
715
|
+
// sibling of the dark-glow shadow tell. Mechanical gates, in order:
|
|
716
|
+
// * page has a dark root background (shared heuristic with the glow scan)
|
|
717
|
+
// * declaration has no url() layer (photographic imagery is exempt)
|
|
718
|
+
// * the gradient's first color stop is chromatic (RGB spread >= 24) and
|
|
719
|
+
// visible (alpha >= 0.7 — deliberately translucent light-scene washes
|
|
720
|
+
// composite with content instead of painting a flat halo, and stay legal)
|
|
721
|
+
// * the gradient's last stop is transparent / near-zero alpha
|
|
722
|
+
// * no small pixel-sized stop positions (<= 24px = dot/texture patterns)
|
|
723
|
+
// * not a repeating-* gradient
|
|
724
|
+
// Achromatic vignettes fail the chroma gate; panel sheens that fade to an
|
|
725
|
+
// opaque surface color fail the transparent-end gate.
|
|
726
|
+
function scanCssTextForRadialHalo(content) {
|
|
727
|
+
const customProps = collectCssCustomProps(content);
|
|
728
|
+
if (!cssTextHasDarkRootBg(content, customProps)) return [];
|
|
729
|
+
|
|
730
|
+
const findings = [];
|
|
731
|
+
const seen = new Set();
|
|
732
|
+
const declRe = /background(?:-image)?\s*:\s*([^;{}]+)/gi;
|
|
733
|
+
let m;
|
|
734
|
+
while ((m = declRe.exec(content)) !== null) {
|
|
735
|
+
const value = resolveVarRefs(m[1].trim(), customProps);
|
|
736
|
+
if (/url\s*\(/i.test(value)) continue;
|
|
737
|
+
|
|
738
|
+
const gradRe = /(repeating-)?radial-gradient\(/gi;
|
|
739
|
+
let g;
|
|
740
|
+
while ((g = gradRe.exec(value)) !== null) {
|
|
741
|
+
if (g[1]) continue; // repeating-* = pattern, not halo
|
|
742
|
+
// Balanced-paren capture of the gradient arguments.
|
|
743
|
+
let depth = 0, end = -1;
|
|
744
|
+
const open = value.indexOf('(', g.index);
|
|
745
|
+
for (let i = open; i < value.length; i++) {
|
|
746
|
+
if (value[i] === '(') depth++;
|
|
747
|
+
else if (value[i] === ')') { depth--; if (depth === 0) { end = i; break; } }
|
|
748
|
+
}
|
|
749
|
+
if (end < 0) break;
|
|
750
|
+
const args = splitTopLevelCommas(value.slice(open + 1, end));
|
|
751
|
+
if (args.length < 2) continue;
|
|
752
|
+
|
|
753
|
+
// Optional prelude (shape / size / `at <pos>`) carries no color.
|
|
754
|
+
const colorTokenRe = /(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color-mix)\([^)]*(?:\([^)]*\))?[^)]*\)|#[0-9a-f]{3,8}\b|\btransparent\b/i;
|
|
755
|
+
const stops = args.filter(a => colorTokenRe.test(a));
|
|
756
|
+
if (stops.length < 2) continue;
|
|
757
|
+
|
|
758
|
+
// Dot/texture exemption: px-sized stop positions mean a repeating
|
|
759
|
+
// background-size pattern, not a page-scale halo.
|
|
760
|
+
const pxStop = stops.some(s => {
|
|
761
|
+
const pm = s.match(/(-?[\d.]+)px\b/);
|
|
762
|
+
return pm && Math.abs(parseFloat(pm[1])) <= 24;
|
|
763
|
+
});
|
|
764
|
+
if (pxStop) continue;
|
|
765
|
+
|
|
766
|
+
const first = stops[0].match(colorTokenRe);
|
|
767
|
+
const last = stops[stops.length - 1].match(colorTokenRe);
|
|
768
|
+
if (!first || !last) continue;
|
|
769
|
+
|
|
770
|
+
const lastColor = /^transparent$/i.test(last[0]) ? { r: 0, g: 0, b: 0, a: 0 } : parseAnyColor(last[0]);
|
|
771
|
+
if (!lastColor || (lastColor.a ?? 1) > 0.05) continue;
|
|
772
|
+
|
|
773
|
+
const firstColor = /^transparent$/i.test(first[0]) ? null : parseAnyColor(first[0]);
|
|
774
|
+
if (!firstColor) continue;
|
|
775
|
+
if ((firstColor.a ?? 1) < 0.7) continue;
|
|
776
|
+
const spread = Math.max(firstColor.r, firstColor.g, firstColor.b) - Math.min(firstColor.r, firstColor.g, firstColor.b);
|
|
777
|
+
if (spread < 24) continue;
|
|
778
|
+
|
|
779
|
+
const snippet = `radial-gradient halo (${colorToHex(firstColor)} → transparent) on dark page`;
|
|
780
|
+
if (seen.has(snippet)) continue;
|
|
781
|
+
seen.add(snippet);
|
|
782
|
+
findings.push({ index: m.index, snippet });
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return findings;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// ---------------------------------------------------------------------------
|
|
789
|
+
// Text-level CSS rule-block scanners (pseudo-element stripes, pulsing dots)
|
|
790
|
+
// ---------------------------------------------------------------------------
|
|
791
|
+
|
|
792
|
+
// Iterate `selector { declarations }` pairs in raw CSS/HTML text. The block
|
|
793
|
+
// body excludes braces, so nested structures (@media, @keyframes) naturally
|
|
794
|
+
// yield their innermost rules with the innermost selector text. Callers
|
|
795
|
+
// create the regex locally — a shared /g instance is not re-entrant.
|
|
796
|
+
const CSS_RULE_BLOCK_SOURCE = String.raw`([^{};]+)\{([^{}]*)\}`;
|
|
797
|
+
|
|
798
|
+
// Parse a declaration block into a prop → value map (last declaration wins,
|
|
799
|
+
// approximating the cascade inside one block). Values keep their raw text
|
|
800
|
+
// with any !important suffix stripped.
|
|
801
|
+
function parseCssDeclBlock(block) {
|
|
802
|
+
const decls = new Map();
|
|
803
|
+
for (const part of String(block || '').split(';')) {
|
|
804
|
+
const idx = part.indexOf(':');
|
|
805
|
+
if (idx <= 0) continue;
|
|
806
|
+
const prop = part.slice(0, idx).trim().toLowerCase();
|
|
807
|
+
const value = part.slice(idx + 1).replace(/\s*!important\s*$/i, '').trim();
|
|
808
|
+
if (prop && value) decls.set(prop, value);
|
|
809
|
+
}
|
|
810
|
+
return decls;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function cssLengthToPx(value) {
|
|
814
|
+
const m = String(value || '').trim().match(/^(-?[\d.]+)(px|rem|em)$/i);
|
|
815
|
+
if (!m) return null;
|
|
816
|
+
const n = parseFloat(m[1]);
|
|
817
|
+
return m[2].toLowerCase() === 'px' ? n : n * 16;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function isZeroOffset(value) {
|
|
821
|
+
return value != null && /^-?0(?:px|%|rem|em)?$/.test(String(value).trim());
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Side-tab variant: the accent stripe drawn as an absolutely-positioned
|
|
825
|
+
// ::before/::after pseudo-element (narrow colored box hugging a vertical
|
|
826
|
+
// edge) instead of a border-left/right. The element-level border checks
|
|
827
|
+
// never see it — pseudo-elements aren't part of the DOM the cascade walks —
|
|
828
|
+
// so this scans stylesheet text directly, mirroring the border rule's
|
|
829
|
+
// gates: >= 3px thick, chromatic fill, full height against a side edge.
|
|
830
|
+
function scanCssTextForPseudoStripe(rawContent) {
|
|
831
|
+
// Blank comment bodies byte-for-byte so commented-out rules are not
|
|
832
|
+
// scanned as live CSS and every rule keeps its source offset (each
|
|
833
|
+
// finding carries `index` so line-based callers can attribute it and
|
|
834
|
+
// line-scoped inline ignores can match).
|
|
835
|
+
const content = String(rawContent || '').replace(/\/\*[\s\S]*?\*\//g,
|
|
836
|
+
(block) => block.replace(/[^\n]/g, ' '));
|
|
837
|
+
const customProps = collectCssCustomProps(content);
|
|
838
|
+
const findings = [];
|
|
839
|
+
const seen = new Set();
|
|
840
|
+
const ruleRe = new RegExp(CSS_RULE_BLOCK_SOURCE, 'g');
|
|
841
|
+
let m;
|
|
842
|
+
while ((m = ruleRe.exec(content)) !== null) {
|
|
843
|
+
const selector = m[1].trim();
|
|
844
|
+
if (!/::?(?:before|after)\b/i.test(selector)) continue;
|
|
845
|
+
// Keep the border rule's prose exemptions (blockquote bars etc.).
|
|
846
|
+
if (/\b(?:blockquote|pre|code|nav|hr)\b/i.test(selector)) continue;
|
|
847
|
+
const decls = parseCssDeclBlock(m[2]);
|
|
848
|
+
const position = decls.get('position');
|
|
849
|
+
if (position !== 'absolute' && position !== 'fixed') continue;
|
|
850
|
+
|
|
851
|
+
const widthPx = cssLengthToPx(resolveVarRefs(
|
|
852
|
+
decls.get('width') || decls.get('inline-size') || '', customProps));
|
|
853
|
+
const heightPx = cssLengthToPx(resolveVarRefs(
|
|
854
|
+
decls.get('height') || decls.get('block-size') || '', customProps));
|
|
855
|
+
const verticalCandidate = widthPx != null && widthPx >= 3 && widthPx <= 12;
|
|
856
|
+
// Horizontal variant (top/bottom stripe) carries extra exemptions:
|
|
857
|
+
// link/button underline affordances, selected-state indicators
|
|
858
|
+
// (aria-selected="true", aria-current, active/current/selected class
|
|
859
|
+
// hints), and state-conditional (:hover/:focus/...) affordances are
|
|
860
|
+
// not stripes. Tab-strip membership alone ([role=tab], .tabs, bare
|
|
861
|
+
// [aria-selected]) is NOT exempt — a stripe on every tab in the
|
|
862
|
+
// group is decoration; only the selected item's underline stays.
|
|
863
|
+
const horizontalCandidate = heightPx != null && heightPx >= 3 && heightPx <= 12
|
|
864
|
+
&& !/(?:^|[\s>+~,(])(?:a|button|summary|tr|td|th|table|li)(?![\w-])/i.test(selector)
|
|
865
|
+
&& !/\[aria-selected\s*[*^$|~]?=\s*["']?true/i.test(selector)
|
|
866
|
+
&& !/\[aria-current(?!\s*[*^$|~]?=\s*["']?false)/i.test(selector)
|
|
867
|
+
&& !/(?:^|[\s._[-])(?:active|current|selected|btn[\w-]*|button[\w-]*|link[\w-]*)(?![\w])/i.test(selector)
|
|
868
|
+
&& !/:(?:hover|focus|focus-visible|focus-within|active|checked)\b/i.test(selector);
|
|
869
|
+
if (!verticalCandidate && !horizontalCandidate) continue;
|
|
870
|
+
|
|
871
|
+
// Resolve edge offsets, letting an `inset` shorthand fill the gaps.
|
|
872
|
+
const offsets = {
|
|
873
|
+
top: decls.get('top'), right: decls.get('right'),
|
|
874
|
+
bottom: decls.get('bottom'), left: decls.get('left'),
|
|
875
|
+
};
|
|
876
|
+
const inset = decls.get('inset');
|
|
877
|
+
if (inset) {
|
|
878
|
+
const p = inset.split(/\s+/);
|
|
879
|
+
const [t, r, b, l] =
|
|
880
|
+
p.length === 1 ? [p[0], p[0], p[0], p[0]]
|
|
881
|
+
: p.length === 2 ? [p[0], p[1], p[0], p[1]]
|
|
882
|
+
: p.length === 3 ? [p[0], p[1], p[2], p[1]]
|
|
883
|
+
: p;
|
|
884
|
+
if (offsets.top == null) offsets.top = t;
|
|
885
|
+
if (offsets.right == null) offsets.right = r;
|
|
886
|
+
if (offsets.bottom == null) offsets.bottom = b;
|
|
887
|
+
if (offsets.left == null) offsets.left = l;
|
|
888
|
+
}
|
|
889
|
+
if (offsets.left == null) offsets.left = decls.get('inset-inline-start');
|
|
890
|
+
if (offsets.right == null) offsets.right = decls.get('inset-inline-end');
|
|
891
|
+
|
|
892
|
+
const heightValue = String(resolveVarRefs(
|
|
893
|
+
decls.get('height') || decls.get('block-size') || '', customProps)).trim();
|
|
894
|
+
const widthValue = String(resolveVarRefs(
|
|
895
|
+
decls.get('width') || decls.get('inline-size') || '', customProps)).trim();
|
|
896
|
+
|
|
897
|
+
let edge = null;
|
|
898
|
+
let thicknessPx = null;
|
|
899
|
+
if (verticalCandidate) {
|
|
900
|
+
// Full-height stripes hug both corners; the "floating" variant backs
|
|
901
|
+
// off each end by a small inset (top/bottom a few px) so the bar
|
|
902
|
+
// clears the card's corners. Both read as the same side-tab accent —
|
|
903
|
+
// corner treatment is styling, not a different pattern.
|
|
904
|
+
const topPx = cssLengthToPx(resolveVarRefs(String(offsets.top ?? ''), customProps));
|
|
905
|
+
const bottomPx = cssLengthToPx(resolveVarRefs(String(offsets.bottom ?? ''), customProps));
|
|
906
|
+
const fullHeight = (isZeroOffset(offsets.top) && isZeroOffset(offsets.bottom))
|
|
907
|
+
|| /^100(?:\.0*)?%$/.test(heightValue)
|
|
908
|
+
|| (topPx != null && bottomPx != null
|
|
909
|
+
&& topPx >= 0 && topPx <= 20 && bottomPx >= 0 && bottomPx <= 20);
|
|
910
|
+
if (fullHeight) {
|
|
911
|
+
edge = isZeroOffset(offsets.left) ? 'left'
|
|
912
|
+
: isZeroOffset(offsets.right) ? 'right' : null;
|
|
913
|
+
thicknessPx = widthPx;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (!edge && horizontalCandidate) {
|
|
917
|
+
const fullWidth = (isZeroOffset(offsets.left) && isZeroOffset(offsets.right))
|
|
918
|
+
|| /^100(?:\.0*)?%$/.test(widthValue);
|
|
919
|
+
if (fullWidth) {
|
|
920
|
+
edge = isZeroOffset(offsets.top) ? 'top'
|
|
921
|
+
: isZeroOffset(offsets.bottom) ? 'bottom' : null;
|
|
922
|
+
thicknessPx = heightPx;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
if (!edge) continue;
|
|
926
|
+
|
|
927
|
+
// Chromatic fill only — a neutral hairline divider is not an accent
|
|
928
|
+
// stripe. Unresolvable colors err toward detection, matching the
|
|
929
|
+
// border rule's unknown-format default.
|
|
930
|
+
const bg = String(resolveVarRefs(
|
|
931
|
+
decls.get('background-color') || decls.get('background') || '', customProps)).trim();
|
|
932
|
+
if (!bg || /^(?:none|transparent|inherit|initial|unset|currentcolor)$/i.test(bg)) continue;
|
|
933
|
+
const colorToken = bg.match(/(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb)\([^)]*\)|#[0-9a-f]{3,8}\b/i);
|
|
934
|
+
const parsed = parseAnyColor(colorToken ? colorToken[0] : bg);
|
|
935
|
+
if (parsed) {
|
|
936
|
+
if ((parsed.a ?? 1) < 0.1) continue;
|
|
937
|
+
const spread = Math.max(parsed.r, parsed.g, parsed.b) - Math.min(parsed.r, parsed.g, parsed.b);
|
|
938
|
+
if (spread < 30) continue;
|
|
939
|
+
} else if (/^(?:white|black|gray|grey|silver)$/i.test(bg)) {
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (seen.has(selector)) continue;
|
|
944
|
+
seen.add(selector);
|
|
945
|
+
// The selector group absorbs whitespace trailing the previous rule;
|
|
946
|
+
// advance past it so `index` points at the selector itself.
|
|
947
|
+
const selectorStart = m.index + (m[1].length - m[1].trimStart().length);
|
|
948
|
+
findings.push({
|
|
949
|
+
id: 'side-tab',
|
|
950
|
+
snippet: `${selector} — absolute ${thicknessPx}px pseudo-element stripe (${edge}: 0)`,
|
|
951
|
+
index: selectorStart,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
return findings;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Side-tab stripe drawn as a single-edge inset box-shadow
|
|
958
|
+
// (x or y offset 3-12px, other axis 0, no blur/spread, chromatic color):
|
|
959
|
+
// paints a bar along one edge with no border property involved, so the
|
|
960
|
+
// element-level border checks never see it. Selection-state indicators
|
|
961
|
+
// are exempt — an inset stripe on [aria-current] / .active / [role=tab]
|
|
962
|
+
// marks the selected item; the same stripe unconditionally on every item
|
|
963
|
+
// is decoration and flags.
|
|
964
|
+
function scanCssTextForInsetStripe(content) {
|
|
965
|
+
const customProps = collectCssCustomProps(content);
|
|
966
|
+
const findings = [];
|
|
967
|
+
const seen = new Set();
|
|
968
|
+
const ruleRe = new RegExp(CSS_RULE_BLOCK_SOURCE, 'g');
|
|
969
|
+
let m;
|
|
970
|
+
while ((m = ruleRe.exec(content)) !== null) {
|
|
971
|
+
const selector = m[1].trim();
|
|
972
|
+
// Selection-state contexts: current-item markers and interaction
|
|
973
|
+
// states. Tab-strip membership alone ([role=tab], .tabs, bare
|
|
974
|
+
// [aria-selected]) is NOT exempt — a stripe on every tab in the
|
|
975
|
+
// group is decoration; only the selected item's indicator stays.
|
|
976
|
+
if (/:(?:hover|focus|focus-visible|focus-within|active|checked|target)\b/i.test(selector)) continue;
|
|
977
|
+
if (/\[aria-selected\s*[*^$|~]?=\s*["']?true/i.test(selector)) continue;
|
|
978
|
+
if (/\[aria-current(?!\s*[*^$|~]?=\s*["']?false)/i.test(selector)) continue;
|
|
979
|
+
if (/(?:^|[\s._[-])(?:active|current|selected)(?![\w])/i.test(selector)) continue;
|
|
980
|
+
// Structural tags where a single-edge inset shadow is depth/quoting,
|
|
981
|
+
// not an accent stripe.
|
|
982
|
+
if (/(?:^|[\s>+~,(])(?:button|hr|tr|td|th|table|blockquote|pre|code)(?![\w-])/i.test(selector)) continue;
|
|
983
|
+
|
|
984
|
+
const decls = parseCssDeclBlock(m[2]);
|
|
985
|
+
const shadow = decls.get('box-shadow');
|
|
986
|
+
if (!shadow || !/\binset\b/i.test(shadow)) continue;
|
|
987
|
+
// Narrow fixed-width elements (logo marks, icon glyphs) use inset
|
|
988
|
+
// fills as artwork, not edge stripes. Stripe targets — cards, badges,
|
|
989
|
+
// menu items — are wider or leave width to layout.
|
|
990
|
+
const declaredWidth = cssLengthToPx(resolveVarRefs(decls.get('width') || decls.get('inline-size') || '', customProps));
|
|
991
|
+
if (declaredWidth != null && declaredWidth <= 40) continue;
|
|
992
|
+
const value = resolveVarRefs(shadow, customProps);
|
|
993
|
+
for (const layer of value.split(/,(?![^(]*\))/)) {
|
|
994
|
+
if (!/\binset\b/i.test(layer)) continue;
|
|
995
|
+
const colorInfo = findShadowColor(layer);
|
|
996
|
+
// Unresolvable colors (currentColor, external vars): don't guess.
|
|
997
|
+
if (!colorInfo || !colorInfo.color) continue;
|
|
998
|
+
const c = colorInfo.color;
|
|
999
|
+
if ((c.a ?? 1) < 0.1) continue;
|
|
1000
|
+
const chroma = Math.max(c.r, c.g, c.b) - Math.min(c.r, c.g, c.b);
|
|
1001
|
+
if (chroma < 30) continue;
|
|
1002
|
+
const vals = extractShadowLengths(layer, colorInfo.start, colorInfo.end);
|
|
1003
|
+
const x = vals[0] || 0, y = vals[1] || 0, blur = vals[2] || 0, sp = vals[3] || 0;
|
|
1004
|
+
if (blur !== 0 || sp !== 0) continue;
|
|
1005
|
+
const ax = Math.abs(x), ay = Math.abs(y);
|
|
1006
|
+
const isStripe = (ax >= 3 && ax <= 12 && ay === 0) || (ay >= 3 && ay <= 12 && ax === 0);
|
|
1007
|
+
if (!isStripe) continue;
|
|
1008
|
+
if (seen.has(selector)) break;
|
|
1009
|
+
seen.add(selector);
|
|
1010
|
+
const edge = ay === 0 ? (x > 0 ? 'left' : 'right') : (y > 0 ? 'top' : 'bottom');
|
|
1011
|
+
findings.push({
|
|
1012
|
+
id: 'side-tab',
|
|
1013
|
+
snippet: `${selector} — inset box-shadow ${ay === 0 ? ax : ay}px stripe (${edge})`,
|
|
1014
|
+
});
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return findings;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Collect @keyframes names whose body travels horizontally — the marquee
|
|
1022
|
+
// loop. X travel is measured across every translateX/translate/translate3d
|
|
1023
|
+
// X component in the body: a centered element animating something else
|
|
1024
|
+
// keeps a constant -50% X (zero travel) and never qualifies, while a
|
|
1025
|
+
// ticker moves from its resting position to a large offset. Keyframes
|
|
1026
|
+
// with a single X sample that also vary scale/opacity read as pulses or
|
|
1027
|
+
// breathes, not marquees.
|
|
1028
|
+
function collectMarqueeKeyframes(content) {
|
|
1029
|
+
const names = new Set();
|
|
1030
|
+
const re = /@(?:-webkit-)?keyframes\s+([\w-]+)\s*\{/g;
|
|
1031
|
+
let m;
|
|
1032
|
+
while ((m = re.exec(content)) !== null) {
|
|
1033
|
+
let depth = 1;
|
|
1034
|
+
let i = re.lastIndex;
|
|
1035
|
+
while (i < content.length && depth > 0) {
|
|
1036
|
+
const ch = content.charCodeAt(i);
|
|
1037
|
+
if (ch === 0x7b /* { */) depth++;
|
|
1038
|
+
else if (ch === 0x7d /* } */) depth--;
|
|
1039
|
+
i++;
|
|
1040
|
+
}
|
|
1041
|
+
const body = content.slice(re.lastIndex, Math.max(re.lastIndex, i - 1));
|
|
1042
|
+
re.lastIndex = i;
|
|
1043
|
+
|
|
1044
|
+
// Only percentage travel qualifies: a content marquee translates by a
|
|
1045
|
+
// fraction of its own (unknown) track width, so generated tickers use
|
|
1046
|
+
// -50% / -100%. Pixel-travel loops are bespoke product animations —
|
|
1047
|
+
// sweeping playheads, progress indicators — not marquees.
|
|
1048
|
+
const pct = [];
|
|
1049
|
+
const xRe = /\btranslate(?:X|3d)?\(\s*(-?[\d.]+)%/gi;
|
|
1050
|
+
let xm;
|
|
1051
|
+
while ((xm = xRe.exec(body)) !== null) pct.push(parseFloat(xm[1]));
|
|
1052
|
+
if (pct.length === 0) continue;
|
|
1053
|
+
if (pct.length === 1 && /\bscale\(|\bopacity\s*:/i.test(body)) continue;
|
|
1054
|
+
// Implicit start: a lone declared X animates from the element's
|
|
1055
|
+
// resting position, so its magnitude is the travel.
|
|
1056
|
+
const travelPct = pct.length > 1 ? Math.max(...pct) - Math.min(...pct) : Math.abs(pct[0]);
|
|
1057
|
+
if (travelPct >= 20) names.add(m[1]);
|
|
1058
|
+
}
|
|
1059
|
+
return names;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Auto-scrolling marquee: a <marquee> element, or an infinite animation
|
|
1063
|
+
// bound to a keyframe loop that travels a large horizontal distance.
|
|
1064
|
+
// Rotation/opacity animations never qualify (no X travel); JS-driven
|
|
1065
|
+
// carousels with user controls have no infinite CSS X-loop to match.
|
|
1066
|
+
function scanCssTextForMarquee(content) {
|
|
1067
|
+
const findings = [];
|
|
1068
|
+
if (/<marquee\b/i.test(content)) {
|
|
1069
|
+
findings.push({ id: 'marquee', snippet: '<marquee> element' });
|
|
1070
|
+
}
|
|
1071
|
+
const marqueeKeyframes = collectMarqueeKeyframes(content);
|
|
1072
|
+
if (marqueeKeyframes.size === 0) return findings;
|
|
1073
|
+
const seen = new Set();
|
|
1074
|
+
const ruleRe = new RegExp(CSS_RULE_BLOCK_SOURCE, 'g');
|
|
1075
|
+
let m;
|
|
1076
|
+
while ((m = ruleRe.exec(content)) !== null) {
|
|
1077
|
+
const selector = m[1].trim();
|
|
1078
|
+
const decls = parseCssDeclBlock(m[2]);
|
|
1079
|
+
for (const name of infiniteAnimationNames(decls)) {
|
|
1080
|
+
if (!marqueeKeyframes.has(name)) continue;
|
|
1081
|
+
const key = `${selector} ${name}`;
|
|
1082
|
+
if (seen.has(key)) continue;
|
|
1083
|
+
seen.add(key);
|
|
1084
|
+
findings.push({ id: 'marquee', snippet: `${selector} — infinite horizontal loop animation "${name}"` });
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return findings;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// Collect @keyframes names and whether each one reads as a "pulse" —
|
|
1091
|
+
// i.e. it varies opacity, scale, or box-shadow. Rotation-only keyframes
|
|
1092
|
+
// (spinners) are explicitly not pulses.
|
|
1093
|
+
function collectPulseKeyframes(content) {
|
|
1094
|
+
const map = new Map();
|
|
1095
|
+
const re = /@(?:-webkit-)?keyframes\s+([\w-]+)\s*\{/g;
|
|
1096
|
+
let m;
|
|
1097
|
+
while ((m = re.exec(content)) !== null) {
|
|
1098
|
+
let depth = 1;
|
|
1099
|
+
let i = re.lastIndex;
|
|
1100
|
+
while (i < content.length && depth > 0) {
|
|
1101
|
+
const ch = content.charCodeAt(i);
|
|
1102
|
+
if (ch === 0x7b /* { */) depth++;
|
|
1103
|
+
else if (ch === 0x7d /* } */) depth--;
|
|
1104
|
+
i++;
|
|
1105
|
+
}
|
|
1106
|
+
const body = content.slice(re.lastIndex, Math.max(re.lastIndex, i - 1));
|
|
1107
|
+
const pulses = /\bopacity\s*:/i.test(body)
|
|
1108
|
+
|| /\bbox-shadow\s*:/i.test(body)
|
|
1109
|
+
|| /\btransform\s*:[^;{}]*\bscale/i.test(body);
|
|
1110
|
+
if (!map.has(m[1]) || pulses) map.set(m[1], pulses);
|
|
1111
|
+
re.lastIndex = i;
|
|
1112
|
+
}
|
|
1113
|
+
return map;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const ANIMATION_VALUE_KEYWORDS = new Set([
|
|
1117
|
+
'ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear',
|
|
1118
|
+
'infinite', 'alternate', 'alternate-reverse', 'normal', 'reverse',
|
|
1119
|
+
'none', 'forwards', 'backwards', 'both', 'running', 'paused',
|
|
1120
|
+
'step-start', 'step-end', 'inherit', 'initial', 'unset',
|
|
1121
|
+
]);
|
|
1122
|
+
|
|
1123
|
+
// Extract animation names that run with iteration-count: infinite from a
|
|
1124
|
+
// declaration block (shorthand layers or animation-name + iteration-count).
|
|
1125
|
+
function infiniteAnimationNames(decls) {
|
|
1126
|
+
const out = [];
|
|
1127
|
+
const shorthand = decls.get('animation');
|
|
1128
|
+
if (shorthand) {
|
|
1129
|
+
for (const layer of shorthand.split(/,(?![^(]*\))/)) {
|
|
1130
|
+
if (!/\binfinite\b/i.test(layer)) continue;
|
|
1131
|
+
const name = layer.split(/\s+/).find(t =>
|
|
1132
|
+
/^[a-zA-Z_-][\w-]*$/.test(t) && !ANIMATION_VALUE_KEYWORDS.has(t.toLowerCase()));
|
|
1133
|
+
if (name) out.push(name);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
const nameDecl = decls.get('animation-name');
|
|
1137
|
+
if (nameDecl && /\binfinite\b/i.test(decls.get('animation-iteration-count') || '')) {
|
|
1138
|
+
for (const raw of nameDecl.split(',')) {
|
|
1139
|
+
const t = raw.trim();
|
|
1140
|
+
if (t && t.toLowerCase() !== 'none') out.push(t);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return out;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function isRoundDotRadius(radiusValue, w, h) {
|
|
1147
|
+
if (!radiusValue) return false;
|
|
1148
|
+
const first = String(radiusValue).trim().split(/\s+/)[0];
|
|
1149
|
+
const pct = first.match(/^([\d.]+)%$/);
|
|
1150
|
+
if (pct) return parseFloat(pct[1]) >= 40;
|
|
1151
|
+
const px = cssLengthToPx(first);
|
|
1152
|
+
if (px == null) return false;
|
|
1153
|
+
return px >= 999 || px >= 0.4 * Math.min(w, h);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Remove @media blocks whose condition is prefers-reduced-motion: reduce.
|
|
1157
|
+
// Those blocks describe the accessibility fallback, not the default
|
|
1158
|
+
// experience that ships — an `animation: none` reset inside one must not
|
|
1159
|
+
// mask the resting-state animation the page plays for everyone else.
|
|
1160
|
+
function stripReducedMotionBlocks(content) {
|
|
1161
|
+
const re = /@media[^{]*prefers-reduced-motion\s*:\s*reduce[^{]*\{/gi;
|
|
1162
|
+
let out = '';
|
|
1163
|
+
let last = 0;
|
|
1164
|
+
let m;
|
|
1165
|
+
while ((m = re.exec(content)) !== null) {
|
|
1166
|
+
let depth = 1;
|
|
1167
|
+
let i = re.lastIndex;
|
|
1168
|
+
while (i < content.length && depth > 0) {
|
|
1169
|
+
const ch = content.charCodeAt(i);
|
|
1170
|
+
if (ch === 0x7b /* { */) depth++;
|
|
1171
|
+
else if (ch === 0x7d /* } */) depth--;
|
|
1172
|
+
i++;
|
|
1173
|
+
}
|
|
1174
|
+
out += content.slice(last, m.index);
|
|
1175
|
+
last = i;
|
|
1176
|
+
re.lastIndex = i;
|
|
1177
|
+
}
|
|
1178
|
+
return out + content.slice(last);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Source-index ranges of <header> and <nav> landmark elements in an HTML
|
|
1182
|
+
// string. Lets string-level scans decide whether a matched element sits in
|
|
1183
|
+
// the page chrome (the hero/nav region) without needing a DOM.
|
|
1184
|
+
function landmarkSourceRanges(content) {
|
|
1185
|
+
const ranges = [];
|
|
1186
|
+
for (const tag of ['header', 'nav']) {
|
|
1187
|
+
const re = new RegExp(`<${tag}\\b|</${tag}\\s*>`, 'gi');
|
|
1188
|
+
const stack = [];
|
|
1189
|
+
let m;
|
|
1190
|
+
while ((m = re.exec(content)) !== null) {
|
|
1191
|
+
if (m[0].charAt(1) === '/') {
|
|
1192
|
+
const start = stack.pop();
|
|
1193
|
+
if (start != null) ranges.push([start, m.index]);
|
|
1194
|
+
} else {
|
|
1195
|
+
stack.push(m.index);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return ranges;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function indexInSourceRanges(index, ranges) {
|
|
1203
|
+
return ranges.some(([start, end]) => index >= start && index < end);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Does any element targeted by the final compound of `selector` appear
|
|
1207
|
+
// inside a header/nav landmark range of the HTML source? Resolves the last
|
|
1208
|
+
// .class or #id token of the selector against class/id attributes; a
|
|
1209
|
+
// tag-only compound is never resolvable this way and returns false
|
|
1210
|
+
// (conservative: no promotion without placement evidence).
|
|
1211
|
+
function selectorHitsLandmark(content, selector, ranges) {
|
|
1212
|
+
if (!ranges || ranges.length === 0) return false;
|
|
1213
|
+
const last = selector.split(/[\s>+~]+/).filter(Boolean).pop() || '';
|
|
1214
|
+
const idMatch = last.match(/#([A-Za-z_][\w-]*)/);
|
|
1215
|
+
const classMatch = last.match(/\.([A-Za-z_][\w-]*)/);
|
|
1216
|
+
let attrRe = null;
|
|
1217
|
+
if (idMatch) {
|
|
1218
|
+
const id = idMatch[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1219
|
+
attrRe = new RegExp(`<[a-zA-Z][^>]*\\bid\\s*=\\s*["']${id}["']`, 'gi');
|
|
1220
|
+
} else if (classMatch) {
|
|
1221
|
+
const cls = classMatch[1].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1222
|
+
attrRe = new RegExp(`<[a-zA-Z][^>]*\\bclass\\s*=\\s*["'][^"']*(?<![\\w-])${cls}(?![\\w-])[^"']*["']`, 'gi');
|
|
1223
|
+
}
|
|
1224
|
+
if (!attrRe) return false;
|
|
1225
|
+
let m;
|
|
1226
|
+
while ((m = attrRe.exec(content)) !== null) {
|
|
1227
|
+
if (indexInSourceRanges(m.index, ranges)) return true;
|
|
1228
|
+
}
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Small circular indicator bound to an infinite pulse animation — the
|
|
1233
|
+
// decorative "live" dot. Gates: tiny (<= 16px square-ish), round
|
|
1234
|
+
// (border-radius >= 40% or pill values), and an infinite animation whose
|
|
1235
|
+
// keyframes vary opacity/scale/box-shadow (or a pulse/blink/ping name when
|
|
1236
|
+
// the keyframes aren't in the scanned text). Rotation-only animations
|
|
1237
|
+
// (spinners) never flag.
|
|
1238
|
+
//
|
|
1239
|
+
// Declarations for one selector are merged across rule blocks before the
|
|
1240
|
+
// predicate runs: size in the base rule plus the animation added in a
|
|
1241
|
+
// second block (or inside a matching @media block) is the construction
|
|
1242
|
+
// that ships. prefers-reduced-motion: reduce overrides are stripped first
|
|
1243
|
+
// so their animation resets don't mask the default experience. A dot whose
|
|
1244
|
+
// element sits inside a header/nav landmark is the hero liveness cliché
|
|
1245
|
+
// and is promoted to error severity; occurrences elsewhere keep the
|
|
1246
|
+
// registry default severity.
|
|
1247
|
+
function scanCssTextForPulsingDot(content) {
|
|
1248
|
+
const customProps = collectCssCustomProps(content);
|
|
1249
|
+
const keyframes = collectPulseKeyframes(content);
|
|
1250
|
+
const heroRanges = landmarkSourceRanges(content);
|
|
1251
|
+
const findings = [];
|
|
1252
|
+
const seen = new Set();
|
|
1253
|
+
|
|
1254
|
+
// Merge declarations per selector across rule blocks, approximating the
|
|
1255
|
+
// cascade: later declarations for the same property win. Comma lists are
|
|
1256
|
+
// split so `.a, .b { … }` contributes to both selectors. Comments are
|
|
1257
|
+
// stripped first so they neither pollute selector keys nor smuggle a
|
|
1258
|
+
// comma into the selector-list split.
|
|
1259
|
+
const scanText = stripReducedMotionBlocks(content).replace(/\/\*[\s\S]*?\*\//g, ' ');
|
|
1260
|
+
const merged = new Map();
|
|
1261
|
+
const ruleRe = new RegExp(CSS_RULE_BLOCK_SOURCE, 'g');
|
|
1262
|
+
let m;
|
|
1263
|
+
while ((m = ruleRe.exec(scanText)) !== null) {
|
|
1264
|
+
const decls = parseCssDeclBlock(m[2]);
|
|
1265
|
+
if (decls.size === 0) continue;
|
|
1266
|
+
for (const rawSelector of m[1].split(',')) {
|
|
1267
|
+
const selector = rawSelector.trim();
|
|
1268
|
+
if (!selector || selector.startsWith('@')) continue;
|
|
1269
|
+
let acc = merged.get(selector);
|
|
1270
|
+
if (!acc) {
|
|
1271
|
+
acc = new Map();
|
|
1272
|
+
merged.set(selector, acc);
|
|
1273
|
+
}
|
|
1274
|
+
for (const [prop, value] of decls) acc.set(prop, value);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
for (const [selector, decls] of merged) {
|
|
1279
|
+
const names = infiniteAnimationNames(decls);
|
|
1280
|
+
if (names.length === 0) continue;
|
|
1281
|
+
const pulseName = names.find(n => {
|
|
1282
|
+
const known = keyframes.get(n);
|
|
1283
|
+
if (known != null) return known;
|
|
1284
|
+
return /pulse|blink|ping/i.test(n);
|
|
1285
|
+
});
|
|
1286
|
+
if (!pulseName) continue;
|
|
1287
|
+
|
|
1288
|
+
const w = cssLengthToPx(resolveVarRefs(
|
|
1289
|
+
decls.get('width') || decls.get('inline-size') || '', customProps));
|
|
1290
|
+
const h = cssLengthToPx(resolveVarRefs(
|
|
1291
|
+
decls.get('height') || decls.get('block-size') || '', customProps));
|
|
1292
|
+
if (w == null || h == null || w < 2 || h < 2 || w > 16 || h > 16) continue;
|
|
1293
|
+
|
|
1294
|
+
const radius = resolveVarRefs(decls.get('border-radius') || '', customProps);
|
|
1295
|
+
if (!isRoundDotRadius(radius, w, h)) continue;
|
|
1296
|
+
|
|
1297
|
+
if (seen.has(selector)) continue;
|
|
1298
|
+
seen.add(selector);
|
|
1299
|
+
const inLandmark = selectorHitsLandmark(content, selector, heroRanges);
|
|
1300
|
+
findings.push({
|
|
1301
|
+
id: 'pulsing-dot',
|
|
1302
|
+
snippet: `${selector} — ${w}x${h}px dot with infinite "${pulseName}" animation${inLandmark ? ' in header/nav' : ''}`,
|
|
1303
|
+
selector,
|
|
1304
|
+
...(inLandmark ? { severity: 'error' } : {}),
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Tailwind utilities: animate-ping / animate-pulse on a tiny rounded-full
|
|
1309
|
+
// element declared entirely in the class attribute.
|
|
1310
|
+
const classRe = /class\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
|
|
1311
|
+
let cm;
|
|
1312
|
+
while ((cm = classRe.exec(content)) !== null) {
|
|
1313
|
+
const cls = cm[1] || cm[2] || '';
|
|
1314
|
+
const anim = cls.match(/\banimate-(ping|pulse)\b/);
|
|
1315
|
+
if (!anim) continue;
|
|
1316
|
+
if (!/\brounded-full\b/.test(cls)) continue;
|
|
1317
|
+
if (!/\b(?:w|h|size)-(?:1|1\.5|2|2\.5|3|3\.5|4)\b/.test(cls)) continue;
|
|
1318
|
+
const key = `tw:${cls}`;
|
|
1319
|
+
if (seen.has(key)) continue;
|
|
1320
|
+
seen.add(key);
|
|
1321
|
+
const inLandmark = indexInSourceRanges(cm.index, heroRanges);
|
|
1322
|
+
findings.push({
|
|
1323
|
+
id: 'pulsing-dot',
|
|
1324
|
+
snippet: `animate-${anim[1]} on tiny rounded-full element${inLandmark ? ' in header/nav' : ''}`,
|
|
1325
|
+
...(inLandmark ? { severity: 'error' } : {}),
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
return findings;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// Shape-assembled illustration: a large inline SVG composing a pictorial
|
|
1333
|
+
// scene from many primitive shapes (rect / circle / ellipse / polygon) in
|
|
1334
|
+
// several fill colors — the clip-art hero mascot. Gates keep the legitimate
|
|
1335
|
+
// SVG population out:
|
|
1336
|
+
// • icons and logos: intrinsic size gate (>= 200px on both axes, from
|
|
1337
|
+
// width/height attributes or the viewBox when no explicit size is set)
|
|
1338
|
+
// • charts / labeled diagrams: more than two <text>/<tspan> nodes exempts
|
|
1339
|
+
// the graphic (axis labels, callouts)
|
|
1340
|
+
// • line drawings / technical diagrams: primitive count < 8 or fewer
|
|
1341
|
+
// than 3 distinct fills never qualifies (stroke-only art has no fills)
|
|
1342
|
+
// • tiling background textures: any <pattern> definition exempts
|
|
1343
|
+
function scanHtmlForShapeAssembledIllustration(html) {
|
|
1344
|
+
const findings = [];
|
|
1345
|
+
const svgRe = /<svg\b[^>]*>[\s\S]*?<\/svg>/gi;
|
|
1346
|
+
let m;
|
|
1347
|
+
while ((m = svgRe.exec(html)) !== null) {
|
|
1348
|
+
const block = m[0];
|
|
1349
|
+
const openTag = (block.match(/^<svg\b[^>]*>/i) || [''])[0];
|
|
1350
|
+
|
|
1351
|
+
// Data-bearing or annotated graphics: axis labels and callout text
|
|
1352
|
+
// mark a chart or diagram, not a mascot.
|
|
1353
|
+
const textCount = (block.match(/<(?:text|tspan)\b/gi) || []).length;
|
|
1354
|
+
if (textCount > 2) continue;
|
|
1355
|
+
// Tiling texture definitions are decorative backgrounds, not scenes.
|
|
1356
|
+
if (/<pattern\b/i.test(block)) continue;
|
|
1357
|
+
|
|
1358
|
+
const primitives = (block.match(/<(?:rect|circle|ellipse|polygon)\b/gi) || []).length;
|
|
1359
|
+
if (primitives < 8) continue;
|
|
1360
|
+
|
|
1361
|
+
// Intrinsic size: explicit width/height attributes win; fall back to
|
|
1362
|
+
// the viewBox box. Percentage or missing sizes stay unresolvable on
|
|
1363
|
+
// that axis and the viewBox speaks for them.
|
|
1364
|
+
const attrDim = (name) => {
|
|
1365
|
+
// (?<![-\w]) keeps compound attributes like stroke-width from
|
|
1366
|
+
// masquerading as the svg's own width.
|
|
1367
|
+
const am = openTag.match(new RegExp(`(?<![-\\w])${name}\\s*=\\s*["']\\s*([\\d.]+)(?:px)?\\s*["']`, 'i'));
|
|
1368
|
+
return am ? parseFloat(am[1]) : null;
|
|
1369
|
+
};
|
|
1370
|
+
const vb = openTag.match(/\bviewBox\s*=\s*["']\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)\s*["']/i);
|
|
1371
|
+
const w = attrDim('width') ?? (vb ? parseFloat(vb[1]) : null);
|
|
1372
|
+
const h = attrDim('height') ?? (vb ? parseFloat(vb[2]) : null);
|
|
1373
|
+
if (w == null || h == null || w < 200 || h < 200) continue;
|
|
1374
|
+
|
|
1375
|
+
// Distinct fill paints (attributes and inline styles), excluding
|
|
1376
|
+
// non-paints. Multiple fills are what turn a shape pile into a scene.
|
|
1377
|
+
const fills = new Set();
|
|
1378
|
+
for (const fm of block.matchAll(/\bfill\s*[:=]\s*["']?\s*([^"';>}\s]+)/gi)) {
|
|
1379
|
+
const paint = fm[1].trim().toLowerCase();
|
|
1380
|
+
if (!paint || ['none', 'transparent', 'currentcolor', 'inherit'].includes(paint)) continue;
|
|
1381
|
+
fills.add(paint);
|
|
1382
|
+
}
|
|
1383
|
+
if (fills.size < 3) continue;
|
|
1384
|
+
|
|
1385
|
+
findings.push({
|
|
1386
|
+
id: 'shape-assembled-illustration',
|
|
1387
|
+
snippet: `inline <svg> scene: ${primitives} primitive shapes, ~${Math.round(w)}x${Math.round(h)}px, ${fills.size} fill colors`,
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
return findings;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
444
1393
|
/**
|
|
445
1394
|
* Regex-on-HTML checks shared between browser and Node page-level detection.
|
|
446
1395
|
* These don't need DOM access, just the raw HTML string.
|
|
@@ -474,6 +1423,16 @@ function checkHtmlPatterns(html) {
|
|
|
474
1423
|
findings.push({ id: 'gradient-text', snippet: 'bg-clip-text + bg-gradient (Tailwind)' });
|
|
475
1424
|
}
|
|
476
1425
|
|
|
1426
|
+
// --- Borders ---
|
|
1427
|
+
|
|
1428
|
+
// Side-tab accent stripe drawn as an absolutely-positioned pseudo-element
|
|
1429
|
+
// (no border property involved, so the element-level border checks and
|
|
1430
|
+
// the border-left regexes never see it).
|
|
1431
|
+
findings.push(...scanCssTextForPseudoStripe(html));
|
|
1432
|
+
|
|
1433
|
+
// Side-tab accent stripe drawn as a single-edge inset box-shadow.
|
|
1434
|
+
findings.push(...scanCssTextForInsetStripe(html));
|
|
1435
|
+
|
|
477
1436
|
// --- Layout ---
|
|
478
1437
|
|
|
479
1438
|
// Monotonous spacing
|
|
@@ -549,33 +1508,34 @@ function checkHtmlPatterns(html) {
|
|
|
549
1508
|
}
|
|
550
1509
|
}
|
|
551
1510
|
|
|
552
|
-
//
|
|
1511
|
+
// Pulsing status dots (tiny circular elements on infinite pulse animations)
|
|
1512
|
+
findings.push(...scanCssTextForPulsingDot(html));
|
|
553
1513
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const pxVals = [...val.matchAll(/(\d+)px|(?<![.\d])\b(0)\b(?![.\d])/g)].map(p => +(p[1] || p[2]));
|
|
566
|
-
if (pxVals.length >= 3 && pxVals[2] > 4) {
|
|
567
|
-
findings.push({ id: 'dark-glow', snippet: `Colored glow (rgb(${r},${g},${b})) on dark page` });
|
|
568
|
-
break;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
1514
|
+
// Shape-assembled illustrations (large pictorial SVGs built from primitives)
|
|
1515
|
+
findings.push(...scanHtmlForShapeAssembledIllustration(html));
|
|
1516
|
+
|
|
1517
|
+
// Auto-scrolling marquees (<marquee> or infinite horizontal loop animations)
|
|
1518
|
+
findings.push(...scanCssTextForMarquee(html));
|
|
1519
|
+
|
|
1520
|
+
// --- Dark glow / chromatic halo shadows ---
|
|
1521
|
+
|
|
1522
|
+
const glowHits = scanCssTextForGlow(html);
|
|
1523
|
+
if (glowHits.length > 0) {
|
|
1524
|
+
findings.push({ id: 'dark-glow', snippet: glowHits[0].snippet });
|
|
571
1525
|
}
|
|
572
1526
|
|
|
573
|
-
//
|
|
1527
|
+
// Radial-gradient background halo (gradient-drawn sibling of dark-glow)
|
|
1528
|
+
const haloHits = scanCssTextForRadialHalo(html);
|
|
1529
|
+
if (haloHits.length > 0) {
|
|
1530
|
+
findings.push({ id: 'radial-halo', snippet: haloHits[0].snippet });
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// --- Generated-UI tells: repeating-gradient stripes ---
|
|
574
1534
|
if (/repeating-(?:linear|radial|conic)-gradient\s*\(/i.test(html)) {
|
|
575
1535
|
findings.push({ id: 'repeating-stripes-gradient', snippet: 'repeating-gradient decorative stripes' });
|
|
576
1536
|
}
|
|
577
1537
|
|
|
578
|
-
// ---
|
|
1538
|
+
// --- Generated-UI tells: two-axis grid-line background ---
|
|
579
1539
|
// The Codex grid tell is two hairline `linear-gradient(... <color> 1px,
|
|
580
1540
|
// transparent 1px)` layers (one per axis) tiled by a repeating
|
|
581
1541
|
// `background-size` cell. Both signals must co-occur in the SAME style block
|
|
@@ -588,30 +1548,12 @@ function checkHtmlPatterns(html) {
|
|
|
588
1548
|
// in for the second axis. Colors like `oklch(96% 0.012 82 / 0.055)` carry
|
|
589
1549
|
// nested parens, so match the hairline stop directly rather than parsing
|
|
590
1550
|
// whole gradient layers.
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const bgDeclRe = /\bbackground(?:-image)?\s*:\s*([^;{}"']*)/gi;
|
|
595
|
-
const blockRe = /\{([^{}]*)\}|style\s*=\s*"([^"]*)"|style\s*=\s*'([^']*)'/gi;
|
|
596
|
-
let blk;
|
|
597
|
-
while ((blk = blockRe.exec(html)) !== null) {
|
|
598
|
-
const block = blk[1] || blk[2] || blk[3] || '';
|
|
599
|
-
if (!gridSizeRe.test(block)) continue;
|
|
600
|
-
let hairlineCount = 0;
|
|
601
|
-
let bm;
|
|
602
|
-
bgDeclRe.lastIndex = 0;
|
|
603
|
-
while ((bm = bgDeclRe.exec(block)) !== null) {
|
|
604
|
-
const stops = bm[1].match(hairlineRe);
|
|
605
|
-
if (stops) hairlineCount += stops.length;
|
|
606
|
-
}
|
|
607
|
-
if (hairlineCount >= 2) {
|
|
608
|
-
findings.push({ id: 'codex-grid-background', snippet: 'two-axis grid-line gradient background' });
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
1551
|
+
const gridHits = scanCssTextForGridBackground(html);
|
|
1552
|
+
if (gridHits.length > 0) {
|
|
1553
|
+
findings.push({ id: 'codex-grid-background', snippet: gridHits[0].snippet });
|
|
612
1554
|
}
|
|
613
1555
|
|
|
614
|
-
// ---
|
|
1556
|
+
// --- Generated-copy tells: "X theater" framing copy ---
|
|
615
1557
|
// Lives here (regex-on-HTML) rather than in the text-content analyzers so it
|
|
616
1558
|
// runs in the bundled browser path too, not just the CLI/static path.
|
|
617
1559
|
{
|
|
@@ -620,7 +1562,7 @@ function checkHtmlPatterns(html) {
|
|
|
620
1562
|
if (tm) findings.push({ id: 'theater-slop-phrase', snippet: `"${tm[0].trim()}"` });
|
|
621
1563
|
}
|
|
622
1564
|
|
|
623
|
-
// ---
|
|
1565
|
+
// --- Generated-UI tells: image hover transform ---
|
|
624
1566
|
// A CSS `img...:hover { transform: ... }` rule, or a Tailwind hover:scale /
|
|
625
1567
|
// hover:rotate / hover:translate utility on an <img>. Each distinct
|
|
626
1568
|
// mechanism is its own finding.
|
|
@@ -649,7 +1591,11 @@ function checkHtmlPatterns(html) {
|
|
|
649
1591
|
// `background: #abc`. Real browsers always decompose, so the fallback is
|
|
650
1592
|
// a no-op there.
|
|
651
1593
|
function readOwnBackgroundColor(el, computedStyle) {
|
|
652
|
-
|
|
1594
|
+
// Real browsers keep wide-gamut/computed color functions (oklch(), oklab(),
|
|
1595
|
+
// color-mix() results) in getComputedStyle output, which plain parseRgb
|
|
1596
|
+
// misses — a flat oklch button background would silently skip every
|
|
1597
|
+
// contrast check without the parseAnyColor fallback.
|
|
1598
|
+
const bg = parseRgb(computedStyle.backgroundColor) || parseAnyColor(computedStyle.backgroundColor);
|
|
653
1599
|
if (DETECTOR_IS_BROWSER || (bg && bg.a >= 0.1)) return bg;
|
|
654
1600
|
const rawStyle = el.getAttribute?.('style') || '';
|
|
655
1601
|
const bgMatch = rawStyle.match(/background(?:-color)?\s*:\s*([^;]+)/i);
|
|
@@ -671,6 +1617,18 @@ function readOwnBackgroundColor(el, computedStyle) {
|
|
|
671
1617
|
|
|
672
1618
|
function resolveBackground(el, win, customPropMap) {
|
|
673
1619
|
let current = el;
|
|
1620
|
+
// Translucent layers (0.1 < a < 1) found on the way down to an opaque
|
|
1621
|
+
// base. A browser composites these over the base; the old behavior
|
|
1622
|
+
// either returned them as-if-opaque (browser mode) or skipped them
|
|
1623
|
+
// entirely (static mode), both of which misstate the effective surface
|
|
1624
|
+
// for contrast checks (e.g. `background: color-mix(in oklab, var(--hot)
|
|
1625
|
+
// 16%, transparent)` chips on dark pages).
|
|
1626
|
+
const overlays = [];
|
|
1627
|
+
const flatten = (base) => {
|
|
1628
|
+
let acc = base;
|
|
1629
|
+
for (let i = overlays.length - 1; i >= 0; i--) acc = compositeColorOver(overlays[i], acc);
|
|
1630
|
+
return acc;
|
|
1631
|
+
};
|
|
674
1632
|
while (current && current.nodeType === 1) {
|
|
675
1633
|
const style = DETECTOR_IS_BROWSER ? getComputedStyle(current) : win.getComputedStyle(current);
|
|
676
1634
|
const bgImage = style.backgroundImage || '';
|
|
@@ -683,7 +1641,9 @@ function resolveBackground(el, win, customPropMap) {
|
|
|
683
1641
|
// decorative. The old behavior bailed on any gradient ancestor, which
|
|
684
1642
|
// caused massive false-positive contrast findings on grain-textured
|
|
685
1643
|
// body backgrounds.
|
|
686
|
-
|
|
1644
|
+
// Real browsers serialize wide-gamut computed values as oklab()/oklch()
|
|
1645
|
+
// (e.g. any color-mix() result), which plain parseRgb misses.
|
|
1646
|
+
let bg = parseRgb(style.backgroundColor) || parseAnyColor(style.backgroundColor);
|
|
687
1647
|
if (!DETECTOR_IS_BROWSER && (!bg || bg.a < 0.1)) {
|
|
688
1648
|
// jsdom returns literal "var(--X)" / "oklch(...)" strings. Resolve
|
|
689
1649
|
// through customPropMap so Tailwind v4 color tokens become RGB.
|
|
@@ -703,7 +1663,8 @@ function resolveBackground(el, win, customPropMap) {
|
|
|
703
1663
|
}
|
|
704
1664
|
|
|
705
1665
|
if (bg && bg.a > 0.1) {
|
|
706
|
-
if (
|
|
1666
|
+
if (bg.a >= 0.99) return flatten(bg);
|
|
1667
|
+
overlays.push(bg);
|
|
707
1668
|
}
|
|
708
1669
|
// No solid bg-color at this level. If THIS level has a gradient/url
|
|
709
1670
|
// with no underlying solid color we can read:
|
|
@@ -719,41 +1680,66 @@ function resolveBackground(el, win, customPropMap) {
|
|
|
719
1680
|
// bgs worth checking against).
|
|
720
1681
|
if (hasGradientOrUrl) {
|
|
721
1682
|
if (current.tagName === 'BODY' || current.tagName === 'HTML') {
|
|
722
|
-
return { r: 255, g: 255, b: 255, a: 1 };
|
|
1683
|
+
return flatten({ r: 255, g: 255, b: 255, a: 1 });
|
|
723
1684
|
}
|
|
724
1685
|
return null;
|
|
725
1686
|
}
|
|
726
1687
|
current = current.parentElement;
|
|
727
1688
|
}
|
|
728
|
-
return { r: 255, g: 255, b: 255 };
|
|
1689
|
+
return flatten({ r: 255, g: 255, b: 255, a: 1 });
|
|
729
1690
|
}
|
|
730
1691
|
|
|
731
1692
|
// Walk parents looking for a gradient background and return its color stops.
|
|
732
1693
|
// Used as a fallback when resolveBackground() returns null because the
|
|
733
1694
|
// effective background is a gradient (no single solid color to compare against).
|
|
734
|
-
function resolveGradientStops(el, win) {
|
|
1695
|
+
function resolveGradientStops(el, win, customPropMap) {
|
|
735
1696
|
let current = el;
|
|
736
1697
|
while (current && current.nodeType === 1) {
|
|
737
1698
|
const style = DETECTOR_IS_BROWSER ? getComputedStyle(current) : win.getComputedStyle(current);
|
|
738
1699
|
const bgImage = style.backgroundImage || '';
|
|
1700
|
+
let stops = null;
|
|
739
1701
|
if (bgImage && bgImage !== 'none' && /gradient/i.test(bgImage)) {
|
|
740
|
-
const
|
|
741
|
-
if (
|
|
1702
|
+
const parsed = parseGradientColors(bgImage);
|
|
1703
|
+
if (parsed.length > 0) stops = parsed;
|
|
742
1704
|
}
|
|
743
|
-
if (!DETECTOR_IS_BROWSER) {
|
|
1705
|
+
if (!stops && !DETECTOR_IS_BROWSER) {
|
|
744
1706
|
// jsdom doesn't decompose `background:` shorthand — peek at the raw inline style
|
|
745
1707
|
const rawStyle = current.getAttribute?.('style') || '';
|
|
746
1708
|
const bgMatch = rawStyle.match(/background(?:-image)?\s*:\s*([^;]+)/i);
|
|
747
1709
|
if (bgMatch && /gradient/i.test(bgMatch[1])) {
|
|
748
|
-
const
|
|
749
|
-
if (
|
|
1710
|
+
const parsed = parseGradientColors(bgMatch[1]);
|
|
1711
|
+
if (parsed.length > 0) stops = parsed;
|
|
750
1712
|
}
|
|
751
1713
|
}
|
|
1714
|
+
if (stops) return compositeGradientStops(stops, current, win, customPropMap);
|
|
752
1715
|
current = current.parentElement;
|
|
753
1716
|
}
|
|
754
1717
|
return null;
|
|
755
1718
|
}
|
|
756
1719
|
|
|
1720
|
+
// A translucent gradient stop (e.g. a faint `rgba(52,192,168,0.09)` accent
|
|
1721
|
+
// glow) paints over whatever surface sits beneath the gradient — the browser
|
|
1722
|
+
// composites it, so its effective color is far closer to the base than to the
|
|
1723
|
+
// full-opacity accent. Treating the stop as opaque flags every text child of a
|
|
1724
|
+
// softly-glowing section as low-contrast (issue #409 Case B). Composite each
|
|
1725
|
+
// alpha stop over the resolved surface beneath the gradient element. When that
|
|
1726
|
+
// surface isn't resolvable (another gradient above, no opaque ancestor), drop
|
|
1727
|
+
// the translucent stop rather than guess: a dropped stop can't manufacture a
|
|
1728
|
+
// false finding, and skipping beats a wrong ratio.
|
|
1729
|
+
function compositeGradientStops(stops, gradientEl, win, customPropMap) {
|
|
1730
|
+
const hasAlpha = stops.some(s => (s.a ?? 1) < 0.99);
|
|
1731
|
+
if (!hasAlpha) return stops;
|
|
1732
|
+
const base = resolveBackground(gradientEl.parentElement || gradientEl, win, customPropMap);
|
|
1733
|
+
const out = [];
|
|
1734
|
+
for (const s of stops) {
|
|
1735
|
+
const a = s.a ?? 1;
|
|
1736
|
+
if (a >= 0.99) { out.push(s); continue; }
|
|
1737
|
+
if (base) out.push(compositeColorOver(s, base));
|
|
1738
|
+
// else: unresolvable base — drop the translucent stop (skip, don't guess).
|
|
1739
|
+
}
|
|
1740
|
+
return out.length ? out : null;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
757
1743
|
// Parse a single CSS length token to pixels. Accepts "12px", "50%", a
|
|
758
1744
|
// shorthand like "12px 4px" (uses the first value), or empty / null.
|
|
759
1745
|
// Returns the pixel value, or null when the input is unparseable.
|
|
@@ -787,6 +1773,43 @@ function resolveBorderRadiusPx(el, style, widthPx, win) {
|
|
|
787
1773
|
|
|
788
1774
|
// Browser adapters — call getComputedStyle/getBoundingClientRect on live DOM
|
|
789
1775
|
|
|
1776
|
+
// Selected-state context for accent stripes. Only an actual selection
|
|
1777
|
+
// marker exempts the stripe as the standard active-item indicator:
|
|
1778
|
+
// aria-selected="true", aria-current (any non-false value), or an
|
|
1779
|
+
// active/current/selected class hint. Tab-strip MEMBERSHIP alone
|
|
1780
|
+
// ([role=tablist]/[role=tab]/.tabs ancestry, aria-selected="false")
|
|
1781
|
+
// deliberately does not — a chromatic stripe repeated on every tab in
|
|
1782
|
+
// the group, or on every menu item, is decoration, not state; the
|
|
1783
|
+
// selected item's own underline stays legal.
|
|
1784
|
+
function isTabContextElement(el) {
|
|
1785
|
+
if (!el) return false;
|
|
1786
|
+
try {
|
|
1787
|
+
if (el.closest?.('[aria-selected="true"], [aria-current]:not([aria-current="false"])')) return true;
|
|
1788
|
+
} catch { /* selector engine differences — fall through to class scan */ }
|
|
1789
|
+
let cur = el, depth = 0;
|
|
1790
|
+
while (cur && cur.nodeType === 1 && depth < 6) {
|
|
1791
|
+
const cls = String(cur.getAttribute?.('class') || cur.className || '');
|
|
1792
|
+
if (/(?:^|[\s_-])(?:active|current|selected)(?:$|[\s_-])/i.test(cls)) return true;
|
|
1793
|
+
cur = cur.parentElement;
|
|
1794
|
+
depth++;
|
|
1795
|
+
}
|
|
1796
|
+
return false;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
// Status-surface context for accent borders. On a live status/alert region
|
|
1800
|
+
// (role=status|alert|alertdialog|log, or aria-live=polite|assertive) a colored
|
|
1801
|
+
// single-edge border is the established severity-accent convention — a toast,
|
|
1802
|
+
// snackbar, or callout bar — not the decorative side-tab tell. The element
|
|
1803
|
+
// itself or a wrapping live region qualifies. This never fires from the
|
|
1804
|
+
// CSS-only / regex scanners, which have no role information.
|
|
1805
|
+
function isStatusContextElement(el) {
|
|
1806
|
+
if (!el) return false;
|
|
1807
|
+
try {
|
|
1808
|
+
if (el.closest?.('[role="status"], [role="alert"], [role="alertdialog"], [role="log"], [aria-live="polite"], [aria-live="assertive"]')) return true;
|
|
1809
|
+
} catch { /* selector engine differences — fall through */ }
|
|
1810
|
+
return false;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
790
1813
|
function checkElementBordersDOM(el) {
|
|
791
1814
|
const tag = el.tagName.toLowerCase();
|
|
792
1815
|
if (BORDER_SAFE_TAGS.has(tag)) return [];
|
|
@@ -799,7 +1822,105 @@ function checkElementBordersDOM(el) {
|
|
|
799
1822
|
widths[s] = parseFloat(style[`border${s}Width`]) || 0;
|
|
800
1823
|
colors[s] = style[`border${s}Color`] || '';
|
|
801
1824
|
}
|
|
802
|
-
|
|
1825
|
+
const ownBg = parseRgb(style.backgroundColor) || parseAnyColor(style.backgroundColor);
|
|
1826
|
+
return checkBorders(tag, widths, colors, parseFloat(style.borderRadius) || 0, {
|
|
1827
|
+
tabContext: isTabContextElement(el),
|
|
1828
|
+
statusContext: isStatusContextElement(el),
|
|
1829
|
+
badgeLike: !!(ownBg && (ownBg.a ?? 1) > 0.1),
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// Browser-side twin of scanCssTextForPseudoStripe. The text scanner reads
|
|
1834
|
+
// stylesheet source, so a stripe whose color only exists at runtime (an
|
|
1835
|
+
// inline per-card custom property, a JS-assigned var) or whose geometry
|
|
1836
|
+
// resolves in layout never matches it. In a real browser the pseudo-element's
|
|
1837
|
+
// computed style carries the actual used color and px geometry — check those
|
|
1838
|
+
// directly. Gates mirror the text scanner: 3-12px thick, chromatic fill,
|
|
1839
|
+
// spanning (nearly) the full edge; corner rounding on the host card is
|
|
1840
|
+
// irrelevant. Exemptions stay narrow: structural/prose tags, real selection
|
|
1841
|
+
// markers (isTabContextElement), and button/link affordances for the
|
|
1842
|
+
// horizontal variant.
|
|
1843
|
+
function checkElementPseudoStripeDOM(el) {
|
|
1844
|
+
const tag = el.tagName.toLowerCase();
|
|
1845
|
+
if (BORDER_SAFE_TAGS.has(tag) || tag === 'summary') return [];
|
|
1846
|
+
if (el.closest?.('nav, blockquote, pre')) return [];
|
|
1847
|
+
if (!isRenderedForBrowserRule(el)) return [];
|
|
1848
|
+
const rect = el.getBoundingClientRect();
|
|
1849
|
+
if (rect.width < 40 || rect.height < 20) return [];
|
|
1850
|
+
if (isTabContextElement(el)) return [];
|
|
1851
|
+
|
|
1852
|
+
const findings = [];
|
|
1853
|
+
for (const which of ['::before', '::after']) {
|
|
1854
|
+
let ps;
|
|
1855
|
+
try { ps = getComputedStyle(el, which); } catch { continue; }
|
|
1856
|
+
if (!ps || ps.content === 'none' || ps.content === '') continue;
|
|
1857
|
+
if (ps.position !== 'absolute' && ps.position !== 'fixed') continue;
|
|
1858
|
+
if ((parseFloat(ps.opacity) || 0) <= 0.01 || ps.display === 'none') continue;
|
|
1859
|
+
const w = parseFloat(ps.width) || 0;
|
|
1860
|
+
const h = parseFloat(ps.height) || 0;
|
|
1861
|
+
if (!(w > 0 && h > 0)) continue;
|
|
1862
|
+
|
|
1863
|
+
// Used values: for absolutely-positioned boxes the browser resolves
|
|
1864
|
+
// both edge offsets after layout, so left/right (and top/bottom) are
|
|
1865
|
+
// real distances, never "auto".
|
|
1866
|
+
const left = parseFloat(ps.left);
|
|
1867
|
+
const right = parseFloat(ps.right);
|
|
1868
|
+
const top = parseFloat(ps.top);
|
|
1869
|
+
const bottom = parseFloat(ps.bottom);
|
|
1870
|
+
const hugs = (v) => Number.isFinite(v) && v >= -2 && v <= 2;
|
|
1871
|
+
|
|
1872
|
+
let edge = null;
|
|
1873
|
+
let thickness = null;
|
|
1874
|
+
// Vertical stripe: narrow box spanning (nearly) the full height of the
|
|
1875
|
+
// host, hugging its left or right edge. "Nearly" tolerates the floating
|
|
1876
|
+
// variant that backs off each end by a small inset.
|
|
1877
|
+
if (w >= 3 && w <= 12 && h >= rect.height - 44 && h >= rect.height * 0.5) {
|
|
1878
|
+
edge = hugs(left) ? 'left' : hugs(right) ? 'right' : null;
|
|
1879
|
+
thickness = w;
|
|
1880
|
+
}
|
|
1881
|
+
// Horizontal stripe riding the top or bottom edge. Button/link-styled
|
|
1882
|
+
// hosts keep their underline affordances.
|
|
1883
|
+
if (!edge && h >= 3 && h <= 12 && w >= rect.width - 44 && w >= rect.width * 0.5) {
|
|
1884
|
+
const cls = String(el.getAttribute?.('class') || el.className || '');
|
|
1885
|
+
if (!/(?:^|[\s_-])(?:btn|button|link)(?:$|[\s\w_-])/i.test(cls)) {
|
|
1886
|
+
edge = hugs(top) ? 'top' : hugs(bottom) ? 'bottom' : null;
|
|
1887
|
+
thickness = h;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (!edge) continue;
|
|
1891
|
+
|
|
1892
|
+
const bg = parseRgb(ps.backgroundColor) || parseAnyColor(ps.backgroundColor);
|
|
1893
|
+
if (!bg || (bg.a ?? 1) < 0.1) continue;
|
|
1894
|
+
if (Math.max(bg.r, bg.g, bg.b) - Math.min(bg.r, bg.g, bg.b) < 30) continue;
|
|
1895
|
+
|
|
1896
|
+
findings.push({
|
|
1897
|
+
id: 'side-tab',
|
|
1898
|
+
snippet: `${classSelector(el)}${which} — absolute ${thickness}px pseudo-element stripe (${edge})`,
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
return findings;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// Full-cover surface pseudo (browser): a ::before/::after positioned
|
|
1905
|
+
// absolute/fixed whose box covers (nearly) the whole host and carries an
|
|
1906
|
+
// opaque background. That pseudo is the element's visible surface even
|
|
1907
|
+
// though the element's own background-color reads transparent — the nav-CTA
|
|
1908
|
+
// construction that otherwise escapes every own-background contrast gate.
|
|
1909
|
+
function readPseudoSurfaceDOM(el, rect) {
|
|
1910
|
+
for (const which of ['::before', '::after']) {
|
|
1911
|
+
let ps;
|
|
1912
|
+
try { ps = getComputedStyle(el, which); } catch { continue; }
|
|
1913
|
+
if (!ps || ps.content === 'none' || ps.content === '') continue;
|
|
1914
|
+
if (ps.position !== 'absolute' && ps.position !== 'fixed') continue;
|
|
1915
|
+
if (ps.display === 'none' || (parseFloat(ps.opacity) || 1) < 0.9) continue;
|
|
1916
|
+
const w = parseFloat(ps.width) || 0;
|
|
1917
|
+
const h = parseFloat(ps.height) || 0;
|
|
1918
|
+
if (w < rect.width - 4 || h < rect.height - 4) continue;
|
|
1919
|
+
const bg = parseRgb(ps.backgroundColor) || parseAnyColor(ps.backgroundColor);
|
|
1920
|
+
if (!bg || (bg.a ?? 1) < 0.9) continue;
|
|
1921
|
+
return bg;
|
|
1922
|
+
}
|
|
1923
|
+
return null;
|
|
803
1924
|
}
|
|
804
1925
|
|
|
805
1926
|
function checkElementColorsDOM(el) {
|
|
@@ -812,11 +1933,24 @@ function checkElementColorsDOM(el) {
|
|
|
812
1933
|
const style = getComputedStyle(el);
|
|
813
1934
|
const directText = [...el.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join('');
|
|
814
1935
|
const hasDirectText = directText.trim().length > 0;
|
|
815
|
-
|
|
1936
|
+
let effectiveBg = resolveBackground(el);
|
|
1937
|
+
let ownBg = readOwnBackgroundColor(el, style);
|
|
1938
|
+
if (!ownBg || (ownBg.a ?? 1) <= 0.5) {
|
|
1939
|
+
const pseudoSurface = readPseudoSurfaceDOM(el, rect);
|
|
1940
|
+
if (pseudoSurface) {
|
|
1941
|
+
ownBg = pseudoSurface;
|
|
1942
|
+
effectiveBg = pseudoSurface;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
816
1945
|
return checkColors({
|
|
817
1946
|
tag,
|
|
818
|
-
|
|
819
|
-
|
|
1947
|
+
// Chrome serializes computed colors specified in modern spaces as
|
|
1948
|
+
// oklch()/oklab() strings; without the parseAnyColor fallback the text
|
|
1949
|
+
// color comes back null and the low-contrast / gray-on-color checks
|
|
1950
|
+
// silently never run (the shipped miss: a nav CTA whose text color was
|
|
1951
|
+
// an oklch token near its own oklch background).
|
|
1952
|
+
textColor: parseRgb(style.color) || parseAnyColor(style.color),
|
|
1953
|
+
bgColor: ownBg,
|
|
820
1954
|
effectiveBg,
|
|
821
1955
|
effectiveBgStops: effectiveBg ? null : resolveGradientStops(el),
|
|
822
1956
|
fontSize: parseFloat(style.fontSize) || 16,
|
|
@@ -877,6 +2011,21 @@ function checkElementItalicSerifDOM(el) {
|
|
|
877
2011
|
});
|
|
878
2012
|
}
|
|
879
2013
|
|
|
2014
|
+
function domAccentDashPseudo(el) {
|
|
2015
|
+
for (const which of ['::before', '::after']) {
|
|
2016
|
+
let ps;
|
|
2017
|
+
try { ps = getComputedStyle(el, which); } catch { continue; }
|
|
2018
|
+
if (!ps || ps.content === 'none' || ps.content === '') continue;
|
|
2019
|
+
const w = parseFloat(ps.width) || 0;
|
|
2020
|
+
const h = parseFloat(ps.height) || 0;
|
|
2021
|
+
if (!(w >= 8 && w <= 80 && h >= 1 && h <= 6)) continue;
|
|
2022
|
+
const bg = parseRgb(ps.backgroundColor) || parseAnyColor(ps.backgroundColor);
|
|
2023
|
+
if (!bg || (bg.a ?? 1) < 0.1) continue;
|
|
2024
|
+
if (Math.max(bg.r, bg.g, bg.b) - Math.min(bg.r, bg.g, bg.b) >= 30) return true;
|
|
2025
|
+
}
|
|
2026
|
+
return false;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
880
2029
|
function checkElementHeroEyebrowDOM(el) {
|
|
881
2030
|
const tag = el.tagName.toLowerCase();
|
|
882
2031
|
if (tag !== 'h1') return [];
|
|
@@ -888,6 +2037,7 @@ function checkElementHeroEyebrowDOM(el) {
|
|
|
888
2037
|
headingTag: tag,
|
|
889
2038
|
headingText: el.textContent || '',
|
|
890
2039
|
headingFontSize: parseFloat(headStyle.fontSize) || 0,
|
|
2040
|
+
headingInApplicationContext: !!el.closest('[role="tabpanel"], [role="dialog"], [role="application"], dialog'),
|
|
891
2041
|
siblingTag: sibling.tagName.toLowerCase(),
|
|
892
2042
|
siblingText: sibling.textContent || '',
|
|
893
2043
|
siblingTextTransform: sibStyle.textTransform || '',
|
|
@@ -895,6 +2045,7 @@ function checkElementHeroEyebrowDOM(el) {
|
|
|
895
2045
|
siblingLetterSpacing: parseFloat(sibStyle.letterSpacing) || 0,
|
|
896
2046
|
siblingFontWeight: sibStyle.fontWeight || '',
|
|
897
2047
|
siblingColor: sibStyle.color || '',
|
|
2048
|
+
siblingHasAccentDashPseudo: domAccentDashPseudo(sibling),
|
|
898
2049
|
});
|
|
899
2050
|
}
|
|
900
2051
|
|
|
@@ -959,8 +2110,10 @@ function resolveVarRefs(raw, customPropMap, depth = 0) {
|
|
|
959
2110
|
// detector's contrast / color checks.
|
|
960
2111
|
function oklchToRgb(L, C, H) {
|
|
961
2112
|
const hRad = (H * Math.PI) / 180;
|
|
962
|
-
|
|
963
|
-
|
|
2113
|
+
return oklabToRgb(L, C * Math.cos(hRad), C * Math.sin(hRad));
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
function oklabToRgb(L, a, b) {
|
|
964
2117
|
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
965
2118
|
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
966
2119
|
const s_ = L - 0.0894841775 * a - 1.2914855480 * b;
|
|
@@ -980,13 +2133,178 @@ function oklchToRgb(L, C, H) {
|
|
|
980
2133
|
};
|
|
981
2134
|
}
|
|
982
2135
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
2136
|
+
function hslToRgb(h, s, l) {
|
|
2137
|
+
h = ((h % 360) + 360) % 360;
|
|
2138
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
2139
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
2140
|
+
const m0 = l - c / 2;
|
|
2141
|
+
const [r, g, b] =
|
|
2142
|
+
h < 60 ? [c, x, 0] :
|
|
2143
|
+
h < 120 ? [x, c, 0] :
|
|
2144
|
+
h < 180 ? [0, c, x] :
|
|
2145
|
+
h < 240 ? [0, x, c] :
|
|
2146
|
+
h < 300 ? [x, 0, c] : [c, 0, x];
|
|
2147
|
+
return {
|
|
2148
|
+
r: Math.round((r + m0) * 255),
|
|
2149
|
+
g: Math.round((g + m0) * 255),
|
|
2150
|
+
b: Math.round((b + m0) * 255),
|
|
2151
|
+
a: 1,
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
function hwbToRgb(h, w, bl) {
|
|
2156
|
+
if (w + bl >= 1) {
|
|
2157
|
+
const g = Math.round((w / (w + bl)) * 255);
|
|
2158
|
+
return { r: g, g, b: g, a: 1 };
|
|
2159
|
+
}
|
|
2160
|
+
const base = hslToRgb(h, 1, 0.5);
|
|
2161
|
+
const mix = (c) => Math.round(((c / 255) * (1 - w - bl) + w) * 255);
|
|
2162
|
+
return { r: mix(base.r), g: mix(base.g), b: mix(base.b), a: 1 };
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// Common CSS named colors — the handful that actually show up in generated
|
|
2166
|
+
// UIs, not the full 148-name spec list. Includes the achromatic names so a
|
|
2167
|
+
// named gray parses (and correctly reads as no-chroma) instead of being
|
|
2168
|
+
// treated as an unknown color.
|
|
2169
|
+
const CSS_NAMED_COLORS = {
|
|
2170
|
+
black: { r: 0, g: 0, b: 0 },
|
|
2171
|
+
white: { r: 255, g: 255, b: 255 },
|
|
2172
|
+
gray: { r: 128, g: 128, b: 128 },
|
|
2173
|
+
grey: { r: 128, g: 128, b: 128 },
|
|
2174
|
+
silver: { r: 192, g: 192, b: 192 },
|
|
2175
|
+
dimgray: { r: 105, g: 105, b: 105 },
|
|
2176
|
+
darkgray: { r: 169, g: 169, b: 169 },
|
|
2177
|
+
lightgray: { r: 211, g: 211, b: 211 },
|
|
2178
|
+
gainsboro: { r: 220, g: 220, b: 220 },
|
|
2179
|
+
whitesmoke: { r: 245, g: 245, b: 245 },
|
|
2180
|
+
red: { r: 255, g: 0, b: 0 },
|
|
2181
|
+
crimson: { r: 220, g: 20, b: 60 },
|
|
2182
|
+
tomato: { r: 255, g: 99, b: 71 },
|
|
2183
|
+
coral: { r: 255, g: 127, b: 80 },
|
|
2184
|
+
salmon: { r: 250, g: 128, b: 114 },
|
|
2185
|
+
orange: { r: 255, g: 165, b: 0 },
|
|
2186
|
+
gold: { r: 255, g: 215, b: 0 },
|
|
2187
|
+
yellow: { r: 255, g: 255, b: 0 },
|
|
2188
|
+
olive: { r: 128, g: 128, b: 0 },
|
|
2189
|
+
lime: { r: 0, g: 255, b: 0 },
|
|
2190
|
+
green: { r: 0, g: 128, b: 0 },
|
|
2191
|
+
teal: { r: 0, g: 128, b: 128 },
|
|
2192
|
+
turquoise: { r: 64, g: 224, b: 208 },
|
|
2193
|
+
cyan: { r: 0, g: 255, b: 255 },
|
|
2194
|
+
aqua: { r: 0, g: 255, b: 255 },
|
|
2195
|
+
skyblue: { r: 135, g: 206, b: 235 },
|
|
2196
|
+
dodgerblue: { r: 30, g: 144, b: 255 },
|
|
2197
|
+
blue: { r: 0, g: 0, b: 255 },
|
|
2198
|
+
navy: { r: 0, g: 0, b: 128 },
|
|
2199
|
+
indigo: { r: 75, g: 0, b: 130 },
|
|
2200
|
+
rebeccapurple: { r: 102, g: 51, b: 153 },
|
|
2201
|
+
purple: { r: 128, g: 0, b: 128 },
|
|
2202
|
+
violet: { r: 238, g: 130, b: 238 },
|
|
2203
|
+
orchid: { r: 218, g: 112, b: 214 },
|
|
2204
|
+
magenta: { r: 255, g: 0, b: 255 },
|
|
2205
|
+
fuchsia: { r: 255, g: 0, b: 255 },
|
|
2206
|
+
hotpink: { r: 255, g: 105, b: 180 },
|
|
2207
|
+
pink: { r: 255, g: 192, b: 203 },
|
|
2208
|
+
maroon: { r: 128, g: 0, b: 0 },
|
|
2209
|
+
};
|
|
2210
|
+
|
|
2211
|
+
// Split a string on top-level commas (ignoring commas nested in parens).
|
|
2212
|
+
function splitTopLevelCommas(str) {
|
|
2213
|
+
const parts = [];
|
|
2214
|
+
let depth = 0, start = 0;
|
|
2215
|
+
for (let i = 0; i < str.length; i++) {
|
|
2216
|
+
const ch = str[i];
|
|
2217
|
+
if (ch === '(') depth++;
|
|
2218
|
+
else if (ch === ')') depth = Math.max(0, depth - 1);
|
|
2219
|
+
else if (ch === ',' && depth === 0) {
|
|
2220
|
+
parts.push(str.slice(start, i).trim());
|
|
2221
|
+
start = i + 1;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
const tail = str.slice(start).trim();
|
|
2225
|
+
if (tail) parts.push(tail);
|
|
2226
|
+
return parts;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// Evaluate a CSS color-mix() expression to {r,g,b,a}. Returns null when
|
|
2230
|
+
// the expression can't be resolved (unresolved var(), unknown colors).
|
|
2231
|
+
//
|
|
2232
|
+
// Mixing is done with premultiplied alpha in sRGB regardless of the
|
|
2233
|
+
// declared interpolation space. That is exact for the dominant generated-UI
|
|
2234
|
+
// pattern — `color-mix(in oklab, <color> N%, transparent)` — where the
|
|
2235
|
+
// result is simply <color> at alpha N% in ANY rectangular space, and a
|
|
2236
|
+
// close-enough approximation for opaque-opaque mixes (the detector only
|
|
2237
|
+
// consumes these values for contrast/chroma thresholds, not for display).
|
|
2238
|
+
function parseColorMix(str) {
|
|
2239
|
+
const m = String(str).trim().match(/^color-mix\(/i);
|
|
2240
|
+
if (!m) return null;
|
|
2241
|
+
// Balanced-paren capture of the arguments.
|
|
2242
|
+
let depth = 0, end = -1;
|
|
2243
|
+
const open = str.indexOf('(');
|
|
2244
|
+
for (let i = open; i < str.length; i++) {
|
|
2245
|
+
if (str[i] === '(') depth++;
|
|
2246
|
+
else if (str[i] === ')') { depth--; if (depth === 0) { end = i; break; } }
|
|
2247
|
+
}
|
|
2248
|
+
if (end < 0) return null;
|
|
2249
|
+
const args = splitTopLevelCommas(str.slice(open + 1, end));
|
|
2250
|
+
if (args.length !== 3 || !/^in\s/i.test(args[0])) return null;
|
|
2251
|
+
|
|
2252
|
+
const parseComponent = (component) => {
|
|
2253
|
+
// Percentage may lead or trail the color per spec.
|
|
2254
|
+
let pct = null;
|
|
2255
|
+
let colorStr = component;
|
|
2256
|
+
const trail = component.match(/\s+([\d.]+)%$/);
|
|
2257
|
+
const lead = component.match(/^([\d.]+)%\s+/);
|
|
2258
|
+
if (trail) { pct = parseFloat(trail[1]); colorStr = component.slice(0, trail.index).trim(); }
|
|
2259
|
+
else if (lead) { pct = parseFloat(lead[1]); colorStr = component.slice(lead[0].length).trim(); }
|
|
2260
|
+
let color;
|
|
2261
|
+
if (/^transparent$/i.test(colorStr)) color = { r: 0, g: 0, b: 0, a: 0 };
|
|
2262
|
+
else color = parseAnyColor(colorStr);
|
|
2263
|
+
if (!color) return null;
|
|
2264
|
+
return { color, pct };
|
|
2265
|
+
};
|
|
2266
|
+
|
|
2267
|
+
const c1 = parseComponent(args[1]);
|
|
2268
|
+
const c2 = parseComponent(args[2]);
|
|
2269
|
+
if (!c1 || !c2) return null;
|
|
2270
|
+
let p1 = c1.pct, p2 = c2.pct;
|
|
2271
|
+
if (p1 == null && p2 == null) { p1 = 50; p2 = 50; }
|
|
2272
|
+
else if (p1 == null) p1 = 100 - p2;
|
|
2273
|
+
else if (p2 == null) p2 = 100 - p1;
|
|
2274
|
+
const sum = p1 + p2;
|
|
2275
|
+
if (sum <= 0) return null;
|
|
2276
|
+
// Per spec: weights normalize to sum; when sum < 100 the result alpha is
|
|
2277
|
+
// additionally scaled by sum/100.
|
|
2278
|
+
const w1 = p1 / sum, w2 = p2 / sum;
|
|
2279
|
+
const alphaScale = sum < 100 ? sum / 100 : 1;
|
|
2280
|
+
const a1 = c1.color.a ?? 1, a2 = c2.color.a ?? 1;
|
|
2281
|
+
const a = (a1 * w1 + a2 * w2) * alphaScale;
|
|
2282
|
+
if (a <= 0) return { r: 0, g: 0, b: 0, a: 0 };
|
|
2283
|
+
const mix = (ch) => Math.round((c1.color[ch] * a1 * w1 + c2.color[ch] * a2 * w2) / (a1 * w1 + a2 * w2));
|
|
2284
|
+
return { r: mix('r'), g: mix('g'), b: mix('b'), a: Math.min(1, a) };
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
// Composite a translucent color over an opaque(ish) base (simple
|
|
2288
|
+
// source-over in sRGB). Returns an opaque {r,g,b,a:1}.
|
|
2289
|
+
function compositeColorOver(top, base) {
|
|
2290
|
+
const a = top.a ?? 1;
|
|
2291
|
+
return {
|
|
2292
|
+
r: Math.round(top.r * a + base.r * (1 - a)),
|
|
2293
|
+
g: Math.round(top.g * a + base.g * (1 - a)),
|
|
2294
|
+
b: Math.round(top.b * a + base.b * (1 - a)),
|
|
2295
|
+
a: 1,
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
// Extended color parser: rgb/rgba/hex/oklch/oklab/hsl/hwb/color-mix/common
|
|
2300
|
+
// named colors. Returns null on no match. Use this when the input might be
|
|
2301
|
+
// any CSS color form; use plain parseRgb when you only expect computed rgb()
|
|
2302
|
+
// values from real browsers.
|
|
986
2303
|
function parseAnyColor(s) {
|
|
987
2304
|
if (!s || typeof s !== 'string') return null;
|
|
988
2305
|
const str = s.trim();
|
|
989
2306
|
if (str === 'transparent' || str === 'currentcolor' || str === 'inherit') return null;
|
|
2307
|
+
if (/^color-mix\(/i.test(str)) return parseColorMix(str);
|
|
990
2308
|
let m;
|
|
991
2309
|
m = str.match(/rgba?\(\s*(\d+(?:\.\d+)?)\s*,?\s*(\d+(?:\.\d+)?)\s*,?\s*(\d+(?:\.\d+)?)(?:\s*[,/]\s*([\d.]+))?\s*\)/);
|
|
992
2310
|
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 };
|
|
@@ -1024,6 +2342,41 @@ function parseAnyColor(s) {
|
|
|
1024
2342
|
}
|
|
1025
2343
|
return rgb;
|
|
1026
2344
|
}
|
|
2345
|
+
// OKLAB — a/b are signed axes; percentages map 100% → 0.4.
|
|
2346
|
+
m = str.match(/oklab\(\s*([\d.]+)(%?)\s+(-?[\d.]+)(%?)\s+(-?[\d.]+)(%?)(?:\s*\/\s*([\d.]+)(%)?)?\s*\)/i);
|
|
2347
|
+
if (m) {
|
|
2348
|
+
const L = m[2] === '%' ? parseFloat(m[1]) / 100 : parseFloat(m[1]);
|
|
2349
|
+
const a = m[4] === '%' ? parseFloat(m[3]) * 0.004 : parseFloat(m[3]);
|
|
2350
|
+
const b = m[6] === '%' ? parseFloat(m[5]) * 0.004 : parseFloat(m[5]);
|
|
2351
|
+
const rgb = oklabToRgb(L, a, b);
|
|
2352
|
+
if (m[7] !== undefined) {
|
|
2353
|
+
const alpha = parseFloat(m[7]);
|
|
2354
|
+
rgb.a = m[8] === '%' ? alpha / 100 : alpha;
|
|
2355
|
+
}
|
|
2356
|
+
return rgb;
|
|
2357
|
+
}
|
|
2358
|
+
// HSL/HSLA — comma or space syntax, optional deg on hue.
|
|
2359
|
+
m = str.match(/hsla?\(\s*(-?[\d.]+)(?:deg)?\s*[,\s]\s*([\d.]+)%\s*[,\s]\s*([\d.]+)%(?:\s*[,/]\s*([\d.]+)(%)?)?\s*\)/i);
|
|
2360
|
+
if (m) {
|
|
2361
|
+
const rgb = hslToRgb(parseFloat(m[1]), parseFloat(m[2]) / 100, parseFloat(m[3]) / 100);
|
|
2362
|
+
if (m[4] !== undefined) {
|
|
2363
|
+
const alpha = parseFloat(m[4]);
|
|
2364
|
+
rgb.a = m[5] === '%' ? alpha / 100 : alpha;
|
|
2365
|
+
}
|
|
2366
|
+
return rgb;
|
|
2367
|
+
}
|
|
2368
|
+
// HWB — hue whiteness% blackness%.
|
|
2369
|
+
m = str.match(/hwb\(\s*(-?[\d.]+)(?:deg)?\s+([\d.]+)%\s+([\d.]+)%(?:\s*\/\s*([\d.]+)(%)?)?\s*\)/i);
|
|
2370
|
+
if (m) {
|
|
2371
|
+
const rgb = hwbToRgb(parseFloat(m[1]), parseFloat(m[2]) / 100, parseFloat(m[3]) / 100);
|
|
2372
|
+
if (m[4] !== undefined) {
|
|
2373
|
+
const alpha = parseFloat(m[4]);
|
|
2374
|
+
rgb.a = m[5] === '%' ? alpha / 100 : alpha;
|
|
2375
|
+
}
|
|
2376
|
+
return rgb;
|
|
2377
|
+
}
|
|
2378
|
+
const named = CSS_NAMED_COLORS[str.toLowerCase()];
|
|
2379
|
+
if (named) return { ...named, a: 1 };
|
|
1027
2380
|
return null;
|
|
1028
2381
|
}
|
|
1029
2382
|
|
|
@@ -1036,7 +2389,7 @@ function parseColorResolved(str, customPropMap) {
|
|
|
1036
2389
|
return parseAnyColor(resolved);
|
|
1037
2390
|
}
|
|
1038
2391
|
|
|
1039
|
-
const
|
|
2392
|
+
const KICKER_SKIP_SELECTOR = [
|
|
1040
2393
|
'nav',
|
|
1041
2394
|
'form',
|
|
1042
2395
|
'table',
|
|
@@ -1055,7 +2408,7 @@ const REPEATED_KICKER_SKIP_SELECTOR = [
|
|
|
1055
2408
|
'[data-impeccable-allow-kickers]',
|
|
1056
2409
|
].join(',');
|
|
1057
2410
|
|
|
1058
|
-
const
|
|
2411
|
+
const KICKER_CARD_CONTEXT_SELECTOR = [
|
|
1059
2412
|
'article',
|
|
1060
2413
|
'button',
|
|
1061
2414
|
'a',
|
|
@@ -1073,23 +2426,32 @@ function cleanInlineText(el) {
|
|
|
1073
2426
|
.trim();
|
|
1074
2427
|
}
|
|
1075
2428
|
|
|
1076
|
-
function
|
|
1077
|
-
const item = heading.closest?.(
|
|
2429
|
+
function isKickerCardContext(heading, kicker) {
|
|
2430
|
+
const item = heading.closest?.(KICKER_CARD_CONTEXT_SELECTOR);
|
|
1078
2431
|
return Boolean(item && (!item.contains || item.contains(kicker)));
|
|
1079
2432
|
}
|
|
1080
2433
|
|
|
1081
|
-
|
|
2434
|
+
// Meta lines above headlines join category and date (or path crumbs) with
|
|
2435
|
+
// separator glyphs, or carry a year. A kicker is one short phrase; metadata
|
|
2436
|
+
// keeps its markers.
|
|
2437
|
+
const KICKER_META_TEXT_RE = /[·•|]|\s[\/›»>]\s|\b(19|20)\d{2}\b/;
|
|
2438
|
+
// Legal and document numbering: "Section 4.2", "Article IX", "§ 12.3",
|
|
2439
|
+
// dotted decimal outlines. The label identifies the clause, so it stays.
|
|
2440
|
+
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;
|
|
2441
|
+
|
|
2442
|
+
function isKickerCandidate(opts) {
|
|
1082
2443
|
const {
|
|
1083
|
-
|
|
2444
|
+
headingLevel,
|
|
1084
2445
|
headingText,
|
|
1085
2446
|
headingFontSize,
|
|
1086
2447
|
kickerTag,
|
|
1087
2448
|
kickerText,
|
|
1088
2449
|
kickerTextTransform,
|
|
2450
|
+
kickerFontVariant,
|
|
1089
2451
|
kickerFontSize,
|
|
1090
2452
|
kickerLetterSpacing,
|
|
1091
2453
|
} = opts;
|
|
1092
|
-
if (!
|
|
2454
|
+
if (!headingLevel || headingLevel > 4) return false;
|
|
1093
2455
|
if (!headingText || headingText.length < 3) return false;
|
|
1094
2456
|
if (/^\/[\w-]+/i.test(headingText.replace(/^"|"$/g, '').trim())) return false;
|
|
1095
2457
|
if (!(headingFontSize >= 20)) return false;
|
|
@@ -1097,9 +2459,13 @@ function isRepeatedKickerCandidate(opts) {
|
|
|
1097
2459
|
if (!['p', 'span', 'div', 'small'].includes(kickerTag)) return false;
|
|
1098
2460
|
if (!kickerText || kickerText.length < 2 || kickerText.length > 34) return false;
|
|
1099
2461
|
if (/^step\s*\d+/i.test(kickerText) || /^\d{1,2}$/.test(kickerText)) return false;
|
|
2462
|
+
if (KICKER_META_TEXT_RE.test(kickerText)) return false;
|
|
2463
|
+
if (KICKER_DOC_NUMBERING_RE.test(kickerText)) return false;
|
|
1100
2464
|
|
|
2465
|
+
const isSmallCaps = /small-caps/.test(kickerFontVariant || '');
|
|
1101
2466
|
const isUppercased = kickerTextTransform === 'uppercase'
|
|
1102
|
-
|| (/[A-Z]/.test(kickerText) && !/[a-z]/.test(kickerText))
|
|
2467
|
+
|| (/[A-Z]/.test(kickerText) && !/[a-z]/.test(kickerText))
|
|
2468
|
+
|| isSmallCaps;
|
|
1103
2469
|
if (!isUppercased) return false;
|
|
1104
2470
|
if (!(kickerFontSize > 0 && kickerFontSize <= 14)) return false;
|
|
1105
2471
|
const minTrackedSpacing = Math.max(1, kickerFontSize * 0.08);
|
|
@@ -1107,37 +2473,64 @@ function isRepeatedKickerCandidate(opts) {
|
|
|
1107
2473
|
return true;
|
|
1108
2474
|
}
|
|
1109
2475
|
|
|
1110
|
-
|
|
2476
|
+
// Resolve a heading level for the anchor element: 1-4 for h1-h4, aria-level
|
|
2477
|
+
// (default 2) for role="heading" elements, 0 otherwise.
|
|
2478
|
+
function kickerHeadingLevel(heading) {
|
|
2479
|
+
const tag = heading.tagName.toLowerCase();
|
|
2480
|
+
const byTag = /^h([1-6])$/.exec(tag);
|
|
2481
|
+
if (byTag) return parseInt(byTag[1], 10);
|
|
2482
|
+
const role = heading.getAttribute?.('role') || '';
|
|
2483
|
+
if (role.toLowerCase() !== 'heading') return 0;
|
|
2484
|
+
const ariaLevel = parseInt(heading.getAttribute?.('aria-level') || '', 10);
|
|
2485
|
+
return Number.isFinite(ariaLevel) && ariaLevel >= 1 ? ariaLevel : 2;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
function collectKickerCandidates(doc, getStyle, resolveLetterSpacing) {
|
|
1111
2489
|
const candidates = [];
|
|
1112
|
-
for (const heading of doc.querySelectorAll('h2, h3, h4')) {
|
|
1113
|
-
|
|
2490
|
+
for (const heading of doc.querySelectorAll('h1, h2, h3, h4, [role="heading"]')) {
|
|
2491
|
+
const headingLevel = kickerHeadingLevel(heading);
|
|
2492
|
+
if (!headingLevel || headingLevel > 4) continue;
|
|
2493
|
+
if (heading.closest?.(KICKER_SKIP_SELECTOR)) continue;
|
|
2494
|
+
// Application contexts (tab panels, dialogs) use compact context labels
|
|
2495
|
+
// above headings to describe state, not to decorate. Same carve-out the
|
|
2496
|
+
// hero-eyebrow rule makes.
|
|
2497
|
+
if (heading.closest?.('[role="tabpanel"], [role="dialog"], [role="application"], dialog')) continue;
|
|
1114
2498
|
const kicker = heading.previousElementSibling;
|
|
1115
|
-
if (!kicker || kicker.closest?.(
|
|
1116
|
-
if (
|
|
2499
|
+
if (!kicker || kicker.closest?.(KICKER_SKIP_SELECTOR)) continue;
|
|
2500
|
+
if (isKickerCardContext(heading, kicker)) continue;
|
|
1117
2501
|
|
|
1118
2502
|
const headingStyle = getStyle(heading);
|
|
1119
2503
|
const kickerStyle = getStyle(kicker);
|
|
2504
|
+
const headingTag = heading.tagName.toLowerCase();
|
|
1120
2505
|
const headingText = (heading.textContent || '').replace(/\s+/g, ' ').trim();
|
|
1121
2506
|
const kickerText = cleanInlineText(kicker) || (kicker.textContent || '').replace(/\s+/g, ' ').trim();
|
|
1122
2507
|
const headingFontSize = resolveLetterSpacing(headingStyle.fontSize || '', 16) || parseFloat(headingStyle.fontSize) || 0;
|
|
1123
2508
|
const kickerFontSize = resolveLetterSpacing(kickerStyle.fontSize || '', 16) || parseFloat(kickerStyle.fontSize) || 0;
|
|
1124
2509
|
const kickerLetterSpacing = resolveLetterSpacing(kickerStyle.letterSpacing || '', kickerFontSize);
|
|
1125
2510
|
|
|
1126
|
-
if (!
|
|
1127
|
-
|
|
2511
|
+
if (!isKickerCandidate({
|
|
2512
|
+
headingLevel,
|
|
1128
2513
|
headingText,
|
|
1129
2514
|
headingFontSize,
|
|
1130
2515
|
kickerTag: kicker.tagName.toLowerCase(),
|
|
1131
2516
|
kickerText,
|
|
1132
2517
|
kickerTextTransform: kickerStyle.textTransform || '',
|
|
2518
|
+
kickerFontVariant: `${kickerStyle.fontVariant || ''} ${kickerStyle.fontVariantCaps || ''}`,
|
|
1133
2519
|
kickerFontSize,
|
|
1134
2520
|
kickerLetterSpacing,
|
|
1135
2521
|
})) {
|
|
1136
2522
|
continue;
|
|
1137
2523
|
}
|
|
1138
2524
|
|
|
2525
|
+
// A tracked-caps eyebrow above a hero-scale h1 belongs to
|
|
2526
|
+
// hero-eyebrow-chip (which also covers the accent-bold and dash-prefix
|
|
2527
|
+
// stylings there). Stand down so one element gets one finding.
|
|
2528
|
+
if (headingTag === 'h1' && headingFontSize >= 48 && kickerLetterSpacing >= 1.6) {
|
|
2529
|
+
continue;
|
|
2530
|
+
}
|
|
2531
|
+
|
|
1139
2532
|
candidates.push({
|
|
1140
|
-
headingTag
|
|
2533
|
+
headingTag,
|
|
1141
2534
|
headingText: headingText.replace(/^"|"$/g, '').slice(0, 60),
|
|
1142
2535
|
kickerText: kickerText.slice(0, 40),
|
|
1143
2536
|
});
|
|
@@ -1145,13 +2538,177 @@ function collectRepeatedSectionKickerCandidates(doc, getStyle, resolveLetterSpac
|
|
|
1145
2538
|
return candidates;
|
|
1146
2539
|
}
|
|
1147
2540
|
|
|
1148
|
-
function
|
|
1149
|
-
const candidates =
|
|
2541
|
+
function checkKickerAboveHeadingDOM() {
|
|
2542
|
+
const candidates = collectKickerCandidates(
|
|
2543
|
+
document,
|
|
2544
|
+
(el) => getComputedStyle(el),
|
|
2545
|
+
(value, fontSize) => resolveLengthPx(value, fontSize) || 0,
|
|
2546
|
+
);
|
|
2547
|
+
return checkKickerAboveHeading({ candidates });
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
// ── Numbered section labels ─────────────────────────────────────────────────
|
|
2551
|
+
// Sibling of the kicker-above-heading rule: instead of a tracked uppercase word,
|
|
2552
|
+
// the section scaffold is a tiny numeric index riding beside each section
|
|
2553
|
+
// heading — bare and zero-padded, or an index joined to a short micro-label
|
|
2554
|
+
// by a separator glyph. The kicker rule deliberately excludes bare 1-2 digit
|
|
2555
|
+
// labels; this rule owns that shape.
|
|
2556
|
+
|
|
2557
|
+
const NUMBERED_LABEL_TAGS = new Set(['span', 'p', 'div', 'small', 'em', 'strong', 'b']);
|
|
2558
|
+
|
|
2559
|
+
// Returns { index, text } when the trimmed text reads as a section index
|
|
2560
|
+
// label, else null. Two accepted shapes: a zero-padded/two-digit bare index,
|
|
2561
|
+
// or a 1-2 digit index followed by a non-word separator and a short label.
|
|
2562
|
+
function parseNumberedLabelText(rawText) {
|
|
2563
|
+
const text = (rawText || '').replace(/\s+/g, ' ').trim();
|
|
2564
|
+
if (!text || text.length > 40) return null;
|
|
2565
|
+
let m = /^(\d{2})$/.exec(text);
|
|
2566
|
+
if (!m) m = /^(\d{1,2})\s*[^\w\s]\s*\S/.exec(text);
|
|
2567
|
+
if (!m) return null;
|
|
2568
|
+
const index = parseInt(m[1], 10);
|
|
2569
|
+
if (!Number.isFinite(index) || index > 40) return null;
|
|
2570
|
+
return { index, text };
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
function isNumberedSectionLabelCandidate(opts) {
|
|
2574
|
+
const {
|
|
2575
|
+
headingTag, headingText, headingFontSize,
|
|
2576
|
+
labelTag, labelIndex, labelText,
|
|
2577
|
+
labelFontSize, labelLetterSpacing, labelFontWeight,
|
|
2578
|
+
labelFontFamily, labelTextTransform, labelColor,
|
|
2579
|
+
} = opts;
|
|
2580
|
+
if (!['h2', 'h3', 'h4'].includes(headingTag)) return false;
|
|
2581
|
+
if (!headingText || headingText.length < 3) return false;
|
|
2582
|
+
if (!labelTag || !NUMBERED_LABEL_TAGS.has(labelTag)) return false;
|
|
2583
|
+
if (labelIndex == null || !labelText) return false;
|
|
2584
|
+
// Tiny rendered size is the tell — a display-scale section number is a
|
|
2585
|
+
// different (deliberate) device and stays legal.
|
|
2586
|
+
if (!(labelFontSize > 0 && labelFontSize <= 13)) return false;
|
|
2587
|
+
// The heading must be visibly larger where we can resolve its size.
|
|
2588
|
+
// clamp()/var() sizes come back unparseable (0) in the static engine —
|
|
2589
|
+
// the remaining gates carry the check there.
|
|
2590
|
+
if (headingFontSize > 0 && headingFontSize < labelFontSize * 1.3) return false;
|
|
2591
|
+
// Deliberate micro-label styling separates the scaffold from incidental
|
|
2592
|
+
// small text: mono face, bold weight, tracking, uppercase, or accent color.
|
|
2593
|
+
const weight = Number(labelFontWeight) || 400;
|
|
2594
|
+
return /mono/i.test(labelFontFamily || '')
|
|
2595
|
+
|| weight >= 600
|
|
2596
|
+
|| (labelLetterSpacing || 0) >= 0.5
|
|
2597
|
+
|| (labelTextTransform || '') === 'uppercase'
|
|
2598
|
+
|| isAccentColor(labelColor || '');
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
function collectNumberedSectionLabelCandidates(doc, getStyle, resolveLetterSpacing) {
|
|
2602
|
+
const candidates = [];
|
|
2603
|
+
const seenLabels = new Set();
|
|
2604
|
+
for (const heading of doc.querySelectorAll('h2, h3, h4')) {
|
|
2605
|
+
if (heading.closest?.(KICKER_SKIP_SELECTOR)) continue;
|
|
2606
|
+
// The index sits either directly before the heading, or before the
|
|
2607
|
+
// wrapper the heading leads (label | <div><h2>…</h2>…</div>).
|
|
2608
|
+
let label = heading.previousElementSibling;
|
|
2609
|
+
if (!label) {
|
|
2610
|
+
const parent = heading.parentElement;
|
|
2611
|
+
const firstChild = parent?.children?.[0];
|
|
2612
|
+
if (firstChild === heading) label = parent.previousElementSibling;
|
|
2613
|
+
}
|
|
2614
|
+
if (!label || seenLabels.has(label)) continue;
|
|
2615
|
+
if (label.closest?.(KICKER_SKIP_SELECTOR)) continue;
|
|
2616
|
+
if (HEADING_TAGS.has(label.tagName.toLowerCase())) continue;
|
|
2617
|
+
if (isKickerCardContext(heading, label)) continue;
|
|
2618
|
+
|
|
2619
|
+
const labelText = cleanInlineText(label) || (label.textContent || '').replace(/\s+/g, ' ').trim();
|
|
2620
|
+
const parsed = parseNumberedLabelText(labelText);
|
|
2621
|
+
if (!parsed) continue;
|
|
2622
|
+
|
|
2623
|
+
const headingStyle = getStyle(heading);
|
|
2624
|
+
const labelStyle = getStyle(label);
|
|
2625
|
+
const headingText = (heading.textContent || '').replace(/\s+/g, ' ').trim();
|
|
2626
|
+
const headingFontSize = resolveLetterSpacing(headingStyle.fontSize || '', 16) || parseFloat(headingStyle.fontSize) || 0;
|
|
2627
|
+
const labelFontSize = resolveLetterSpacing(labelStyle.fontSize || '', 16) || parseFloat(labelStyle.fontSize) || 0;
|
|
2628
|
+
|
|
2629
|
+
if (!isNumberedSectionLabelCandidate({
|
|
2630
|
+
headingTag: heading.tagName.toLowerCase(),
|
|
2631
|
+
headingText,
|
|
2632
|
+
headingFontSize,
|
|
2633
|
+
labelTag: label.tagName.toLowerCase(),
|
|
2634
|
+
labelIndex: parsed.index,
|
|
2635
|
+
labelText: parsed.text,
|
|
2636
|
+
labelFontSize,
|
|
2637
|
+
labelLetterSpacing: resolveLetterSpacing(labelStyle.letterSpacing || '', labelFontSize),
|
|
2638
|
+
labelFontWeight: labelStyle.fontWeight || '',
|
|
2639
|
+
labelFontFamily: labelStyle.fontFamily || '',
|
|
2640
|
+
labelTextTransform: labelStyle.textTransform || '',
|
|
2641
|
+
labelColor: labelStyle.color || '',
|
|
2642
|
+
})) {
|
|
2643
|
+
continue;
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
seenLabels.add(label);
|
|
2647
|
+
candidates.push({
|
|
2648
|
+
index: parsed.index,
|
|
2649
|
+
labelText: parsed.text.slice(0, 24),
|
|
2650
|
+
headingTag: heading.tagName.toLowerCase(),
|
|
2651
|
+
headingText: headingText.replace(/^"|"$/g, '').slice(0, 60),
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
return candidates;
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
function checkNumberedSectionLabels(opts) {
|
|
2658
|
+
const { candidates, minCount = 2 } = opts;
|
|
2659
|
+
if (!Array.isArray(candidates) || candidates.length < minCount) return [];
|
|
2660
|
+
// A repeated identical number is some other device; the scaffold counts up.
|
|
2661
|
+
const distinctIndices = new Set(candidates.map(c => c.index));
|
|
2662
|
+
if (distinctIndices.size < 2) return [];
|
|
2663
|
+
return candidates.map(candidate => ({
|
|
2664
|
+
id: 'numbered-section-labels',
|
|
2665
|
+
snippet: `tiny numbered label "${candidate.labelText}" beside ${candidate.headingTag} "${candidate.headingText}" (${candidates.length} on page)`,
|
|
2666
|
+
}));
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
function checkNumberedSectionLabelsFromDoc(doc, win) {
|
|
2670
|
+
const candidates = collectNumberedSectionLabelCandidates(
|
|
2671
|
+
doc,
|
|
2672
|
+
(el) => win.getComputedStyle(el),
|
|
2673
|
+
(value, fontSize) => resolveLengthPx(value, fontSize) || 0,
|
|
2674
|
+
);
|
|
2675
|
+
return checkNumberedSectionLabels({ candidates });
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
function checkNumberedSectionLabelsDOM() {
|
|
2679
|
+
const candidates = collectNumberedSectionLabelCandidates(
|
|
1150
2680
|
document,
|
|
1151
2681
|
(el) => getComputedStyle(el),
|
|
1152
2682
|
(value, fontSize) => resolveLengthPx(value, fontSize) || 0,
|
|
1153
2683
|
);
|
|
1154
|
-
return
|
|
2684
|
+
return checkNumberedSectionLabels({ candidates });
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
// Em-dash overuse (ADVISORY) — pure logic shared by the browser DOM check.
|
|
2688
|
+
// Mirrors the regex/static-HTML analyzer in engines/regex/detect-text.mjs:
|
|
2689
|
+
// two gates (absolute floor + density) so a long article using a few dashes is
|
|
2690
|
+
// left alone while a short, dash-per-clause page is flagged. Operates on
|
|
2691
|
+
// already-rendered text, so no HTML-entity decoding is needed (the browser has
|
|
2692
|
+
// resolved `—` to the literal glyph). Exported for jsdom unit tests.
|
|
2693
|
+
function checkEmDashOveruse(text) {
|
|
2694
|
+
const body = typeof text === 'string' ? text.replace(/\s+/g, ' ') : '';
|
|
2695
|
+
let count = 0;
|
|
2696
|
+
const re = /[—]|--(?=\S)/g;
|
|
2697
|
+
while (re.exec(body) !== null) count++;
|
|
2698
|
+
if (count < EM_DASH_FLOOR) return [];
|
|
2699
|
+
if (body.length > count * EM_DASH_CHARS_PER_DASH) return [];
|
|
2700
|
+
return [{ id: 'em-dash-overuse', snippet: `${count} em-dashes in body text` }];
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
function checkEmDashOveruseDOM() {
|
|
2704
|
+
const body = document.body;
|
|
2705
|
+
if (!body) return [];
|
|
2706
|
+
// innerText reflects rendered, visible text; fall back to textContent for
|
|
2707
|
+
// engines (jsdom) that don't compute innerText.
|
|
2708
|
+
const text = typeof body.innerText === 'string' && body.innerText
|
|
2709
|
+
? body.innerText
|
|
2710
|
+
: (body.textContent || '');
|
|
2711
|
+
return checkEmDashOveruse(text);
|
|
1155
2712
|
}
|
|
1156
2713
|
|
|
1157
2714
|
function checkElementMotionDOM(el) {
|
|
@@ -1170,7 +2727,14 @@ function checkElementMotionDOM(el) {
|
|
|
1170
2727
|
function checkElementGlowDOM(el) {
|
|
1171
2728
|
const tag = el.tagName.toLowerCase();
|
|
1172
2729
|
const style = getComputedStyle(el);
|
|
1173
|
-
|
|
2730
|
+
const boxShadow = style.boxShadow && style.boxShadow !== 'none' ? style.boxShadow : '';
|
|
2731
|
+
// text-shadow inherits: only check the element that introduces it, so one
|
|
2732
|
+
// declaration doesn't produce a finding on every descendant.
|
|
2733
|
+
let textShadow = style.textShadow && style.textShadow !== 'none' ? style.textShadow : '';
|
|
2734
|
+
if (textShadow && el.parentElement && getComputedStyle(el.parentElement).textShadow === textShadow) {
|
|
2735
|
+
textShadow = '';
|
|
2736
|
+
}
|
|
2737
|
+
if (!boxShadow && !textShadow) return [];
|
|
1174
2738
|
// Use parent's background — glow radiates outward, so the surrounding context matters
|
|
1175
2739
|
// If resolveBackground returns null (gradient), try to infer from the gradient colors
|
|
1176
2740
|
let parentBg = el.parentElement ? resolveBackground(el.parentElement) : resolveBackground(el);
|
|
@@ -1193,7 +2757,7 @@ function checkElementGlowDOM(el) {
|
|
|
1193
2757
|
cur = cur.parentElement;
|
|
1194
2758
|
}
|
|
1195
2759
|
}
|
|
1196
|
-
return checkGlow({ tag, boxShadow
|
|
2760
|
+
return checkGlow({ tag, boxShadow, textShadow, effectiveBg: parentBg });
|
|
1197
2761
|
}
|
|
1198
2762
|
|
|
1199
2763
|
function checkElementAIPaletteDOM(el) {
|
|
@@ -1253,6 +2817,131 @@ function checkElementAIPaletteDOM(el) {
|
|
|
1253
2817
|
return findings;
|
|
1254
2818
|
}
|
|
1255
2819
|
|
|
2820
|
+
// ─── Decorative radial spotlight glow ───────────────────────────────────────
|
|
2821
|
+
// A soft, low-opacity chromatic radial-gradient fading to transparent, painted
|
|
2822
|
+
// as a decorative wash behind a hero or section. The translucent sibling of the
|
|
2823
|
+
// `radial-halo` tell: `radial-halo` requires a saturated, near-opaque center on
|
|
2824
|
+
// a dark page; this catches the low-alpha "spotlight" the halo gate lets slip
|
|
2825
|
+
// (e.g. `radial-gradient(circle at 52% 38%, rgba(80,111,255,0.26),
|
|
2826
|
+
// transparent 44%)`). The two alpha bands are disjoint, so they never
|
|
2827
|
+
// double-report the same declaration.
|
|
2828
|
+
const SPOTLIGHT_COLOR_TOKEN_RE = /(?:rgba?|hsla?|oklch|oklab|lab|lch|hwb|color-mix)\([^)]*(?:\([^)]*\))?[^)]*\)|#[0-9a-f]{3,8}\b|\btransparent\b/i;
|
|
2829
|
+
|
|
2830
|
+
// Parse the FIRST non-repeating radial-gradient in a background value into its
|
|
2831
|
+
// ordered color stops. Each stop is { color: {r,g,b,a} | null, transparent }.
|
|
2832
|
+
// Returns null when there is no plain radial-gradient to read.
|
|
2833
|
+
function parseRadialGradientStops(value) {
|
|
2834
|
+
if (!value || !/radial-gradient/i.test(value)) return null;
|
|
2835
|
+
const gradRe = /(repeating-)?radial-gradient\(/gi;
|
|
2836
|
+
let g;
|
|
2837
|
+
while ((g = gradRe.exec(value)) !== null) {
|
|
2838
|
+
if (g[1]) continue; // repeating-* is a pattern, not a spotlight
|
|
2839
|
+
let depth = 0, end = -1;
|
|
2840
|
+
const open = value.indexOf('(', g.index);
|
|
2841
|
+
for (let i = open; i < value.length; i++) {
|
|
2842
|
+
if (value[i] === '(') depth++;
|
|
2843
|
+
else if (value[i] === ')') { depth--; if (depth === 0) { end = i; break; } }
|
|
2844
|
+
}
|
|
2845
|
+
if (end < 0) return null;
|
|
2846
|
+
const args = splitTopLevelCommas(value.slice(open + 1, end));
|
|
2847
|
+
// The optional prelude (shape / size / `at <pos>`) carries no color token.
|
|
2848
|
+
const stopArgs = args.filter(a => SPOTLIGHT_COLOR_TOKEN_RE.test(a));
|
|
2849
|
+
if (stopArgs.length < 2) return null;
|
|
2850
|
+
return stopArgs.map(a => {
|
|
2851
|
+
const tok = a.match(SPOTLIGHT_COLOR_TOKEN_RE);
|
|
2852
|
+
if (!tok) return { color: null, transparent: false };
|
|
2853
|
+
if (/^transparent$/i.test(tok[0])) return { color: null, transparent: true };
|
|
2854
|
+
const color = parseAnyColor(tok[0]);
|
|
2855
|
+
return { color, transparent: !!color && (color.a ?? 1) <= 0.05 };
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
return null;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
// Pure gate. `label` is a stable identifier the fixture test keys on.
|
|
2862
|
+
function checkRadialSpotlight({ gradientValue, width, height, label }) {
|
|
2863
|
+
const stops = parseRadialGradientStops(gradientValue);
|
|
2864
|
+
if (!stops || stops.length < 2) return [];
|
|
2865
|
+
|
|
2866
|
+
// Must fade OUT: the last stop is transparent / near-zero alpha. A gradient
|
|
2867
|
+
// between two visible surfaces is a real background, not a floating glow.
|
|
2868
|
+
const last = stops[stops.length - 1];
|
|
2869
|
+
const lastAlpha = last.transparent ? 0 : (last.color ? (last.color.a ?? 1) : 1);
|
|
2870
|
+
if (lastAlpha > 0.05) return [];
|
|
2871
|
+
|
|
2872
|
+
// The visible (non-transparent, parseable) color stops.
|
|
2873
|
+
const colored = stops.filter(s => !s.transparent && s.color && (s.color.a ?? 1) > 0.05);
|
|
2874
|
+
if (colored.length === 0) return [];
|
|
2875
|
+
// One soft glow, not a multi-color composition: at most two visible stops.
|
|
2876
|
+
if (colored.length > 2) return [];
|
|
2877
|
+
// Every visible stop must be LOW opacity. Any opaque stop means a real fill
|
|
2878
|
+
// or a saturated halo (`radial-halo`'s job), not this translucent spotlight.
|
|
2879
|
+
if (colored.some(s => (s.color.a ?? 1) >= 0.45)) return [];
|
|
2880
|
+
// At least one visible stop must be chromatic. A neutral (grayscale)
|
|
2881
|
+
// near-black / near-white vignette is a legitimate lighting move, exempt.
|
|
2882
|
+
const chromatic = colored.find(s => hasChroma(s.color, 24));
|
|
2883
|
+
if (!chromatic) return [];
|
|
2884
|
+
|
|
2885
|
+
// Decorative-scale gate. Badges, avatars, and actual small "lights" are
|
|
2886
|
+
// exempt; a spotlight glow only reads as slop when it washes a large surface.
|
|
2887
|
+
if (!(width >= 240 && height >= 160)) return [];
|
|
2888
|
+
|
|
2889
|
+
const alpha = (chromatic.color.a ?? 1).toFixed(2);
|
|
2890
|
+
const name = label || 'section';
|
|
2891
|
+
return [{
|
|
2892
|
+
id: 'radial-spotlight-glow',
|
|
2893
|
+
snippet: `radial-gradient spotlight glow "${name}" (${colorToHex(chromatic.color)} a${alpha} → transparent) on ${Math.round(width)}x${Math.round(height)} surface`,
|
|
2894
|
+
}];
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
// Read the raw radial-gradient source off an element's computed style, with a
|
|
2898
|
+
// fallback to the `background` shorthand and the inline style attribute for
|
|
2899
|
+
// engines that don't decompose the shorthand into backgroundImage.
|
|
2900
|
+
function elementGradientValue(style, el) {
|
|
2901
|
+
const bgImage = style.backgroundImage && style.backgroundImage !== 'none' ? style.backgroundImage : '';
|
|
2902
|
+
if (/radial-gradient/i.test(bgImage)) return bgImage;
|
|
2903
|
+
const bg = style.background || '';
|
|
2904
|
+
if (/radial-gradient/i.test(bg)) return bg;
|
|
2905
|
+
const rawStyle = el?.getAttribute?.('style') || '';
|
|
2906
|
+
const m = rawStyle.match(/background(?:-image)?\s*:\s*([^;]+)/i);
|
|
2907
|
+
if (m && /radial-gradient/i.test(m[1])) return m[1];
|
|
2908
|
+
return '';
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
function spotlightLabel(el) {
|
|
2912
|
+
const dataName = el.getAttribute?.('data-name');
|
|
2913
|
+
if (dataName) return dataName;
|
|
2914
|
+
if (typeof el.id === 'string' && el.id) return el.id;
|
|
2915
|
+
const cls = typeof el.className === 'string' ? el.className.trim().split(/\s+/)[0] : '';
|
|
2916
|
+
if (cls) return cls;
|
|
2917
|
+
return el.tagName ? el.tagName.toLowerCase() : 'section';
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
function checkElementRadialSpotlightDOM(el) {
|
|
2921
|
+
const style = getComputedStyle(el);
|
|
2922
|
+
const gradientValue = elementGradientValue(style, el);
|
|
2923
|
+
if (!gradientValue) return [];
|
|
2924
|
+
const rect = el.getBoundingClientRect();
|
|
2925
|
+
return checkRadialSpotlight({
|
|
2926
|
+
gradientValue,
|
|
2927
|
+
width: rect.width,
|
|
2928
|
+
height: rect.height,
|
|
2929
|
+
label: spotlightLabel(el),
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
function checkElementRadialSpotlight(el, style, tag, window) {
|
|
2934
|
+
const gradientValue = elementGradientValue(style, el);
|
|
2935
|
+
if (!gradientValue) return [];
|
|
2936
|
+
// Static engine does no layout — read explicit pixel dimensions from CSS.
|
|
2937
|
+
return checkRadialSpotlight({
|
|
2938
|
+
gradientValue,
|
|
2939
|
+
width: parseFloat(style.width) || 0,
|
|
2940
|
+
height: parseFloat(style.height) || 0,
|
|
2941
|
+
label: spotlightLabel(el),
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
|
|
1256
2945
|
const QUALITY_TEXT_TAGS = new Set(['p', 'li', 'td', 'th', 'dd', 'blockquote', 'figcaption']);
|
|
1257
2946
|
|
|
1258
2947
|
// Resolve a CSS font-size value to pixels by walking up the parent chain.
|
|
@@ -1374,6 +3063,55 @@ function textDescendantsFlushSides(el, rect) {
|
|
|
1374
3063
|
return flush;
|
|
1375
3064
|
}
|
|
1376
3065
|
|
|
3066
|
+
// Screen-reader-only ("visually hidden") text is exempt from the tiny-text
|
|
3067
|
+
// floors: it is never rendered, so its size is irrelevant. Detect the two
|
|
3068
|
+
// standard idioms — a known sr-only class on the element or an ancestor, and
|
|
3069
|
+
// the clip / 1px-box pattern. Works in both jsdom (declared styles) and the
|
|
3070
|
+
// browser (computed styles).
|
|
3071
|
+
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]';
|
|
3072
|
+
function isVisuallyHidden(el, style) {
|
|
3073
|
+
if ((el.matches && el.matches(SR_ONLY_SELECTOR)) || (el.closest && el.closest(SR_ONLY_SELECTOR))) return true;
|
|
3074
|
+
const pos = style.position || '';
|
|
3075
|
+
if (pos === 'absolute' || pos === 'fixed') {
|
|
3076
|
+
const clip = style.clip || '';
|
|
3077
|
+
const clipPath = style.clipPath || style.webkitClipPath || style['clip-path'] || '';
|
|
3078
|
+
if (/rect\(\s*0/.test(clip) || /inset\(\s*(?:50%|99|100%)/.test(clipPath)) return true;
|
|
3079
|
+
const w = parseFloat(style.width);
|
|
3080
|
+
const h = parseFloat(style.height);
|
|
3081
|
+
const overflow = style.overflow || '';
|
|
3082
|
+
if ((w === 1 || h === 1) && (overflow === 'hidden' || overflow === 'clip')) return true;
|
|
3083
|
+
}
|
|
3084
|
+
return false;
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
// Elements whose text is never painted: document metadata and script/style
|
|
3088
|
+
// payloads. Their JS / CSS / JSON-LD text satisfies `hasDirectText`, and on
|
|
3089
|
+
// sites that set `html { font-size: 62.5% }` their inherited computed size is
|
|
3090
|
+
// 10px — so the text-size floors flag them as tiny body copy even though
|
|
3091
|
+
// nothing renders (issue #408: dozens of phantom "10px body text" findings on
|
|
3092
|
+
// every Shopify page). Exclude them, plus anything the cascade resolves to
|
|
3093
|
+
// display:none / visibility:hidden. The jsdom path can't lay out, so the
|
|
3094
|
+
// tag/attribute-based exclusions carry the weight there; the display checks are
|
|
3095
|
+
// computed-style reads that resolve without layout in both adapters.
|
|
3096
|
+
const NON_RENDERED_TAGS = new Set([
|
|
3097
|
+
'script', 'style', 'title', 'noscript', 'template', 'head',
|
|
3098
|
+
'meta', 'link', 'base', 'param', 'source', 'track', 'datalist',
|
|
3099
|
+
'col', 'colgroup', 'map', 'area',
|
|
3100
|
+
]);
|
|
3101
|
+
function isNonRenderedText(el, tag, style) {
|
|
3102
|
+
const t = (tag || '').toLowerCase();
|
|
3103
|
+
if (NON_RENDERED_TAGS.has(t)) return true;
|
|
3104
|
+
// Descendants of <head> never render even when the tag itself would
|
|
3105
|
+
// (some sites nest <noscript>/<template> content there).
|
|
3106
|
+
if (el && el.closest && el.closest('head')) return true;
|
|
3107
|
+
if (style) {
|
|
3108
|
+
if (style.display === 'none') return true;
|
|
3109
|
+
const vis = style.visibility;
|
|
3110
|
+
if (vis === 'hidden' || vis === 'collapse') return true;
|
|
3111
|
+
}
|
|
3112
|
+
return false;
|
|
3113
|
+
}
|
|
3114
|
+
|
|
1377
3115
|
// Pure quality checks. Most run on computed CSS and DOM-only inputs (work in
|
|
1378
3116
|
// jsdom and the browser). Two checks (line-length, cramped-padding) gate on
|
|
1379
3117
|
// element rect dimensions, which jsdom can't compute — pass `rect: null` from
|
|
@@ -1384,8 +3122,13 @@ function textDescendantsFlushSides(el, rect) {
|
|
|
1384
3122
|
function checkQuality(opts) {
|
|
1385
3123
|
const { el, tag, style, hasDirectText, textLen, fontSize, lineHeightPx, letterSpacingPx, rect, lineMax = 80, viewportWidth = 0, win = null } = opts;
|
|
1386
3124
|
const findings = [];
|
|
1387
|
-
// Skip browser extension injected elements
|
|
1388
|
-
|
|
3125
|
+
// Skip browser extension injected elements. Read the id via getAttribute
|
|
3126
|
+
// whenever `el.id` is not a string: on a <form> (and other
|
|
3127
|
+
// [LegacyOverrideBuiltIns] hosts) a named control like <input name="id">
|
|
3128
|
+
// shadows the builtin `id` getter and returns the control element, whose
|
|
3129
|
+
// `.startsWith` is undefined and throws (issue #407 — every Shopify product
|
|
3130
|
+
// form ships an <input name="id">).
|
|
3131
|
+
const elId = typeof el.id === 'string' ? el.id : (el.getAttribute?.('id') || '');
|
|
1389
3132
|
if (elId.startsWith('claude-') || elId.startsWith('cic-')) return findings;
|
|
1390
3133
|
|
|
1391
3134
|
// --- Line length too long --- (browser-only: needs rect.width)
|
|
@@ -1653,11 +3396,67 @@ function checkQuality(opts) {
|
|
|
1653
3396
|
const skipTags = ['sub', 'sup', 'code', 'kbd', 'samp', 'var', 'caption', 'figcaption'];
|
|
1654
3397
|
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]');
|
|
1655
3398
|
const isUppercase = style.textTransform === 'uppercase';
|
|
1656
|
-
if (!skipTags.includes(tag) && !inUIContext && !isUppercase) {
|
|
3399
|
+
if (!skipTags.includes(tag) && !inUIContext && !isUppercase && !isNonRenderedText(el, tag, style)) {
|
|
1657
3400
|
findings.push({ id: 'tiny-text', snippet: `${fontSize}px body text` });
|
|
1658
3401
|
}
|
|
1659
3402
|
}
|
|
1660
3403
|
|
|
3404
|
+
// --- Undersized functional / UI text ---
|
|
3405
|
+
// Complements `tiny-text` above, which owns long body copy and deliberately
|
|
3406
|
+
// EXEMPTS the UI furniture layer (nav, footer, links, buttons, labels,
|
|
3407
|
+
// uppercase micro-labels). This rule targets exactly that blind spot: the
|
|
3408
|
+
// interactive and short content-bearing text — nav items, buttons, labels,
|
|
3409
|
+
// table cells, meta rows, timecodes — shipped below an 11px floor.
|
|
3410
|
+
//
|
|
3411
|
+
// The live failure it closes: a build shipped its entire furniture layer at
|
|
3412
|
+
// 8px, and the design hook waved it through because 8px had been added to
|
|
3413
|
+
// the DESIGN.md size ramp. Being on the ramp is a token argument, not a
|
|
3414
|
+
// legibility one, so this rule ignores the design system entirely — a value
|
|
3415
|
+
// on the ramp is still flagged.
|
|
3416
|
+
//
|
|
3417
|
+
// Floors: 11px for anything functional. The floor holds inside a footer;
|
|
3418
|
+
// only NON-interactive legal smallprint gets the softer 10px floor. Exempts
|
|
3419
|
+
// sup/sub, visually-hidden (sr-only) text, and code/terminal contexts.
|
|
3420
|
+
// Uppercase letterspaced micro-labels are still functional — not exempt.
|
|
3421
|
+
{
|
|
3422
|
+
const directText = [...el.childNodes]
|
|
3423
|
+
.filter(n => n.nodeType === 3)
|
|
3424
|
+
.map(n => n.textContent || '')
|
|
3425
|
+
.join('')
|
|
3426
|
+
.replace(/\s+/g, ' ')
|
|
3427
|
+
.trim();
|
|
3428
|
+
const dtLen = directText.length;
|
|
3429
|
+
// `option` renders (in native select popups) so it stays a local skip;
|
|
3430
|
+
// script/style/title/noscript/head-descendants and display:none /
|
|
3431
|
+
// visibility:hidden are handled by isNonRenderedText (shared with tiny-text).
|
|
3432
|
+
const UI_SKIP_TAGS = new Set(['sub', 'sup', 'option']);
|
|
3433
|
+
// jsdom resolves the parent chain in resolveFontSizePx, so em/rem/%-sized
|
|
3434
|
+
// text that computes at or above the floor never reaches here. The browser
|
|
3435
|
+
// adapter additionally catches values only resolvable with real layout
|
|
3436
|
+
// (e.g. viewport-relative units, cascade winners set in linked sheets).
|
|
3437
|
+
if (fontSize > 0 && fontSize < 11 && dtLen >= 2 && !UI_SKIP_TAGS.has(tag) && !isNonRenderedText(el, tag, style)) {
|
|
3438
|
+
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]';
|
|
3439
|
+
const isExemptContext = (el.matches && el.matches(EXEMPT_CONTEXT)) || (el.closest && el.closest(EXEMPT_CONTEXT));
|
|
3440
|
+
if (!isExemptContext && !isVisuallyHidden(el, style)) {
|
|
3441
|
+
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]';
|
|
3442
|
+
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]';
|
|
3443
|
+
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]';
|
|
3444
|
+
const isInteractive = (el.matches && el.matches(INTERACTIVE)) || (el.closest && el.closest(INTERACTIVE));
|
|
3445
|
+
const isFurniture = (el.matches && el.matches(FURNITURE)) || (el.closest && el.closest(FURNITURE));
|
|
3446
|
+
const isSmallprint = (el.matches && el.matches(SMALLPRINT)) || (el.closest && el.closest(SMALLPRINT));
|
|
3447
|
+
const floor = (!isInteractive && isSmallprint) ? 10 : 11;
|
|
3448
|
+
// Fire on functional text only: interactive, structural furniture, or
|
|
3449
|
+
// any short (<=20-char) run — the label / meta / timecode shape. Long
|
|
3450
|
+
// non-furniture body copy stays with `tiny-text`, so the two rules
|
|
3451
|
+
// never double-flag the same element.
|
|
3452
|
+
if (fontSize < floor && (isInteractive || isFurniture || dtLen <= 20)) {
|
|
3453
|
+
const excerpt = directText.slice(0, 40);
|
|
3454
|
+
findings.push({ id: 'undersized-ui-text', snippet: `${fontSize}px functional text "${excerpt}" (below ${floor}px floor)` });
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
|
|
1661
3460
|
// --- All-caps body text ---
|
|
1662
3461
|
if (hasDirectText && textLen > 30 && style.textTransform === 'uppercase') {
|
|
1663
3462
|
if (!['h1','h2','h3','h4','h5','h6'].includes(tag)) {
|
|
@@ -1750,7 +3549,7 @@ function checkElementQuality(el, style, tag, window) {
|
|
|
1750
3549
|
return checkQuality({ el, tag, style, hasDirectText, textLen, fontSize, lineHeightPx, letterSpacingPx, rect: null, win: window });
|
|
1751
3550
|
}
|
|
1752
3551
|
|
|
1753
|
-
function checkElementBorders(tag, style, overrides, resolvedRadius) {
|
|
3552
|
+
function checkElementBorders(tag, style, overrides, resolvedRadius, el = null) {
|
|
1754
3553
|
const sides = ['Top', 'Right', 'Bottom', 'Left'];
|
|
1755
3554
|
const widths = {}, colors = {};
|
|
1756
3555
|
for (const s of sides) {
|
|
@@ -1777,7 +3576,12 @@ function checkElementBorders(tag, style, overrides, resolvedRadius) {
|
|
|
1777
3576
|
const radius = resolvedRadius != null
|
|
1778
3577
|
? resolvedRadius
|
|
1779
3578
|
: (parseFloat(style.borderRadius) || 0);
|
|
1780
|
-
|
|
3579
|
+
const ownBg = parseAnyColor(style.backgroundColor);
|
|
3580
|
+
return checkBorders(tag, widths, colors, radius, {
|
|
3581
|
+
tabContext: isTabContextElement(el),
|
|
3582
|
+
statusContext: isStatusContextElement(el),
|
|
3583
|
+
badgeLike: !!(ownBg && (ownBg.a ?? 1) > 0.1),
|
|
3584
|
+
});
|
|
1781
3585
|
}
|
|
1782
3586
|
|
|
1783
3587
|
function checkElementColors(el, style, tag, window, customPropMap, hasAnchorInheritRule) {
|
|
@@ -1818,12 +3622,32 @@ function checkElementColors(el, style, tag, window, customPropMap, hasAnchorInhe
|
|
|
1818
3622
|
}
|
|
1819
3623
|
}
|
|
1820
3624
|
|
|
3625
|
+
// Own background: resolve var()/oklch() tokens through the custom-property
|
|
3626
|
+
// map first (mirrors the textColor path above). Without this a chip whose
|
|
3627
|
+
// background is `var(--sev)` reads as no-own-bg in the static engine and
|
|
3628
|
+
// the styled-control contrast exception never engages.
|
|
3629
|
+
let ownBg = (customPropMap ? parseColorResolved(style.backgroundColor, customPropMap) : null)
|
|
3630
|
+
|| readOwnBackgroundColor(el, style);
|
|
3631
|
+
|
|
3632
|
+
// Full-cover surface pseudo (static): the cascade pass marks elements
|
|
3633
|
+
// whose ::before/::after paints an opaque covering surface. When the
|
|
3634
|
+
// element itself has no usable own background, that pseudo is the real
|
|
3635
|
+
// surface for contrast purposes.
|
|
3636
|
+
let finalEffectiveBg = effectiveBg;
|
|
3637
|
+
if ((!ownBg || (ownBg.a ?? 1) <= 0.5) && typeof window.getPseudoSurface === 'function') {
|
|
3638
|
+
const pseudoSurface = window.getPseudoSurface(el);
|
|
3639
|
+
if (pseudoSurface) {
|
|
3640
|
+
ownBg = pseudoSurface;
|
|
3641
|
+
finalEffectiveBg = pseudoSurface;
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
|
|
1821
3645
|
return checkColors({
|
|
1822
3646
|
tag,
|
|
1823
3647
|
textColor,
|
|
1824
|
-
bgColor:
|
|
1825
|
-
effectiveBg,
|
|
1826
|
-
effectiveBgStops:
|
|
3648
|
+
bgColor: ownBg,
|
|
3649
|
+
effectiveBg: finalEffectiveBg,
|
|
3650
|
+
effectiveBgStops: finalEffectiveBg ? null : resolveGradientStops(el, window, customPropMap),
|
|
1827
3651
|
fontSize: parseFloat(style.fontSize) || 16,
|
|
1828
3652
|
fontWeight: parseInt(style.fontWeight) || 400,
|
|
1829
3653
|
hasDirectText,
|
|
@@ -1834,6 +3658,50 @@ function checkElementColors(el, style, tag, window, customPropMap, hasAnchorInhe
|
|
|
1834
3658
|
});
|
|
1835
3659
|
}
|
|
1836
3660
|
|
|
3661
|
+
// Static-engine adapter for hover-state contrast. Relies on the static
|
|
3662
|
+
// cascade's hover pass (css-cascade.mjs) exposing a per-element hover style
|
|
3663
|
+
// via window.getHoverStyle — present only when a :hover rule changed the
|
|
3664
|
+
// element's color or background-color relative to its resting state.
|
|
3665
|
+
function checkElementHoverContrast(el, style, tag, window) {
|
|
3666
|
+
if (typeof window.getHoverStyle !== 'function') return [];
|
|
3667
|
+
const hover = window.getHoverStyle(el);
|
|
3668
|
+
if (!hover) return [];
|
|
3669
|
+
|
|
3670
|
+
const directText = [...el.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join('');
|
|
3671
|
+
if (directText.trim().length === 0) return [];
|
|
3672
|
+
|
|
3673
|
+
const textColor = parseAnyColor(hover.color);
|
|
3674
|
+
if (!textColor || (textColor.a != null && textColor.a < 1)) return [];
|
|
3675
|
+
|
|
3676
|
+
const restingOwnBg = parseAnyColor(style.backgroundColor);
|
|
3677
|
+
const hoverOwnBg = parseAnyColor(hover.backgroundColor);
|
|
3678
|
+
const ownBg = hoverOwnBg || restingOwnBg;
|
|
3679
|
+
|
|
3680
|
+
// Effective hover background: the element's own hover bg composited over
|
|
3681
|
+
// whatever sits underneath. Bail when the surface can't be resolved to a
|
|
3682
|
+
// solid color — gradient ancestors are handled (as at rest) by the
|
|
3683
|
+
// resting-state check, not duplicated here.
|
|
3684
|
+
let bg = null;
|
|
3685
|
+
if (ownBg && ownBg.a >= 0.99) {
|
|
3686
|
+
bg = ownBg;
|
|
3687
|
+
} else {
|
|
3688
|
+
const under = resolveBackground(el.parentElement || el, window, null);
|
|
3689
|
+
if (!under) return [];
|
|
3690
|
+
bg = ownBg && ownBg.a > 0.1 ? compositeColorOver(ownBg, under) : under;
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
return checkHoverContrast({
|
|
3694
|
+
tag,
|
|
3695
|
+
textColor,
|
|
3696
|
+
bg,
|
|
3697
|
+
ownBgAlpha: ownBg ? ownBg.a ?? 1 : null,
|
|
3698
|
+
fontSize: parseFloat(style.fontSize) || 16,
|
|
3699
|
+
fontWeight: parseInt(style.fontWeight) || 400,
|
|
3700
|
+
hasDirectText: true,
|
|
3701
|
+
isEmojiOnly: isEmojiOnlyText(directText),
|
|
3702
|
+
});
|
|
3703
|
+
}
|
|
3704
|
+
|
|
1837
3705
|
function checkElementIconTile(el, tag, window) {
|
|
1838
3706
|
if (!HEADING_TAGS.has(tag)) return [];
|
|
1839
3707
|
const sibling = el.previousElementSibling;
|
|
@@ -1902,7 +3770,8 @@ function checkElementHeroEyebrow(el, style, tag, window, customPropMap) {
|
|
|
1902
3770
|
return checkHeroEyebrow({
|
|
1903
3771
|
headingTag: tag,
|
|
1904
3772
|
headingText: el.textContent || '',
|
|
1905
|
-
headingFontSize:
|
|
3773
|
+
headingFontSize: resolveHeroHeadingSizePx(headingFontSizeRaw),
|
|
3774
|
+
headingInApplicationContext: !!el.closest?.('[role="tabpanel"], [role="dialog"], [role="application"], dialog'),
|
|
1906
3775
|
siblingTag: sibling.tagName.toLowerCase(),
|
|
1907
3776
|
siblingText: sibling.textContent || '',
|
|
1908
3777
|
siblingTextTransform: sibStyle.textTransform || '',
|
|
@@ -1910,16 +3779,21 @@ function checkElementHeroEyebrow(el, style, tag, window, customPropMap) {
|
|
|
1910
3779
|
siblingLetterSpacing: resolveLengthPx(letterSpacingRaw, siblingFontSize) || 0,
|
|
1911
3780
|
siblingFontWeight: fontWeightRaw || '',
|
|
1912
3781
|
siblingColor: colorRaw || '',
|
|
3782
|
+
// Static cascade marks elements matched by a ::before/::after rule
|
|
3783
|
+
// whose geometry is a short chromatic dash (css-cascade.mjs).
|
|
3784
|
+
siblingHasAccentDashPseudo: typeof window.hasAccentDashPseudo === 'function'
|
|
3785
|
+
? window.hasAccentDashPseudo(sibling)
|
|
3786
|
+
: false,
|
|
1913
3787
|
});
|
|
1914
3788
|
}
|
|
1915
3789
|
|
|
1916
|
-
function
|
|
1917
|
-
const candidates =
|
|
3790
|
+
function checkKickerAboveHeadingFromDoc(doc, win) {
|
|
3791
|
+
const candidates = collectKickerCandidates(
|
|
1918
3792
|
doc,
|
|
1919
3793
|
(el) => win.getComputedStyle(el),
|
|
1920
3794
|
(value, fontSize) => resolveLengthPx(value, fontSize) || 0,
|
|
1921
3795
|
);
|
|
1922
|
-
return
|
|
3796
|
+
return checkKickerAboveHeading({ candidates });
|
|
1923
3797
|
}
|
|
1924
3798
|
|
|
1925
3799
|
function checkElementMotion(tag, style) {
|
|
@@ -1933,8 +3807,10 @@ function checkElementMotion(tag, style) {
|
|
|
1933
3807
|
}
|
|
1934
3808
|
|
|
1935
3809
|
function checkElementGlow(tag, style, effectiveBg) {
|
|
1936
|
-
|
|
1937
|
-
|
|
3810
|
+
const boxShadow = style.boxShadow && style.boxShadow !== 'none' ? style.boxShadow : '';
|
|
3811
|
+
const textShadow = style.textShadow && style.textShadow !== 'none' ? style.textShadow : '';
|
|
3812
|
+
if (!boxShadow && !textShadow) return [];
|
|
3813
|
+
return checkGlow({ tag, boxShadow, textShadow, effectiveBg });
|
|
1938
3814
|
}
|
|
1939
3815
|
|
|
1940
3816
|
// ─── Section 6: Page-Level Checks ───────────────────────────────────────────
|
|
@@ -2044,6 +3920,157 @@ function checkLayout() {
|
|
|
2044
3920
|
return findings;
|
|
2045
3921
|
}
|
|
2046
3922
|
|
|
3923
|
+
// Heading rhythm (browser-only): a heading binds to the content it
|
|
3924
|
+
// introduces, so its rendered space above must exceed its space below.
|
|
3925
|
+
// Margins alone can't be trusted (collapsing, flex rows, section padding),
|
|
3926
|
+
// so this measures actual getBoundingClientRect gaps between the heading
|
|
3927
|
+
// and the nearest content genuinely above / below it. Fires only when two
|
|
3928
|
+
// or more headings violate the principle — a single occurrence is noise.
|
|
3929
|
+
function checkHeadingRhythmDOM() {
|
|
3930
|
+
const MIN_VIOLATIONS = 2;
|
|
3931
|
+
const CARD_EXEMPT_HEIGHT = 200;
|
|
3932
|
+
const MAX_BELOW_PX = 160; // beyond this the heading isn't binding to nearby content at all
|
|
3933
|
+
const MIN_DEFICIT_PX = 12;
|
|
3934
|
+
|
|
3935
|
+
function isVisibleFlow(el) {
|
|
3936
|
+
const style = getComputedStyle(el);
|
|
3937
|
+
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
|
3938
|
+
if (parseFloat(style.opacity || '1') <= 0.05) return false;
|
|
3939
|
+
if (style.position === 'absolute' || style.position === 'fixed' || style.position === 'sticky') return false;
|
|
3940
|
+
const rect = el.getBoundingClientRect();
|
|
3941
|
+
return rect.width >= 1 && rect.height >= 1;
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
// Edges only count when they share the heading's column — grid layouts
|
|
3945
|
+
// put content beside a heading, and a far-away element in another column
|
|
3946
|
+
// says nothing about the heading's vertical rhythm.
|
|
3947
|
+
function overlapsX(sr, rect) {
|
|
3948
|
+
return Math.min(sr.right, rect.right) - Math.max(sr.left, rect.left) >= 8;
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
// Does this container draw its own top boundary (background, top border,
|
|
3952
|
+
// shadow)? Crossing out of such a container means the container edge is
|
|
3953
|
+
// the separator above the heading, not raw whitespace — exempt.
|
|
3954
|
+
function hasOwnTopBoundary(el) {
|
|
3955
|
+
const style = getComputedStyle(el);
|
|
3956
|
+
const bg = parseAnyColor(style.backgroundColor || '');
|
|
3957
|
+
if (bg && (bg.a ?? 1) > 0.05) return true;
|
|
3958
|
+
if ((parseFloat(style.borderTopWidth) || 0) > 0) return true;
|
|
3959
|
+
if (style.boxShadow && style.boxShadow !== 'none') return true;
|
|
3960
|
+
return false;
|
|
3961
|
+
}
|
|
3962
|
+
|
|
3963
|
+
// Eyebrows, kickers, and index labels sitting directly on top of a
|
|
3964
|
+
// heading belong to the heading's own cluster — space above is measured
|
|
3965
|
+
// from the top of the cluster, not from the label to the heading.
|
|
3966
|
+
function clusterTop(h, rect) {
|
|
3967
|
+
const headingFontSize = parseFloat(getComputedStyle(h).fontSize) || 16;
|
|
3968
|
+
let topEl = h;
|
|
3969
|
+
let top = rect.top;
|
|
3970
|
+
for (let i = 0; i < 3; i++) {
|
|
3971
|
+
const sib = topEl.previousElementSibling;
|
|
3972
|
+
if (!sib || !isVisibleFlow(sib)) break;
|
|
3973
|
+
const sr = sib.getBoundingClientRect();
|
|
3974
|
+
if (!overlapsX(sr, rect)) break;
|
|
3975
|
+
const gap = top - sr.bottom;
|
|
3976
|
+
if (gap < 0 || gap >= 28 || sr.height > 60) break;
|
|
3977
|
+
const text = (sib.textContent || '').trim();
|
|
3978
|
+
const sibFontSize = parseFloat(getComputedStyle(sib).fontSize) || 16;
|
|
3979
|
+
const labelLike = sibFontSize < headingFontSize * 0.75 || text.length <= 40;
|
|
3980
|
+
if (!labelLike || text.length > 80) break;
|
|
3981
|
+
topEl = sib;
|
|
3982
|
+
top = sr.top;
|
|
3983
|
+
}
|
|
3984
|
+
return { topEl, top };
|
|
3985
|
+
}
|
|
3986
|
+
|
|
3987
|
+
// Nearest content edge strictly above the heading cluster. Walks
|
|
3988
|
+
// previous siblings, then out through ancestors. Skips elements that
|
|
3989
|
+
// vertically overlap (flex-row companions, sticky rails) or sit in
|
|
3990
|
+
// another column. Returns null when nothing qualifies — first content
|
|
3991
|
+
// on the page, or the top of a visually bounded container.
|
|
3992
|
+
function edgeAbove(startEl, top, rect) {
|
|
3993
|
+
let node = startEl;
|
|
3994
|
+
while (node && node !== document.body) {
|
|
3995
|
+
let sib = node.previousElementSibling;
|
|
3996
|
+
while (sib) {
|
|
3997
|
+
if (isVisibleFlow(sib)) {
|
|
3998
|
+
const sr = sib.getBoundingClientRect();
|
|
3999
|
+
if (sr.bottom <= top + 2 && overlapsX(sr, rect)) return sr.bottom;
|
|
4000
|
+
}
|
|
4001
|
+
sib = sib.previousElementSibling;
|
|
4002
|
+
}
|
|
4003
|
+
const parent = node.parentElement;
|
|
4004
|
+
if (!parent || parent === document.body) return null;
|
|
4005
|
+
// Leaving a container upward: if it draws its own top edge, that
|
|
4006
|
+
// edge separates the heading from whatever sits above.
|
|
4007
|
+
if (hasOwnTopBoundary(parent)) return null;
|
|
4008
|
+
node = parent;
|
|
4009
|
+
}
|
|
4010
|
+
return null;
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
// Nearest content edge strictly below the heading — the block the
|
|
4014
|
+
// heading introduces. Crosses wrappers freely (headings often share a
|
|
4015
|
+
// row wrapper with an eyebrow or index label).
|
|
4016
|
+
function edgeBelow(h, rect) {
|
|
4017
|
+
let node = h;
|
|
4018
|
+
while (node && node !== document.body) {
|
|
4019
|
+
let sib = node.nextElementSibling;
|
|
4020
|
+
while (sib) {
|
|
4021
|
+
if (isVisibleFlow(sib)) {
|
|
4022
|
+
const sr = sib.getBoundingClientRect();
|
|
4023
|
+
if (sr.top >= rect.bottom - 2 && overlapsX(sr, rect)) return sr.top;
|
|
4024
|
+
}
|
|
4025
|
+
sib = sib.nextElementSibling;
|
|
4026
|
+
}
|
|
4027
|
+
node = node.parentElement;
|
|
4028
|
+
}
|
|
4029
|
+
return null;
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
function insideSmallCard(h) {
|
|
4033
|
+
let cur = h.parentElement;
|
|
4034
|
+
while (cur && cur !== document.body) {
|
|
4035
|
+
if (isCardLikeDOM(cur)) {
|
|
4036
|
+
const cr = cur.getBoundingClientRect();
|
|
4037
|
+
if (cr.height < CARD_EXEMPT_HEIGHT) return true;
|
|
4038
|
+
}
|
|
4039
|
+
cur = cur.parentElement;
|
|
4040
|
+
}
|
|
4041
|
+
return false;
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
const candidates = [];
|
|
4045
|
+
for (const h of document.querySelectorAll('h2, h3, h4')) {
|
|
4046
|
+
if (!isVisibleFlow(h)) continue;
|
|
4047
|
+
const text = (h.textContent || '').trim().replace(/\s+/g, ' ');
|
|
4048
|
+
if (text.length < 3) continue;
|
|
4049
|
+
const rect = h.getBoundingClientRect();
|
|
4050
|
+
const belowTop = edgeBelow(h, rect);
|
|
4051
|
+
if (belowTop == null) continue; // heading introduces nothing measurable
|
|
4052
|
+
const { topEl, top } = clusterTop(h, rect);
|
|
4053
|
+
const aboveBottom = edgeAbove(topEl, top, rect);
|
|
4054
|
+
if (aboveBottom == null) continue; // first content, or bounded container
|
|
4055
|
+
if (insideSmallCard(h)) continue;
|
|
4056
|
+
const above = Math.max(0, top - aboveBottom);
|
|
4057
|
+
const below = Math.max(0, belowTop - rect.bottom);
|
|
4058
|
+
if (below < 6 || below > MAX_BELOW_PX) continue;
|
|
4059
|
+
// Violation: the space above clearly fails to exceed the space below.
|
|
4060
|
+
// Near-equal gaps are ambiguous rather than inverted, so they pass.
|
|
4061
|
+
if (above < below * 0.75 && below - above >= MIN_DEFICIT_PX) {
|
|
4062
|
+
candidates.push({ el: h, tag: h.tagName.toLowerCase(), text: text.slice(0, 60), above, below });
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
if (candidates.length < MIN_VIOLATIONS) return [];
|
|
4067
|
+
return candidates.map(c => ({
|
|
4068
|
+
type: 'heading-rhythm',
|
|
4069
|
+
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)`,
|
|
4070
|
+
el: c.el,
|
|
4071
|
+
}));
|
|
4072
|
+
}
|
|
4073
|
+
|
|
2047
4074
|
// Node page-level checks — take document/window as parameters
|
|
2048
4075
|
|
|
2049
4076
|
function checkPageTypography(doc, win) {
|
|
@@ -2186,6 +4213,135 @@ function checkPageLayout(doc, win) {
|
|
|
2186
4213
|
return findings;
|
|
2187
4214
|
}
|
|
2188
4215
|
|
|
4216
|
+
// ── Repeated text inside one container ──────────────────────────────────────
|
|
4217
|
+
// The same literal string rendered 3+ times in structurally different spots
|
|
4218
|
+
// inside one bordered/elevated container — typically a status word wired
|
|
4219
|
+
// into every slot of a card template. Legitimate repetition is structural:
|
|
4220
|
+
// table columns, calendar grids, nav/menu lists, and templated sibling rows
|
|
4221
|
+
// all repeat text in *parallel* positions, so occurrences whose element
|
|
4222
|
+
// paths inside the container are identical (or live in dedicated repetition
|
|
4223
|
+
// structures) never count. Only 3+ occurrences at 3+ distinct structural
|
|
4224
|
+
// positions flag.
|
|
4225
|
+
|
|
4226
|
+
const REPEATED_TEXT_SKIP_SELECTOR = [
|
|
4227
|
+
'table',
|
|
4228
|
+
'select',
|
|
4229
|
+
'datalist',
|
|
4230
|
+
'nav',
|
|
4231
|
+
'menu',
|
|
4232
|
+
'[role="navigation"]',
|
|
4233
|
+
'[role="menu"]',
|
|
4234
|
+
'[role="menubar"]',
|
|
4235
|
+
'[role="listbox"]',
|
|
4236
|
+
'[role="grid"]',
|
|
4237
|
+
'[role="tablist"]',
|
|
4238
|
+
'[role="radiogroup"]',
|
|
4239
|
+
'[aria-hidden="true"]',
|
|
4240
|
+
].join(',');
|
|
4241
|
+
|
|
4242
|
+
const REPEATED_TEXT_CONTAINER_TAGS = new Set([
|
|
4243
|
+
'div', 'section', 'article', 'aside', 'main', 'figure', 'form', 'fieldset', 'details', 'li',
|
|
4244
|
+
]);
|
|
4245
|
+
|
|
4246
|
+
// A container worth attributing text to: visibly bounded (border on most
|
|
4247
|
+
// sides or an elevation shadow) and surface-like (radius or own background).
|
|
4248
|
+
function isRepeatedTextContainer(style) {
|
|
4249
|
+
if (!style) return false;
|
|
4250
|
+
const hasShadow = !!(style.boxShadow && style.boxShadow !== 'none' && style.boxShadow !== '');
|
|
4251
|
+
const borderSides = ['Top', 'Right', 'Bottom', 'Left']
|
|
4252
|
+
.filter(side => (parseFloat(style[`border${side}Width`]) || 0) >= 1).length;
|
|
4253
|
+
const hasBorder = borderSides >= 3;
|
|
4254
|
+
const hasRadius = (parseFloat(style.borderRadius) || 0) > 0;
|
|
4255
|
+
const bg = parseRgb(style.backgroundColor) || parseAnyColor(style.backgroundColor);
|
|
4256
|
+
const hasBg = !!(bg && (bg.a ?? 1) > 0.1);
|
|
4257
|
+
return isCardLikeFromProps(hasShadow, hasBorder, hasRadius, hasBg);
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
function collectRepeatedContainerTextFindings(doc, getStyle, opts = {}) {
|
|
4261
|
+
const isVisible = opts.isVisible || (() => true);
|
|
4262
|
+
const findings = [];
|
|
4263
|
+
|
|
4264
|
+
const containers = [];
|
|
4265
|
+
const containerSet = new Set();
|
|
4266
|
+
for (const el of doc.querySelectorAll('*')) {
|
|
4267
|
+
if (!REPEATED_TEXT_CONTAINER_TAGS.has(el.tagName.toLowerCase())) continue;
|
|
4268
|
+
if (el.closest?.(REPEATED_TEXT_SKIP_SELECTOR)) continue;
|
|
4269
|
+
if (!isRepeatedTextContainer(getStyle(el))) continue;
|
|
4270
|
+
containers.push(el);
|
|
4271
|
+
containerSet.add(el);
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
for (const container of containers) {
|
|
4275
|
+
if (!isVisible(container)) continue;
|
|
4276
|
+
const descendants = container.querySelectorAll('*');
|
|
4277
|
+
// Page-scale wrappers that merely happen to carry a background are not
|
|
4278
|
+
// the "one card" this rule reasons about.
|
|
4279
|
+
if (descendants.length > 250) continue;
|
|
4280
|
+
|
|
4281
|
+
const groups = new Map();
|
|
4282
|
+
for (const d of descendants) {
|
|
4283
|
+
// Attribute text to the innermost container only.
|
|
4284
|
+
let anc = d.parentElement;
|
|
4285
|
+
let ownedByInner = false;
|
|
4286
|
+
while (anc && anc !== container) {
|
|
4287
|
+
if (containerSet.has(anc)) { ownedByInner = true; break; }
|
|
4288
|
+
anc = anc.parentElement;
|
|
4289
|
+
}
|
|
4290
|
+
if (ownedByInner) continue;
|
|
4291
|
+
if (d.closest?.(REPEATED_TEXT_SKIP_SELECTOR)) continue;
|
|
4292
|
+
// Icon-font glyph names read as text but render as symbols.
|
|
4293
|
+
if (/icon|material-symbols|(?:^|\s)fa[srlbd]?(?:\s|-|$)/i.test(String(d.getAttribute?.('class') || ''))) continue;
|
|
4294
|
+
if (!isVisible(d)) continue;
|
|
4295
|
+
|
|
4296
|
+
const direct = [...d.childNodes]
|
|
4297
|
+
.filter(n => n.nodeType === 3)
|
|
4298
|
+
.map(n => n.textContent)
|
|
4299
|
+
.join(' ')
|
|
4300
|
+
.replace(/\s+/g, ' ')
|
|
4301
|
+
.trim();
|
|
4302
|
+
if (direct.length < 4 || direct.length > 48) continue;
|
|
4303
|
+
if (!/[a-zA-Z]/.test(direct)) continue;
|
|
4304
|
+
|
|
4305
|
+
// Structural signature: the element path from the occurrence up to
|
|
4306
|
+
// the container. Parallel/templated repetition shares one signature.
|
|
4307
|
+
const sig = [];
|
|
4308
|
+
for (let cur = d; cur && cur !== container; cur = cur.parentElement) {
|
|
4309
|
+
const cls = String(cur.getAttribute?.('class') || '')
|
|
4310
|
+
.trim().split(/\s+/).filter(Boolean).sort().join('.');
|
|
4311
|
+
sig.push(cur.tagName.toLowerCase() + (cls ? `.${cls}` : ''));
|
|
4312
|
+
}
|
|
4313
|
+
if (!groups.has(direct)) groups.set(direct, []);
|
|
4314
|
+
groups.get(direct).push(sig.join('>'));
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
for (const [text, sigs] of groups) {
|
|
4318
|
+
if (sigs.length < 3) continue;
|
|
4319
|
+
if (new Set(sigs).size < 3) continue;
|
|
4320
|
+
findings.push({
|
|
4321
|
+
id: 'repeated-container-text',
|
|
4322
|
+
snippet: `"${text.slice(0, 40)}" rendered ${sigs.length}× in distinct spots inside ${classSelector(container)}`,
|
|
4323
|
+
});
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
return findings;
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
function checkRepeatedContainerTextFromDoc(doc, win) {
|
|
4330
|
+
return collectRepeatedContainerTextFindings(
|
|
4331
|
+
doc,
|
|
4332
|
+
(el) => win.getComputedStyle(el),
|
|
4333
|
+
{ isVisible: (el) => String(win.getComputedStyle(el).display || '') !== 'none' },
|
|
4334
|
+
);
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
function checkRepeatedContainerTextDOM() {
|
|
4338
|
+
return collectRepeatedContainerTextFindings(
|
|
4339
|
+
document,
|
|
4340
|
+
(el) => getComputedStyle(el),
|
|
4341
|
+
{ isVisible: isRenderedForBrowserRule },
|
|
4342
|
+
);
|
|
4343
|
+
}
|
|
4344
|
+
|
|
2189
4345
|
// ─── Cream / beige palette (the default "tasteful" AI surface) ────────────────
|
|
2190
4346
|
// A warm, lightly-tinted off-white page background — light, with R≥G≥B and a
|
|
2191
4347
|
// small warm tint (not white, not a strong color). The current reflex surface.
|
|
@@ -2299,7 +4455,7 @@ function checkElementOversizedH1DOM(el) {
|
|
|
2299
4455
|
return checkOversizedH1({ tag, fontSize, headingText, rect, viewportWidth, viewportHeight });
|
|
2300
4456
|
}
|
|
2301
4457
|
|
|
2302
|
-
// ───
|
|
4458
|
+
// ─── Generated-UI tell: hairline border + wide diffuse shadow ────────────────
|
|
2303
4459
|
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;
|
|
2304
4460
|
|
|
2305
4461
|
function shadowLayerAlpha(layer) {
|
|
@@ -2624,22 +4780,674 @@ function checkElementTextOverflowDOM(el) {
|
|
|
2624
4780
|
if (el.clientWidth > 0 && delta >= 16) {
|
|
2625
4781
|
return [{ id: 'text-overflow', snippet: `${classSelector(el)} overflows its box by ${Math.round(delta)}px` }];
|
|
2626
4782
|
}
|
|
4783
|
+
|
|
4784
|
+
// Inline text owners have no client geometry (clientWidth/scrollWidth are
|
|
4785
|
+
// both 0), so the scrollWidth path above never sees them. Their overflow
|
|
4786
|
+
// registers only on a block ancestor, and that ancestor has no direct text
|
|
4787
|
+
// so the ownership gate skips it. (The shipped miss: a nowrap inline
|
|
4788
|
+
// <span> spilling 45px past its fixed-width grid cell.) Measure the inline
|
|
4789
|
+
// box against the padding box of its nearest block container instead.
|
|
4790
|
+
if (el.clientWidth === 0 && rect && rect.width > 0) {
|
|
4791
|
+
let container = el.parentElement;
|
|
4792
|
+
while (container && container.clientWidth === 0) container = container.parentElement;
|
|
4793
|
+
if (!container) return [];
|
|
4794
|
+
// Transforms make rect comparisons lie; skip anything on that path.
|
|
4795
|
+
for (let p = el; p && p !== container.parentElement; p = p.parentElement) {
|
|
4796
|
+
const t = getComputedStyle(p).transform;
|
|
4797
|
+
if (t && t !== 'none') return [];
|
|
4798
|
+
}
|
|
4799
|
+
const cRect = container.getBoundingClientRect();
|
|
4800
|
+
const contentRight = cRect.left + container.clientLeft + container.clientWidth;
|
|
4801
|
+
const spill = rect.right - contentRight;
|
|
4802
|
+
if (spill >= 16) {
|
|
4803
|
+
return [{ id: 'text-overflow', snippet: `${classSelector(el)} overflows its container by ${Math.round(spill)}px` }];
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
2627
4806
|
return [];
|
|
2628
4807
|
}
|
|
2629
4808
|
|
|
4809
|
+
// ---------------------------------------------------------------------------
|
|
4810
|
+
// Blinking cursor (browser-only)
|
|
4811
|
+
// ---------------------------------------------------------------------------
|
|
4812
|
+
|
|
4813
|
+
// Block / underscore glyphs commonly used as a fake text cursor.
|
|
4814
|
+
const CURSOR_GLYPH_RE = /^[_|▀-▟■▮❙❚|]$/;
|
|
4815
|
+
|
|
4816
|
+
// How far down the page still counts as the first-viewport / hero region.
|
|
4817
|
+
// Hero compositions regularly run past a literal viewport height, so the
|
|
4818
|
+
// gate is a landing-region budget, not an exact fold line.
|
|
4819
|
+
const CURSOR_FIRST_VIEWPORT_PX = 1200;
|
|
4820
|
+
|
|
4821
|
+
// Do the named @keyframes only toggle visibility (opacity dropping to ~0 or
|
|
4822
|
+
// visibility:hidden), i.e. a blink rather than a fade/move/spin? Walks the
|
|
4823
|
+
// live CSSOM; cross-origin sheets are skipped.
|
|
4824
|
+
function keyframesToggleVisibilityDOM(name) {
|
|
4825
|
+
if (!name) return false;
|
|
4826
|
+
for (const sheet of document.styleSheets) {
|
|
4827
|
+
let rules;
|
|
4828
|
+
try { rules = sheet.cssRules || sheet.rules; } catch { continue; }
|
|
4829
|
+
if (!rules) continue;
|
|
4830
|
+
const stack = [...rules];
|
|
4831
|
+
while (stack.length) {
|
|
4832
|
+
const rule = stack.shift();
|
|
4833
|
+
if (rule.cssRules && rule.type !== 7) { stack.push(...rule.cssRules); continue; }
|
|
4834
|
+
if (rule.type !== 7 || rule.name !== name) continue; // 7 = KEYFRAMES_RULE
|
|
4835
|
+
let togglesOut = false;
|
|
4836
|
+
for (const frame of rule.cssRules || []) {
|
|
4837
|
+
const fs = frame.style;
|
|
4838
|
+
if (!fs) continue;
|
|
4839
|
+
for (let i = 0; i < fs.length; i++) {
|
|
4840
|
+
const prop = fs[i];
|
|
4841
|
+
if (prop === 'opacity') {
|
|
4842
|
+
if ((parseFloat(fs.getPropertyValue('opacity')) || 0) <= 0.15) togglesOut = true;
|
|
4843
|
+
} else if (prop === 'visibility') {
|
|
4844
|
+
if (/hidden/i.test(fs.getPropertyValue('visibility'))) togglesOut = true;
|
|
4845
|
+
} else if (prop !== 'animation-timing-function') {
|
|
4846
|
+
return false; // keyframes animate something else — not a blink
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
}
|
|
4850
|
+
return togglesOut;
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4853
|
+
return false;
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4856
|
+
// Decorative blinking cursor: a small block / underscore element bound to an
|
|
4857
|
+
// infinite blink animation, sitting in the first-viewport region of a page.
|
|
4858
|
+
// Real editable surfaces (inputs, textareas, contenteditable, role=textbox)
|
|
4859
|
+
// draw their own caret and are exempt. Round pulsing dots stay with the
|
|
4860
|
+
// pulsing-dot rule.
|
|
4861
|
+
function checkElementBlinkingCursorDOM(el) {
|
|
4862
|
+
const tag = el.tagName.toLowerCase();
|
|
4863
|
+
if (['input', 'textarea', 'select', 'img', 'svg', 'script', 'style'].includes(tag)) return [];
|
|
4864
|
+
const style = getComputedStyle(el);
|
|
4865
|
+
|
|
4866
|
+
const iterations = (style.animationIterationCount || '').split(',').map(s => s.trim());
|
|
4867
|
+
if (!iterations.includes('infinite')) return [];
|
|
4868
|
+
const names = (style.animationName || '').split(',').map(s => s.trim()).filter(n => n && n !== 'none');
|
|
4869
|
+
if (names.length === 0) return [];
|
|
4870
|
+
const blinkName = names.find(n => /blink|caret|cursor/i.test(n))
|
|
4871
|
+
|| names.find(n => keyframesToggleVisibilityDOM(n));
|
|
4872
|
+
if (!blinkName) return [];
|
|
4873
|
+
|
|
4874
|
+
// Real caret contexts are exempt.
|
|
4875
|
+
if (el.isContentEditable || el.closest('[contenteditable=""], [contenteditable="true"], [role="textbox"]')) return [];
|
|
4876
|
+
|
|
4877
|
+
const rect = el.getBoundingClientRect();
|
|
4878
|
+
if (rect.width <= 0 || rect.height <= 0) return [];
|
|
4879
|
+
|
|
4880
|
+
// First-viewport gate: the hero cliché, not a footer terminal.
|
|
4881
|
+
const pageTop = rect.top + (window.scrollY || 0);
|
|
4882
|
+
if (pageTop > CURSOR_FIRST_VIEWPORT_PX) return [];
|
|
4883
|
+
|
|
4884
|
+
// Cursor shape: a lone block/underscore glyph, or an empty solid
|
|
4885
|
+
// rectangle sized like a text caret (block or underscore form).
|
|
4886
|
+
const text = (el.textContent || '').trim();
|
|
4887
|
+
const glyphCursor = text.length === 1 && CURSOR_GLYPH_RE.test(text);
|
|
4888
|
+
let blockCursor = false;
|
|
4889
|
+
if (!glyphCursor) {
|
|
4890
|
+
if (text.length > 0 || el.childElementCount > 0) return [];
|
|
4891
|
+
const bg = parseAnyColor(style.backgroundColor || '');
|
|
4892
|
+
const filled = bg && (bg.a ?? 1) > 0.2;
|
|
4893
|
+
const hasBorderFill = ['Left', 'Right', 'Bottom'].some(
|
|
4894
|
+
side => (parseFloat(style[`border${side}Width`]) || 0) >= 1,
|
|
4895
|
+
);
|
|
4896
|
+
if (!filled && !hasBorderFill) return [];
|
|
4897
|
+
const vertical = rect.width >= 1 && rect.width <= 24 && rect.height >= 6 && rect.height <= 48 && rect.height >= rect.width;
|
|
4898
|
+
const underscore = rect.height >= 1 && rect.height <= 6 && rect.width >= 4 && rect.width <= 24;
|
|
4899
|
+
if (!vertical && !underscore) return [];
|
|
4900
|
+
// Round dots are the pulsing-dot rule's territory.
|
|
4901
|
+
const radiusPx = parseFloat(style.borderRadius) || 0;
|
|
4902
|
+
if (radiusPx >= 0.4 * Math.min(rect.width, rect.height)) return [];
|
|
4903
|
+
blockCursor = true;
|
|
4904
|
+
}
|
|
4905
|
+
if (!glyphCursor && !blockCursor) return [];
|
|
4906
|
+
|
|
4907
|
+
// Hero-region promotion: a fake caret blinking in the first ~900px or
|
|
4908
|
+
// inside the page chrome is the shipped hero cliché, not an incidental
|
|
4909
|
+
// flourish. Promote those from the registry's advisory to warning;
|
|
4910
|
+
// lower first-viewport occurrences keep the default severity.
|
|
4911
|
+
const inHeroRegion = pageTop <= 900
|
|
4912
|
+
|| !!(el.closest && el.closest('header, nav, [role="banner"], [role="navigation"]'));
|
|
4913
|
+
return [{
|
|
4914
|
+
id: 'blinking-cursor',
|
|
4915
|
+
snippet: `${classSelector(el)} — ${Math.round(rect.width)}x${Math.round(rect.height)}px blinking cursor (animation "${blinkName}") in the first viewport`,
|
|
4916
|
+
...(inHeroRegion ? { severity: 'warning' } : {}),
|
|
4917
|
+
}];
|
|
4918
|
+
}
|
|
4919
|
+
|
|
4920
|
+
// ---------------------------------------------------------------------------
|
|
4921
|
+
// Content invisible at rest (browser-only, driven by the URL engine)
|
|
4922
|
+
// ---------------------------------------------------------------------------
|
|
4923
|
+
|
|
4924
|
+
// Tags whose text never renders, or whose hidden state is legitimate UI
|
|
4925
|
+
// (templates, dialogs, native select options). Text inside them stays out of
|
|
4926
|
+
// both the numerator and the denominator.
|
|
4927
|
+
const HIDDEN_TEXT_EXCLUDE_TAGS = new Set([
|
|
4928
|
+
'script', 'style', 'noscript', 'template', 'title', 'head', 'meta', 'link',
|
|
4929
|
+
'option', 'optgroup', 'select', 'datalist', 'dialog',
|
|
4930
|
+
]);
|
|
4931
|
+
|
|
4932
|
+
// Measure how many text characters currently render invisible (computed
|
|
4933
|
+
// opacity ~0 or visibility hidden anywhere on the ancestor chain) versus
|
|
4934
|
+
// visible. display:none / [hidden] / aria-hidden subtrees are legitimately
|
|
4935
|
+
// hidden UI (menus, tab panels, templates): they are excluded from the
|
|
4936
|
+
// denominator entirely rather than counted as invisible.
|
|
4937
|
+
function measureHiddenTextDOM() {
|
|
4938
|
+
const cache = new Map();
|
|
4939
|
+
function stateOf(el) {
|
|
4940
|
+
if (!el || el.nodeType !== 1 || el === document.documentElement) return 'visible';
|
|
4941
|
+
const cached = cache.get(el);
|
|
4942
|
+
if (cached) return cached;
|
|
4943
|
+
let state;
|
|
4944
|
+
const tag = el.tagName.toLowerCase();
|
|
4945
|
+
if (HIDDEN_TEXT_EXCLUDE_TAGS.has(tag)) {
|
|
4946
|
+
state = 'excluded';
|
|
4947
|
+
} else {
|
|
4948
|
+
const parentState = stateOf(el.parentElement);
|
|
4949
|
+
if (parentState === 'excluded') {
|
|
4950
|
+
state = 'excluded';
|
|
4951
|
+
} else {
|
|
4952
|
+
const style = getComputedStyle(el);
|
|
4953
|
+
if (style.display === 'none' || el.hidden || el.getAttribute('aria-hidden') === 'true'
|
|
4954
|
+
|| String(style.contentVisibility || '').toLowerCase() === 'hidden') {
|
|
4955
|
+
state = 'excluded';
|
|
4956
|
+
} else if (parentState === 'invisible'
|
|
4957
|
+
|| (parseFloat(style.opacity) || 0) <= 0.02
|
|
4958
|
+
|| /^(hidden|collapse)$/.test(style.visibility)) {
|
|
4959
|
+
state = 'invisible';
|
|
4960
|
+
} else {
|
|
4961
|
+
state = 'visible';
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
cache.set(el, state);
|
|
4966
|
+
return state;
|
|
4967
|
+
}
|
|
4968
|
+
|
|
4969
|
+
let totalChars = 0;
|
|
4970
|
+
let hiddenChars = 0;
|
|
4971
|
+
const hiddenSamples = [];
|
|
4972
|
+
for (const el of document.querySelectorAll('body *')) {
|
|
4973
|
+
let len = 0;
|
|
4974
|
+
for (const node of el.childNodes) {
|
|
4975
|
+
if (node.nodeType === 3) len += node.textContent.replace(/\s+/g, ' ').trim().length;
|
|
4976
|
+
}
|
|
4977
|
+
if (!len) continue;
|
|
4978
|
+
const state = stateOf(el);
|
|
4979
|
+
if (state === 'excluded') continue;
|
|
4980
|
+
totalChars += len;
|
|
4981
|
+
if (state === 'invisible') {
|
|
4982
|
+
hiddenChars += len;
|
|
4983
|
+
if (hiddenSamples.length < 3) {
|
|
4984
|
+
const text = String(el.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 40);
|
|
4985
|
+
if (text) hiddenSamples.push(text);
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
return { totalChars, hiddenChars, hiddenSamples };
|
|
4990
|
+
}
|
|
4991
|
+
|
|
4992
|
+
// Pure threshold check over a measureHiddenTextDOM() result. The URL engine
|
|
4993
|
+
// calls it AFTER a reveal sweep (scroll through the document so every
|
|
4994
|
+
// IntersectionObserver / scroll reveal had its chance to fire, then back to
|
|
4995
|
+
// the top): a healthy reveal-on-scroll page drops to ~0 invisible text after
|
|
4996
|
+
// the sweep, while a page whose reveal script died keeps most of its text at
|
|
4997
|
+
// opacity 0 forever. Fires only when the invisible share stays above 30%
|
|
4998
|
+
// with a real amount of text behind it.
|
|
4999
|
+
function checkContentHiddenAtRest({ totalChars = 0, hiddenChars = 0, hiddenSamples = [] } = {}) {
|
|
5000
|
+
if (totalChars < 200 || hiddenChars < 150) return [];
|
|
5001
|
+
const share = hiddenChars / totalChars;
|
|
5002
|
+
if (share <= 0.3) return [];
|
|
5003
|
+
const sample = hiddenSamples.length ? ` (e.g. "${hiddenSamples[0]}")` : '';
|
|
5004
|
+
return [{
|
|
5005
|
+
id: 'content-hidden-at-rest',
|
|
5006
|
+
snippet: `${Math.round(share * 100)}% of page text (${hiddenChars} of ${totalChars} chars) stays at opacity 0 / visibility hidden after reveal handlers ran${sample}`,
|
|
5007
|
+
}];
|
|
5008
|
+
}
|
|
5009
|
+
|
|
5010
|
+
// ---------------------------------------------------------------------------
|
|
5011
|
+
// Edge-flush cards in horizontal scrollers (browser-only)
|
|
5012
|
+
// ---------------------------------------------------------------------------
|
|
5013
|
+
|
|
5014
|
+
// A visually-defined card (own opaque background, or borders on 2+ sides)
|
|
5015
|
+
// inside a horizontal scroller, sitting flush against one edge of the
|
|
5016
|
+
// scroller's clip box at rest while keeping a clear gutter on the other
|
|
5017
|
+
// side. The canonical bug: the first snap panel is sized wider than the
|
|
5018
|
+
// scroller, so its cards end exactly at the clip edge with their rounded
|
|
5019
|
+
// corners cut, while every sibling panel keeps its inset. Cards that extend
|
|
5020
|
+
// far past the edge are deliberate peeks and stay exempt.
|
|
5021
|
+
function checkEdgeFlushCardsDOM() {
|
|
5022
|
+
const findings = [];
|
|
5023
|
+
const vh = window.innerHeight || 800;
|
|
5024
|
+
const isScroller = (s) => /(auto|scroll)/.test(s.overflowX || '') || /(auto|scroll)/.test(s.overflow || '');
|
|
5025
|
+
|
|
5026
|
+
for (const scroller of document.querySelectorAll('*')) {
|
|
5027
|
+
const style = getComputedStyle(scroller);
|
|
5028
|
+
if (!isScroller(style)) continue;
|
|
5029
|
+
if (scroller.scrollWidth <= scroller.clientWidth + 8) continue;
|
|
5030
|
+
// At rest only: a user-scrolled or snapped-forward scroller legitimately
|
|
5031
|
+
// shows cut cards at both edges.
|
|
5032
|
+
if (scroller.scrollLeft > 4) continue;
|
|
5033
|
+
const scRect = scroller.getBoundingClientRect();
|
|
5034
|
+
if (scRect.width < 120 || scRect.height < 60) continue;
|
|
5035
|
+
// Landing-region gate: the defect matters where the page opens.
|
|
5036
|
+
if (scRect.top + (window.scrollY || 0) > 2 * vh) continue;
|
|
5037
|
+
const contentLeft = scRect.left + scroller.clientLeft;
|
|
5038
|
+
const contentRight = contentLeft + scroller.clientWidth;
|
|
5039
|
+
|
|
5040
|
+
const flush = [];
|
|
5041
|
+
for (const card of scroller.querySelectorAll('*')) {
|
|
5042
|
+
if (!isRenderedForBrowserRule(card)) continue;
|
|
5043
|
+
// Attribute cards to their nearest scroller only (nested scrollers).
|
|
5044
|
+
let owner = card.parentElement;
|
|
5045
|
+
while (owner && owner !== scroller && !isScroller(getComputedStyle(owner))) owner = owner.parentElement;
|
|
5046
|
+
if (owner !== scroller) continue;
|
|
5047
|
+
const cs = getComputedStyle(card);
|
|
5048
|
+
const rect = card.getBoundingClientRect();
|
|
5049
|
+
if (rect.width < 80 || rect.height < 40) continue;
|
|
5050
|
+
const bg = parseAnyColor(cs.backgroundColor || '');
|
|
5051
|
+
const hasBg = !!(bg && (bg.a ?? 1) > 0.5);
|
|
5052
|
+
const borderSides = ['Top', 'Right', 'Bottom', 'Left']
|
|
5053
|
+
.filter(side => (parseFloat(cs[`border${side}Width`]) || 0) > 0).length;
|
|
5054
|
+
if (!hasBg && borderSides < 2) continue;
|
|
5055
|
+
const leftGutter = rect.left - contentLeft;
|
|
5056
|
+
const rightGap = contentRight - rect.right;
|
|
5057
|
+
// Flush right with a left gutter, or the mirror. The -24 floor keeps
|
|
5058
|
+
// deliberately peeking next-cards (cut mid-card) exempt.
|
|
5059
|
+
const flushRight = leftGutter >= 6 && rightGap < 8 && rightGap > -24;
|
|
5060
|
+
const flushLeft = rightGap >= 6 && leftGutter < 8 && leftGutter > -24;
|
|
5061
|
+
if (!flushRight && !flushLeft) continue;
|
|
5062
|
+
flush.push({ card, edge: flushRight ? 'right' : 'left', gap: Math.round(flushRight ? rightGap : leftGutter) });
|
|
5063
|
+
}
|
|
5064
|
+
if (flush.length === 0) continue;
|
|
5065
|
+
const worst = flush.reduce((a, b) => (b.gap < a.gap ? b : a));
|
|
5066
|
+
findings.push({
|
|
5067
|
+
el: scroller,
|
|
5068
|
+
type: 'edge-flush-cards',
|
|
5069
|
+
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)})`,
|
|
5070
|
+
});
|
|
5071
|
+
}
|
|
5072
|
+
return findings;
|
|
5073
|
+
}
|
|
5074
|
+
|
|
5075
|
+
// ---------------------------------------------------------------------------
|
|
5076
|
+
// Text occlusion / element overlap (browser-only)
|
|
5077
|
+
// ---------------------------------------------------------------------------
|
|
5078
|
+
|
|
5079
|
+
// An opaque decorated box: a near-solid background fill or two-plus visible
|
|
5080
|
+
// borders make it hide whatever sits behind it. Gradient / image fills are
|
|
5081
|
+
// deliberately excluded — a scrim gradient over hero imagery is a contrast
|
|
5082
|
+
// layer, not an occluder, and belongs to the pixel low-contrast rule.
|
|
5083
|
+
function isOpaqueDecoratedBox(cs) {
|
|
5084
|
+
if (!cs) return false;
|
|
5085
|
+
const bg = parseAnyColor(cs.backgroundColor || '');
|
|
5086
|
+
if (bg && (bg.a ?? 1) > 0.6) return true;
|
|
5087
|
+
const borderSides = ['Top', 'Right', 'Bottom', 'Left'].filter((side) => {
|
|
5088
|
+
if ((parseFloat(cs[`border${side}Width`]) || 0) <= 0) return false;
|
|
5089
|
+
const bc = parseAnyColor(cs[`border${side}Color`] || '');
|
|
5090
|
+
return bc && (bc.a ?? 1) > 0.3;
|
|
5091
|
+
}).length;
|
|
5092
|
+
return borderSides >= 2;
|
|
5093
|
+
}
|
|
5094
|
+
|
|
5095
|
+
// Is this element lifted out of normal flow into a layer that can cover
|
|
5096
|
+
// siblings? Two normal-flow blocks stacked vertically cannot truly hide each
|
|
5097
|
+
// other's ink — an overlap between their rects is line-box bleed from tight
|
|
5098
|
+
// leading (a display headline reaching up over the line before it), not
|
|
5099
|
+
// occlusion. Only out-of-flow positioning (absolute / fixed / sticky) moves an
|
|
5100
|
+
// element off its own row onto the pixels of another; an in-place transform or
|
|
5101
|
+
// relative nudge on a display headline does not.
|
|
5102
|
+
function isLayeredElement(el) {
|
|
5103
|
+
for (let cur = el; cur && cur.nodeType === 1 && cur !== document.body; cur = cur.parentElement) {
|
|
5104
|
+
const pos = String(getComputedStyle(cur).position || 'static');
|
|
5105
|
+
if (pos === 'absolute' || pos === 'fixed' || pos === 'sticky') return true;
|
|
5106
|
+
}
|
|
5107
|
+
return false;
|
|
5108
|
+
}
|
|
5109
|
+
|
|
5110
|
+
function elementDirectText(el) {
|
|
5111
|
+
let t = '';
|
|
5112
|
+
for (const node of el.childNodes || []) {
|
|
5113
|
+
if (node.nodeType === 3) t += node.textContent;
|
|
5114
|
+
}
|
|
5115
|
+
return t.trim();
|
|
5116
|
+
}
|
|
5117
|
+
|
|
5118
|
+
// Rendered gate that, unlike isRenderedForBrowserRule, does NOT exempt
|
|
5119
|
+
// aria-hidden subtrees: a decorative aria-hidden box still paints on screen
|
|
5120
|
+
// and can still visually cover real text.
|
|
5121
|
+
function isPaintedForOcclusion(el) {
|
|
5122
|
+
for (let cur = el; cur && cur.nodeType === 1; cur = cur.parentElement) {
|
|
5123
|
+
const style = getComputedStyle(cur);
|
|
5124
|
+
const visibility = String(style.visibility || '').toLowerCase();
|
|
5125
|
+
if (style.display === 'none' || visibility === 'hidden' || visibility === 'collapse') return false;
|
|
5126
|
+
if ((parseFloat(style.opacity) || 0) <= 0.05) return false;
|
|
5127
|
+
if (String(style.contentVisibility || '').toLowerCase() === 'hidden') return false;
|
|
5128
|
+
}
|
|
5129
|
+
return true;
|
|
5130
|
+
}
|
|
5131
|
+
|
|
5132
|
+
// Detects text that is actually painted UNDER an opaque box or another text
|
|
5133
|
+
// run (the reader can't read it), plus two structural overlap tells the
|
|
5134
|
+
// elementFromPoint probe can't reach: a large headline whose edge tucks behind
|
|
5135
|
+
// an opaque card, and an inline element whose leaked padding-box (a common
|
|
5136
|
+
// class-name-collision bug) covers a sibling.
|
|
5137
|
+
//
|
|
5138
|
+
// The occlusion probe is viewport-bound: elementFromPoint only answers for the
|
|
5139
|
+
// scan's current viewport (scroll 0), so the ground-truth paths cover the
|
|
5140
|
+
// first-viewport composition where collisions matter most. The inline-leak
|
|
5141
|
+
// path is pure geometry and runs anywhere on the page.
|
|
5142
|
+
const OCCLUSION_TEXT_SKIP_TAGS = new Set(['script', 'style', 'noscript', 'template', 'title']);
|
|
5143
|
+
|
|
5144
|
+
function checkTextOcclusionDOM() {
|
|
5145
|
+
const findings = [];
|
|
5146
|
+
const seenVictims = new Set();
|
|
5147
|
+
const vw = window.innerWidth || 1280;
|
|
5148
|
+
const vh = window.innerHeight || 800;
|
|
5149
|
+
|
|
5150
|
+
const isFloated = (cs) => {
|
|
5151
|
+
const f = String(cs.cssFloat || cs.float || 'none').toLowerCase();
|
|
5152
|
+
return f === 'left' || f === 'right';
|
|
5153
|
+
};
|
|
5154
|
+
const isMarqueeish = (el, cs) => {
|
|
5155
|
+
if (el.tagName === 'MARQUEE') return true;
|
|
5156
|
+
const ident = `${el.getAttribute?.('class') || ''} ${el.getAttribute?.('id') || ''}`;
|
|
5157
|
+
if (/\b(marquee|ticker|scroller|carousel|conveyor)\b/i.test(ident)) return true;
|
|
5158
|
+
const anim = String(cs.animationName || '').toLowerCase();
|
|
5159
|
+
return /marquee|ticker|scroll/.test(anim);
|
|
5160
|
+
};
|
|
5161
|
+
// A fixed or sticky overlay (status bar, toolbar, sticky header) floats above
|
|
5162
|
+
// scrolling content by design — whatever sits under it at rest scrolls clear,
|
|
5163
|
+
// so it is not occluding the page.
|
|
5164
|
+
const isPinnedOverlay = (el) => {
|
|
5165
|
+
for (let cur = el; cur && cur.nodeType === 1 && cur !== document.body; cur = cur.parentElement) {
|
|
5166
|
+
const pos = String(getComputedStyle(cur).position || 'static');
|
|
5167
|
+
if (pos === 'fixed' || pos === 'sticky') return true;
|
|
5168
|
+
}
|
|
5169
|
+
return false;
|
|
5170
|
+
};
|
|
5171
|
+
|
|
5172
|
+
// Collect renderable text owners in / near the first viewport for the
|
|
5173
|
+
// elementFromPoint probe. SVG <text> counts too.
|
|
5174
|
+
const textEls = [];
|
|
5175
|
+
for (const el of document.querySelectorAll('body *')) {
|
|
5176
|
+
const tag = el.tagName.toLowerCase();
|
|
5177
|
+
if (OCCLUSION_TEXT_SKIP_TAGS.has(tag)) continue;
|
|
5178
|
+
const inSvg = !!el.closest('svg');
|
|
5179
|
+
if (inSvg && tag !== 'text') continue;
|
|
5180
|
+
const text = inSvg ? (el.textContent || '').trim() : elementDirectText(el);
|
|
5181
|
+
if (text.length < 2) continue;
|
|
5182
|
+
if (!isPaintedForOcclusion(el)) continue;
|
|
5183
|
+
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
|
|
5184
|
+
if (rect.width < 6 || rect.height < 6) continue;
|
|
5185
|
+
// Viewport-bound probe: keep text whose box overlaps the live viewport.
|
|
5186
|
+
if (rect.bottom <= 0 || rect.top >= vh) continue;
|
|
5187
|
+
textEls.push({ el, rect, text, inSvg });
|
|
5188
|
+
}
|
|
5189
|
+
|
|
5190
|
+
for (const victim of textEls) {
|
|
5191
|
+
const { el, rect, text } = victim;
|
|
5192
|
+
if (seenVictims.has(el)) continue;
|
|
5193
|
+
const style = getComputedStyle(el);
|
|
5194
|
+
if (isScreenReaderOnlyTextStyle(style, { width: rect.width, height: rect.height, clientWidth: el.clientWidth, clientHeight: el.clientHeight })) continue;
|
|
5195
|
+
|
|
5196
|
+
const cols = Math.max(6, Math.min(30, Math.round(rect.width / 12)));
|
|
5197
|
+
const rows = Math.max(1, Math.min(4, Math.round(rect.height / 14)));
|
|
5198
|
+
let total = 0;
|
|
5199
|
+
let occluded = 0;
|
|
5200
|
+
let occluderEl = null;
|
|
5201
|
+
let occluderKind = '';
|
|
5202
|
+
for (let i = 0; i < cols; i++) {
|
|
5203
|
+
const x = rect.left + rect.width * ((i + 0.5) / cols);
|
|
5204
|
+
if (x < 1 || x > vw - 1) continue;
|
|
5205
|
+
for (let j = 0; j < rows; j++) {
|
|
5206
|
+
const y = rect.top + rect.height * ((j + 0.5) / rows);
|
|
5207
|
+
if (y < 1 || y > vh - 1) continue;
|
|
5208
|
+
total++;
|
|
5209
|
+
const top = document.elementFromPoint(x, y);
|
|
5210
|
+
if (!top) continue;
|
|
5211
|
+
// Text visible here: the probe returns the text itself, a descendant,
|
|
5212
|
+
// or one of its ancestors (the text's own container / background).
|
|
5213
|
+
if (top === el || el.contains(top) || top.contains(el)) continue;
|
|
5214
|
+
const topCs = getComputedStyle(top);
|
|
5215
|
+
if (isFloated(topCs) || isMarqueeish(top, topCs) || isPinnedOverlay(top)) continue;
|
|
5216
|
+
const topTag = top.tagName.toLowerCase();
|
|
5217
|
+
// Text sitting under a raw image/video is contrast territory (deduped
|
|
5218
|
+
// against the pixel low-contrast rule); leave those alone here.
|
|
5219
|
+
if (['img', 'video', 'canvas', 'picture'].includes(topTag)) continue;
|
|
5220
|
+
const topHasText = elementDirectText(top).length > 0 || !!top.closest('svg');
|
|
5221
|
+
if (isOpaqueDecoratedBox(topCs)) {
|
|
5222
|
+
occluded++;
|
|
5223
|
+
if (!occluderEl) { occluderEl = top; occluderKind = 'box'; }
|
|
5224
|
+
} else if (topHasText) {
|
|
5225
|
+
occluded++;
|
|
5226
|
+
if (!occluderEl) { occluderEl = top; occluderKind = 'text'; }
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
if (total === 0 || !occluderEl) continue;
|
|
5231
|
+
const occFrac = occluded / total;
|
|
5232
|
+
// A solid box's paint fills its rect, so box coverage is real at a lower
|
|
5233
|
+
// bar. Text coverage rides on elementFromPoint returning the occluder's box
|
|
5234
|
+
// (line box / container), which can exceed its actual glyph ink, so the
|
|
5235
|
+
// text bar is higher — partial overlaps below it are crowding, not burial.
|
|
5236
|
+
if (occFrac < (occluderKind === 'text' ? 0.45 : 0.3)) continue;
|
|
5237
|
+
|
|
5238
|
+
// (i) Substantial occlusion: a real slab of the text is behind something.
|
|
5239
|
+
if (occluderKind === 'text') {
|
|
5240
|
+
// Two SVG texts inside the same emblem (concentric arcs, monogram) are one
|
|
5241
|
+
// decorative unit, not a collision.
|
|
5242
|
+
const victimSvg = el.closest('svg');
|
|
5243
|
+
const occSvg = occluderEl.closest('svg');
|
|
5244
|
+
if (victimSvg && occSvg && victimSvg === occSvg) continue;
|
|
5245
|
+
// Both sides in plain flow: the overlap is line-box bleed from tight
|
|
5246
|
+
// leading (a big headline reaching up over its own eyebrow), not one text
|
|
5247
|
+
// run painted over another.
|
|
5248
|
+
if (!isLayeredElement(el) && !isLayeredElement(occluderEl)) continue;
|
|
5249
|
+
}
|
|
5250
|
+
seenVictims.add(el);
|
|
5251
|
+
findings.push({
|
|
5252
|
+
el,
|
|
5253
|
+
type: 'text-occlusion',
|
|
5254
|
+
detail: `${classSelector(el)} "${text.slice(0, 24)}" is ${Math.round(occFrac * 100)}% covered by ${occluderKind === 'text' ? 'overlapping text' : 'an opaque element'} (${classSelector(occluderEl)})`,
|
|
5255
|
+
});
|
|
5256
|
+
}
|
|
5257
|
+
|
|
5258
|
+
// (ii) Headline overhanging an opaque card: a display-scale line whose bulk
|
|
5259
|
+
// sits outside a bounded content card but whose edge clips into it. The text
|
|
5260
|
+
// may still paint on top and stay readable, but the two layers were dropped
|
|
5261
|
+
// on the same pixels — a placement collision, not a composition.
|
|
5262
|
+
const cards = [];
|
|
5263
|
+
for (const el of document.querySelectorAll('body *')) {
|
|
5264
|
+
if (el.closest('svg')) continue;
|
|
5265
|
+
if (!isPaintedForOcclusion(el)) continue;
|
|
5266
|
+
const cs = getComputedStyle(el);
|
|
5267
|
+
const bg = parseAnyColor(cs.backgroundColor || '');
|
|
5268
|
+
const bgImg = cs.backgroundImage || '';
|
|
5269
|
+
if (!bg || (bg.a ?? 1) <= 0.7) continue;
|
|
5270
|
+
if (bgImg && bgImg !== 'none' && /(gradient|url)\(/i.test(bgImg)) continue;
|
|
5271
|
+
const hasBorder = ['Top', 'Right', 'Bottom', 'Left'].some((s) => (parseFloat(cs[`border${s}Width`]) || 0) > 0);
|
|
5272
|
+
const hasShadow = cs.boxShadow && cs.boxShadow !== 'none';
|
|
5273
|
+
if (!hasBorder && !hasShadow) continue;
|
|
5274
|
+
if (isPinnedOverlay(el)) continue;
|
|
5275
|
+
let cr; try { cr = el.getBoundingClientRect(); } catch { continue; }
|
|
5276
|
+
if (cr.width < 100 || cr.width > 0.8 * vw || cr.height < 60) continue;
|
|
5277
|
+
cards.push({ el, rect: cr });
|
|
5278
|
+
}
|
|
5279
|
+
for (const victim of textEls) {
|
|
5280
|
+
const { el, rect, text } = victim;
|
|
5281
|
+
if (seenVictims.has(el)) continue;
|
|
5282
|
+
const style = getComputedStyle(el);
|
|
5283
|
+
if ((parseFloat(style.fontSize) || 16) < 40) continue;
|
|
5284
|
+
let lineHeight = parseFloat(style.lineHeight);
|
|
5285
|
+
if (!Number.isFinite(lineHeight)) lineHeight = (parseFloat(style.fontSize) || 16) * 1.2;
|
|
5286
|
+
const centerX = rect.left + rect.width / 2;
|
|
5287
|
+
for (const card of cards) {
|
|
5288
|
+
if (card.el === el || el.contains(card.el) || card.el.contains(el)) continue;
|
|
5289
|
+
const ix = Math.max(0, Math.min(rect.right, card.rect.right) - Math.max(rect.left, card.rect.left));
|
|
5290
|
+
const iy = Math.max(0, Math.min(rect.bottom, card.rect.bottom) - Math.max(rect.top, card.rect.top));
|
|
5291
|
+
if (ix < 8 || iy < 0.5 * lineHeight) continue;
|
|
5292
|
+
// The headline's bulk must sit outside the card — only its edge clips in.
|
|
5293
|
+
if (centerX >= card.rect.left && centerX <= card.rect.right) continue;
|
|
5294
|
+
if (ix > 0.5 * rect.width) continue;
|
|
5295
|
+
seenVictims.add(el);
|
|
5296
|
+
findings.push({
|
|
5297
|
+
el,
|
|
5298
|
+
type: 'text-occlusion',
|
|
5299
|
+
detail: `${classSelector(el)} "${text.slice(0, 24)}" overhangs ${classSelector(card.el)} by ${Math.round(ix)}px — the headline and the card collide`,
|
|
5300
|
+
});
|
|
5301
|
+
break;
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
|
|
5305
|
+
// (iii) Inline padding leak: an inline element with an opaque background and
|
|
5306
|
+
// large vertical padding paints a filled block whose padding-box overflows
|
|
5307
|
+
// its line (inline padding reserves no vertical space), so the fill lands on
|
|
5308
|
+
// the content above and below instead of enclosing its own text. The
|
|
5309
|
+
// canonical bug is a class-name collision that hands a decorative marker a
|
|
5310
|
+
// payoff card's padding. The tell is a rendered height several times the line
|
|
5311
|
+
// height, which distinguishes the leak from a padded inline highlight.
|
|
5312
|
+
for (const el of document.querySelectorAll('body *')) {
|
|
5313
|
+
if (el.closest('svg')) continue;
|
|
5314
|
+
if (!isPaintedForOcclusion(el)) continue;
|
|
5315
|
+
const cs = getComputedStyle(el);
|
|
5316
|
+
if (cs.display !== 'inline') continue;
|
|
5317
|
+
const bg = parseAnyColor(cs.backgroundColor || '');
|
|
5318
|
+
if (!bg || (bg.a ?? 1) <= 0.6) continue;
|
|
5319
|
+
const padTop = parseFloat(cs.paddingTop) || 0;
|
|
5320
|
+
const padBottom = parseFloat(cs.paddingBottom) || 0;
|
|
5321
|
+
if (padTop + padBottom < 24) continue;
|
|
5322
|
+
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
|
|
5323
|
+
if (rect.width < 12 || rect.height < 24) continue;
|
|
5324
|
+
const fontSize = parseFloat(cs.fontSize) || 16;
|
|
5325
|
+
let lineHeight = parseFloat(cs.lineHeight);
|
|
5326
|
+
if (!Number.isFinite(lineHeight)) lineHeight = fontSize * 1.4;
|
|
5327
|
+
// The padding box has to overflow the line by a clear margin — a padded
|
|
5328
|
+
// inline highlight sits at roughly one line height, the leak at several.
|
|
5329
|
+
if (rect.height < 2.2 * lineHeight) continue;
|
|
5330
|
+
if (seenVictims.has(el)) continue;
|
|
5331
|
+
// Name a neighbour the fill lands on, if one is nearby (paint state aside,
|
|
5332
|
+
// reveal-on-scroll siblings still occupy the space it covers).
|
|
5333
|
+
let overlaps = null;
|
|
5334
|
+
for (const other of el.parentElement ? el.parentElement.children : []) {
|
|
5335
|
+
if (other === el || el.contains(other) || other.contains(el)) continue;
|
|
5336
|
+
if (getComputedStyle(other).display === 'none') continue;
|
|
5337
|
+
const oRect = other.getBoundingClientRect();
|
|
5338
|
+
const ix = Math.max(0, Math.min(rect.right, oRect.right) - Math.max(rect.left, oRect.left));
|
|
5339
|
+
const iy = Math.max(0, Math.min(rect.bottom, oRect.bottom) - Math.max(rect.top, oRect.top));
|
|
5340
|
+
if (ix > 4 && iy > 4 && (other.textContent || '').trim().length > 0) { overlaps = other; break; }
|
|
5341
|
+
}
|
|
5342
|
+
seenVictims.add(el);
|
|
5343
|
+
findings.push({
|
|
5344
|
+
el,
|
|
5345
|
+
type: 'text-occlusion',
|
|
5346
|
+
detail: `${classSelector(el)} is an inline element whose opaque fill leaks ${Math.round(rect.height)}px past its line${overlaps ? ` onto ${classSelector(overlaps)}` : ''}`,
|
|
5347
|
+
});
|
|
5348
|
+
}
|
|
5349
|
+
|
|
5350
|
+
return findings;
|
|
5351
|
+
}
|
|
5352
|
+
|
|
5353
|
+
// ---------------------------------------------------------------------------
|
|
5354
|
+
// First-viewport column overflow — the stretched-hero signature (browser-only)
|
|
5355
|
+
// ---------------------------------------------------------------------------
|
|
5356
|
+
|
|
5357
|
+
// A multi-column composition that opens the page (grid/flex with two or more
|
|
5358
|
+
// side-by-side columns, each a real share of the width) where one column's
|
|
5359
|
+
// content runs far past the fold while its sibling fits inside a single
|
|
5360
|
+
// viewport. The row stretches to the tall column, so the short one floats in a
|
|
5361
|
+
// screen-and-a-half of dead space and the fold falls deep inside a single
|
|
5362
|
+
// section. Single-column pages and full-page heroes (no sibling column) are
|
|
5363
|
+
// exempt because there is no fitting sibling to contrast against.
|
|
5364
|
+
function checkFirstViewportColumnOverflowDOM() {
|
|
5365
|
+
const findings = [];
|
|
5366
|
+
const vw = window.innerWidth || 1280;
|
|
5367
|
+
const vh = window.innerHeight || 800;
|
|
5368
|
+
const isMultiCol = (s) => /(^|inline-)(grid|flex)$/.test(String(s.display || ''));
|
|
5369
|
+
|
|
5370
|
+
for (const el of document.querySelectorAll('body *')) {
|
|
5371
|
+
const style = getComputedStyle(el);
|
|
5372
|
+
if (!isMultiCol(style)) continue;
|
|
5373
|
+
let rect; try { rect = el.getBoundingClientRect(); } catch { continue; }
|
|
5374
|
+
if (rect.width < 0.5 * vw) continue;
|
|
5375
|
+
const pageTop = rect.top + (window.scrollY || 0);
|
|
5376
|
+
const pageBottom = pageTop + rect.height;
|
|
5377
|
+
// The fold must fall inside this container: it opens within the first
|
|
5378
|
+
// viewport and runs past it.
|
|
5379
|
+
if (pageTop >= vh * 0.9 || pageBottom <= vh) continue;
|
|
5380
|
+
|
|
5381
|
+
// Direct children that read as side-by-side columns: a real width share,
|
|
5382
|
+
// not full-bleed (stacked single column), sharing the container's top row.
|
|
5383
|
+
const cols = [];
|
|
5384
|
+
for (const child of el.children) {
|
|
5385
|
+
const cs = getComputedStyle(child);
|
|
5386
|
+
if (cs.display === 'none') continue;
|
|
5387
|
+
if (String(cs.position || '') === 'absolute' || String(cs.position || '') === 'fixed') continue;
|
|
5388
|
+
let cr; try { cr = child.getBoundingClientRect(); } catch { continue; }
|
|
5389
|
+
const wShare = cr.width / rect.width;
|
|
5390
|
+
if (wShare < 0.25 || wShare > 0.9) continue;
|
|
5391
|
+
if (cr.height < 40) continue;
|
|
5392
|
+
// Content extent: how far the child's own content actually reaches,
|
|
5393
|
+
// independent of a stretched row height.
|
|
5394
|
+
let contentBottom = cr.top;
|
|
5395
|
+
for (const d of child.querySelectorAll('*')) {
|
|
5396
|
+
const ds = getComputedStyle(d);
|
|
5397
|
+
if (ds.position === 'absolute' || ds.position === 'fixed') continue;
|
|
5398
|
+
if (ds.display === 'none' || ds.visibility === 'hidden') continue;
|
|
5399
|
+
let dr; try { dr = d.getBoundingClientRect(); } catch { continue; }
|
|
5400
|
+
if (dr.width > 0 && dr.height > 0) contentBottom = Math.max(contentBottom, dr.bottom);
|
|
5401
|
+
}
|
|
5402
|
+
cols.push({ child, top: cr.top, contentH: contentBottom - cr.top });
|
|
5403
|
+
}
|
|
5404
|
+
if (cols.length < 2) continue;
|
|
5405
|
+
// Side-by-side: the two candidate columns must share the top row.
|
|
5406
|
+
cols.sort((a, b) => b.contentH - a.contentH);
|
|
5407
|
+
const tall = cols[0];
|
|
5408
|
+
const shortest = cols[cols.length - 1];
|
|
5409
|
+
if (Math.abs(tall.top - shortest.top) > 0.25 * vh) continue;
|
|
5410
|
+
if (tall.contentH <= vh * 1.4) continue;
|
|
5411
|
+
if (shortest.contentH > vh) continue;
|
|
5412
|
+
|
|
5413
|
+
findings.push({
|
|
5414
|
+
el,
|
|
5415
|
+
type: 'first-viewport-column-overflow',
|
|
5416
|
+
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`,
|
|
5417
|
+
});
|
|
5418
|
+
}
|
|
5419
|
+
return findings;
|
|
5420
|
+
}
|
|
5421
|
+
|
|
2630
5422
|
export {
|
|
5423
|
+
CSS_NAMED_COLORS,
|
|
2631
5424
|
checkBorders,
|
|
2632
5425
|
isEmojiOnlyText,
|
|
2633
5426
|
checkColors,
|
|
5427
|
+
checkHoverContrast,
|
|
5428
|
+
checkElementHoverContrast,
|
|
5429
|
+
parseColorMix,
|
|
5430
|
+
compositeColorOver,
|
|
2634
5431
|
isCardLikeFromProps,
|
|
2635
5432
|
checkIconTile,
|
|
2636
5433
|
resolveSerif,
|
|
2637
5434
|
checkItalicSerif,
|
|
2638
5435
|
isAccentColor,
|
|
2639
5436
|
checkHeroEyebrow,
|
|
2640
|
-
|
|
5437
|
+
checkKickerAboveHeading,
|
|
2641
5438
|
checkMotion,
|
|
2642
5439
|
checkGlow,
|
|
5440
|
+
scanCssTextForGlow,
|
|
5441
|
+
scanCssTextForGridBackground,
|
|
5442
|
+
scanCssTextForRadialHalo,
|
|
5443
|
+
scanCssTextForPseudoStripe,
|
|
5444
|
+
scanCssTextForInsetStripe,
|
|
5445
|
+
scanCssTextForMarquee,
|
|
5446
|
+
collectMarqueeKeyframes,
|
|
5447
|
+
collectCssCustomProps,
|
|
5448
|
+
cssLengthToPx,
|
|
5449
|
+
scanCssTextForPulsingDot,
|
|
5450
|
+
scanHtmlForShapeAssembledIllustration,
|
|
2643
5451
|
checkHtmlPatterns,
|
|
2644
5452
|
readOwnBackgroundColor,
|
|
2645
5453
|
resolveBackground,
|
|
@@ -2657,12 +5465,28 @@ export {
|
|
|
2657
5465
|
parseAnyColor,
|
|
2658
5466
|
parseColorResolved,
|
|
2659
5467
|
cleanInlineText,
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
5468
|
+
isKickerCandidate,
|
|
5469
|
+
collectKickerCandidates,
|
|
5470
|
+
checkKickerAboveHeadingDOM,
|
|
5471
|
+
parseNumberedLabelText,
|
|
5472
|
+
isNumberedSectionLabelCandidate,
|
|
5473
|
+
collectNumberedSectionLabelCandidates,
|
|
5474
|
+
checkNumberedSectionLabels,
|
|
5475
|
+
checkNumberedSectionLabelsFromDoc,
|
|
5476
|
+
checkNumberedSectionLabelsDOM,
|
|
5477
|
+
checkEmDashOveruse,
|
|
5478
|
+
checkEmDashOveruseDOM,
|
|
5479
|
+
isRepeatedTextContainer,
|
|
5480
|
+
collectRepeatedContainerTextFindings,
|
|
5481
|
+
checkRepeatedContainerTextFromDoc,
|
|
5482
|
+
checkRepeatedContainerTextDOM,
|
|
5483
|
+
checkElementPseudoStripeDOM,
|
|
2663
5484
|
checkElementMotionDOM,
|
|
2664
5485
|
checkElementGlowDOM,
|
|
2665
5486
|
checkElementAIPaletteDOM,
|
|
5487
|
+
checkElementRadialSpotlightDOM,
|
|
5488
|
+
checkElementRadialSpotlight,
|
|
5489
|
+
checkRadialSpotlight,
|
|
2666
5490
|
resolveFontSizePx,
|
|
2667
5491
|
resolveLengthPx,
|
|
2668
5492
|
checkQuality,
|
|
@@ -2675,7 +5499,7 @@ export {
|
|
|
2675
5499
|
checkElementIconTile,
|
|
2676
5500
|
checkElementItalicSerif,
|
|
2677
5501
|
checkElementHeroEyebrow,
|
|
2678
|
-
|
|
5502
|
+
checkKickerAboveHeadingFromDoc,
|
|
2679
5503
|
checkElementMotion,
|
|
2680
5504
|
checkElementGlow,
|
|
2681
5505
|
checkTypography,
|
|
@@ -2698,4 +5522,13 @@ export {
|
|
|
2698
5522
|
checkElementClippedOverflowDOM,
|
|
2699
5523
|
isScreenReaderOnlyTextStyle,
|
|
2700
5524
|
checkElementTextOverflowDOM,
|
|
5525
|
+
checkHeadingRhythmDOM,
|
|
5526
|
+
checkElementBlinkingCursorDOM,
|
|
5527
|
+
measureHiddenTextDOM,
|
|
5528
|
+
checkContentHiddenAtRest,
|
|
5529
|
+
checkEdgeFlushCardsDOM,
|
|
5530
|
+
isOpaqueDecoratedBox,
|
|
5531
|
+
isLayeredElement,
|
|
5532
|
+
checkTextOcclusionDOM,
|
|
5533
|
+
checkFirstViewportColumnOverflowDOM,
|
|
2701
5534
|
};
|