codehere 0.7.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/dist/application/services/verdict-service.js +154 -0
- package/dist/application/use-cases/assess-cross-vendor.js +126 -0
- package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
- package/dist/application/use-cases/export-aat.js +107 -0
- package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
- package/dist/application/use-cases/generate-final-report.js +140 -15
- package/dist/application/use-cases/generate-narration.js +5 -3
- package/dist/application/use-cases/generate-thread-title.js +5 -2
- package/dist/application/use-cases/narrator-prompt-template.js +1 -1
- package/dist/application/use-cases/plan-auto-review.js +57 -0
- package/dist/application/use-cases/reconcile-audit.js +100 -0
- package/dist/application/use-cases/rerun-tests.js +226 -0
- package/dist/application/use-cases/search-sessions.js +70 -0
- package/dist/application/use-cases/should-auto-review.js +71 -0
- package/dist/application/use-cases/thread-export.js +90 -0
- package/dist/application/use-cases/verify-claim.js +187 -5
- package/dist/domain/audit.js +11 -0
- package/dist/domain/services/codebase-detector.js +0 -6
- package/dist/domain/task.js +91 -0
- package/dist/error-handler.js +3 -115
- package/dist/index.js +279 -31
- package/dist/infrastructure/completion/bash-completion.js +7 -3
- package/dist/infrastructure/completion/fish-completion.js +7 -12
- package/dist/infrastructure/completion/zsh-completion.js +7 -12
- package/dist/infrastructure/config/config-manager.js +4 -3
- package/dist/infrastructure/config/product-context.js +1 -19
- package/dist/infrastructure/context/adapters/figma-source.js +37 -2
- package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
- package/dist/infrastructure/context/github-api.js +110 -0
- package/dist/infrastructure/context/github-device-flow.js +65 -0
- package/dist/infrastructure/context/oauth-store.js +5 -2
- package/dist/infrastructure/context/registry-bootstrap.js +0 -4
- package/dist/infrastructure/cost/budget-config.js +4 -3
- package/dist/infrastructure/cost/cost-router.js +16 -23
- package/dist/infrastructure/cost/spend-aggregator.js +91 -16
- package/dist/infrastructure/cost/team-ledger.js +355 -0
- package/dist/infrastructure/discovery/discovery-log.js +85 -0
- package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
- package/dist/infrastructure/hooks/claude-sdk-hook.js +139 -3
- package/dist/infrastructure/hooks/hook-mode.js +20 -4
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
- package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
- package/dist/infrastructure/observability/execution-tracer.js +1 -1
- package/dist/infrastructure/orchestration/agents-config-seed.js +23 -11
- package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
- package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
- package/dist/infrastructure/orchestration/cassette-recorder.js +1 -1
- package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
- package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
- package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
- package/dist/infrastructure/orchestration/context-handoff.js +32 -14
- package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
- package/dist/infrastructure/orchestration/failover-config.js +1 -11
- package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +134 -3
- package/dist/infrastructure/orchestration/graphify-context.js +14 -59
- package/dist/infrastructure/orchestration/memory-injector.js +4 -1
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +167 -9
- package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
- package/dist/infrastructure/orchestration/progress-aggregator.js +275 -23
- package/dist/infrastructure/orchestration/session-manager.js +593 -94
- package/dist/infrastructure/orchestration/session-persistence.js +177 -41
- package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
- package/dist/infrastructure/protocols/cloud-sync.js +2 -2
- package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
- package/dist/infrastructure/protocols/mcp-server-impl.js +1498 -354
- package/dist/infrastructure/protocols/mcp-tools.js +401 -0
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
- package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
- package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
- package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
- package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
- package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
- package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
- package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
- package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
- package/dist/infrastructure/protocols/ui/styles.js +2147 -0
- package/dist/infrastructure/protocols/ui/tokens.js +96 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +116 -4126
- package/dist/infrastructure/protocols/verify-page.js +884 -148
- package/dist/infrastructure/security/fix-code.js +144 -0
- package/dist/infrastructure/security/fix-secret.js +89 -0
- package/dist/infrastructure/security/permissions-config.js +3 -2
- package/dist/infrastructure/security/scan-types.js +10 -0
- package/dist/infrastructure/security/ship-scan.js +195 -0
- package/dist/infrastructure/security/trust-scorer.js +70 -3
- package/dist/infrastructure/security/vibe-checks.js +99 -0
- package/dist/infrastructure/storage/atomic-write.js +86 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +151 -4
- package/dist/infrastructure/storage/project-thread-reader.js +262 -25
- package/dist/infrastructure/storage/project-thread-title-backfill.js +1 -1
- package/dist/monitoring.js +2 -2
- package/dist/presentation/cli/commands/audit-command.js +79 -4
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/check-command.js +96 -0
- package/dist/presentation/cli/commands/context-source-command.js +20 -1
- package/dist/presentation/cli/commands/help-command.js +5 -5
- package/dist/presentation/cli/commands/hook-command.js +5 -5
- package/dist/presentation/cli/commands/init-command.js +89 -66
- package/dist/presentation/cli/commands/memory-command.js +28 -28
- package/dist/presentation/cli/commands/permission-command.js +1 -12
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/protocols-command.js +4 -4
- package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
- package/dist/presentation/cli/commands/report-command.js +48 -32
- package/dist/presentation/cli/commands/scan-command.js +52 -0
- package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
- package/dist/presentation/cli/commands/search-command.js +51 -0
- package/dist/presentation/cli/commands/setup-command.js +31 -8
- package/dist/presentation/cli/commands/status-command.js +39 -86
- package/dist/presentation/cli/commands/team-command.js +256 -0
- package/dist/presentation/cli/commands/thread-command.js +87 -5
- package/dist/presentation/cli/commands/trust-command.js +53 -31
- package/dist/presentation/cli/commands/verify-command.js +74 -9
- package/dist/presentation/cli/error-display.js +0 -8
- package/dist/presentation/cli/repl.js +15 -7
- package/dist/presentation/cli/task-deliverable.js +36 -10
- package/dist/testing/replay-adapter.js +0 -19
- package/dist/ui.js +51 -239
- package/dist/utils/version.js +0 -6
- package/package.json +30 -10
- package/dist/domain/entities/task.js +0 -61
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/task-repository.js +0 -206
|
@@ -22,122 +22,131 @@
|
|
|
22
22
|
* highlight ring + caption (the visibleAct pattern) and can record one .webm
|
|
23
23
|
* per fixture in-browser, or be driven headless by scripts/record-replay.mjs.
|
|
24
24
|
*/
|
|
25
|
+
import { violatesGitInvariant } from '../../application/use-cases/verify-claim.js';
|
|
26
|
+
import { DESIGN_TOKENS } from './ui/tokens.js';
|
|
27
|
+
import { FONT_FACES } from './ui/font-faces.js';
|
|
25
28
|
export const VERIFY_HTML = `<!doctype html>
|
|
26
29
|
<html lang="en">
|
|
27
30
|
<head>
|
|
28
31
|
<meta charset="utf-8" />
|
|
29
32
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
33
|
+
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
|
30
34
|
<title>codehere · /verify</title>
|
|
31
|
-
<style
|
|
32
|
-
:root
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
--allow:#28c840; --block:#ff5f57; --warn:#febc2e; --info:#76b7ff;
|
|
39
|
-
--radius:8px;
|
|
40
|
-
--sans:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
|
|
41
|
-
--mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;
|
|
42
|
-
}
|
|
35
|
+
<style>${FONT_FACES}${DESIGN_TOKENS}
|
|
36
|
+
/* /verify used to declare its own :root here: cool-gray surfaces from the
|
|
37
|
+
pre-v1 palette, Inter as the voice face, and macOS traffic lights for the
|
|
38
|
+
verdict colours (--allow was var(--verified)). That made three different "verified"
|
|
39
|
+
greens in one product, on a page linked directly from codehere start.
|
|
40
|
+
It now takes the same tokens and the same self-hosted faces as the
|
|
41
|
+
dashboard. DESIGN.md §4.5 always said it should. */
|
|
43
42
|
*{box-sizing:border-box}
|
|
44
43
|
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);font-size:13px;line-height:1.55;-webkit-font-smoothing:antialiased;
|
|
45
44
|
background-image:radial-gradient(circle,rgba(74,144,226,.045) 1px,transparent 1px);background-size:24px 24px;}
|
|
46
45
|
a{color:inherit}
|
|
47
46
|
header.top{display:flex;align-items:center;gap:14px;padding:10px 20px;border-bottom:1px solid var(--border-soft);background:rgba(0,0,0,.55);backdrop-filter:blur(8px);position:sticky;top:0;z-index:5;flex-wrap:wrap}
|
|
48
|
-
.logo{font-size:
|
|
49
|
-
|
|
50
|
-
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
|
47
|
+
.logo{font-size:14.5px;font-weight:700;display:inline-flex;align-items:center;gap:7px;letter-spacing:-.01em}
|
|
48
|
+
/* S16 brand-sync: gradient name only, no dot — the landing nav wordmark carries none */
|
|
51
49
|
.logo .name{background:linear-gradient(135deg,#ff9a76 0%,#4a90e2 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
|
52
50
|
.route{color:var(--fg-40);font-family:var(--mono);font-size:12px}
|
|
53
51
|
.pill{font-size:10.5px;padding:3px 8px;border:1px solid var(--border);border-radius:999px;color:var(--fg-50)}
|
|
54
|
-
.pill.local{border-color:
|
|
52
|
+
.pill.local{border-color:var(--border);color:var(--fg-70)}
|
|
55
53
|
.spacer{flex:1}
|
|
56
|
-
.hint{color:var(--fg-40);font-size:
|
|
54
|
+
.hint{color:var(--fg-40);font-size:12px}
|
|
57
55
|
.hint code{font-family:var(--mono);color:var(--info)}
|
|
58
56
|
.wrap{max-width:1180px;margin:0 auto;padding:22px 20px 80px}
|
|
59
57
|
.actions{display:flex;align-items:center;gap:12px;margin:18px 0 8px;flex-wrap:wrap}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
/* S17 · same token system as the product page: ONE flat accent fill per
|
|
59
|
+
action row (no gradient, no glow), calm hover, visible focus ring.
|
|
60
|
+
Buttons don't inherit type by default — pull every control into the system. */
|
|
61
|
+
button,select,input{font:inherit}
|
|
62
|
+
button.run{background:var(--accent);color:#1a0f08;border:0;font-weight:600;font-size:13px;padding:9px 18px;border-radius:var(--radius);cursor:pointer;transition:filter var(--dur) var(--ease)}
|
|
63
|
+
button.run:hover{filter:brightness(1.06)}
|
|
64
|
+
button.run:disabled{opacity:.6;cursor:default;filter:none}
|
|
65
|
+
button.run:focus-visible,.linkbtn:focus-visible,.cell:focus-visible,.steprow:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
66
|
+
.linkbtn{color:var(--fg-70);text-decoration:none;border:1px solid var(--border);padding:8px 14px;border-radius:var(--radius);font-size:12px;cursor:pointer;background:transparent}
|
|
63
67
|
.linkbtn:hover{border-color:var(--accent);color:var(--fg)}
|
|
64
68
|
.tally{display:inline-flex;gap:14px;align-items:center;font-family:var(--mono);font-size:13px;margin-left:6px}
|
|
65
|
-
.tally .p{color:var(--
|
|
69
|
+
.tally .p{color:var(--verified)} .tally .f{color:var(--caught)} .tally .b{color:var(--fg-40)} .tally .t{color:var(--fg-50)}
|
|
66
70
|
.verdict-line{font-size:12px;margin:4px 0 0}
|
|
67
|
-
.verdict-line.green{color:var(--
|
|
68
|
-
.legend{display:flex;gap:18px;margin:8px 0 0;font-size:
|
|
71
|
+
.verdict-line.green[data-evidence='measured']{color:var(--verified)} .verdict-line.red{color:var(--caught)}
|
|
72
|
+
.legend{display:flex;gap:18px;margin:8px 0 0;font-size:12px;color:var(--fg-50);flex-wrap:wrap}
|
|
69
73
|
.legend b{font-family:var(--mono)}
|
|
70
|
-
h2.section{font-size:
|
|
74
|
+
h2.section{font-size:12px;text-transform:uppercase;letter-spacing:.12em;color:var(--fg-40);margin:26px 0 10px;font-weight:600}
|
|
71
75
|
.verifiers{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px}
|
|
72
|
-
.vchip{border:1px solid var(--border);border-radius:999px;padding:4px 11px;font-size:
|
|
76
|
+
.vchip{border:1px solid var(--border);border-radius:999px;padding:4px 11px;font-size:12px;color:var(--fg-70)}
|
|
73
77
|
.vchip code{font-family:var(--mono);color:var(--accent)}
|
|
74
|
-
table.matrix{width:100%;border-collapse:collapse;font-size:
|
|
78
|
+
table.matrix{width:100%;border-collapse:collapse;font-size:12px;border:1px solid var(--border-soft);border-radius:var(--radius);overflow:hidden}
|
|
75
79
|
table.matrix th,table.matrix td{padding:9px 11px;text-align:left;border-bottom:1px solid var(--border-soft)}
|
|
76
80
|
table.matrix thead th{font-size:10.5px;text-transform:uppercase;letter-spacing:.08em;color:var(--fg-40);background:var(--bg-1);font-weight:600;vertical-align:bottom}
|
|
77
81
|
table.matrix tbody tr:hover{background:rgba(255,255,255,.02)}
|
|
78
82
|
.unit-id{font-family:var(--mono);color:var(--fg)}
|
|
79
|
-
.unit-desc{color:var(--fg-50);font-size:
|
|
80
|
-
.needs{font-family:var(--mono);color:var(--fg-30);font-size:
|
|
83
|
+
.unit-desc{color:var(--fg-50);font-size:12px}
|
|
84
|
+
.needs{font-family:var(--mono);color:var(--fg-30);font-size:12px}
|
|
81
85
|
.cell{display:inline-flex;align-items:center;justify-content:center;width:26px;height:22px;border-radius:5px;font-size:12px;cursor:pointer;border:0;background:transparent;color:var(--fg-30);font-family:var(--mono)}
|
|
82
|
-
.cell.pass{background:rgba(
|
|
83
|
-
.cell.fail{background:rgba(
|
|
86
|
+
.cell.pass{background:rgba(127,195,154,.12);color:var(--verified)}
|
|
87
|
+
.cell.fail{background:rgba(226,130,127,.16);color:var(--caught);box-shadow:0 0 0 1px rgba(226,130,127,.5)}
|
|
84
88
|
.cell.fail.expected{animation:flag 1.6s infinite}
|
|
85
89
|
.cell.blocked{background:rgba(255,255,255,.05);color:var(--fg-40)}
|
|
86
|
-
@keyframes flag{0%,100%{box-shadow:0 0 0 1px rgba(
|
|
90
|
+
@keyframes flag{0%,100%{box-shadow:0 0 0 1px rgba(226,130,127,.5)}50%{box-shadow:0 0 0 1px rgba(226,130,127,1),0 0 12px rgba(226,130,127,.5)}}
|
|
87
91
|
.fixhdr{font-family:var(--mono);font-size:10.5px;color:var(--fg-50);writing-mode:vertical-rl;transform:rotate(180deg);white-space:nowrap;height:96px}
|
|
88
|
-
.fixhdr.probe{color:var(--
|
|
89
|
-
.probe-row td{background:rgba(
|
|
90
|
-
.probe-tag{font-size:
|
|
91
|
-
.note{color:var(--fg-40);font-size:
|
|
92
|
+
.fixhdr.probe{color:var(--caught)}
|
|
93
|
+
.probe-row td{background:rgba(226,130,127,.04)}
|
|
94
|
+
.probe-tag{font-size:10.5px;color:var(--caught);border:1px solid rgba(226,130,127,.4);border-radius:4px;padding:1px 5px;margin-left:8px;font-family:var(--mono)}
|
|
95
|
+
.note{color:var(--fg-40);font-size:12px;margin-top:8px}
|
|
92
96
|
.note code{font-family:var(--mono);color:var(--info)}
|
|
93
97
|
/* replay */
|
|
94
98
|
.grid2{display:grid;grid-template-columns:1.4fr 1fr;gap:18px;margin-top:6px}
|
|
95
99
|
.stage-wrap{border:1px solid var(--border);border-radius:10px;overflow:hidden;background:var(--bg-1);position:relative}
|
|
96
|
-
.stage-wrap.v-pass{box-shadow:0 0 0 1px rgba(
|
|
97
|
-
.stage-wrap.v-fail{box-shadow:0 0 0 1px rgba(
|
|
100
|
+
.stage-wrap.v-pass{box-shadow:0 0 0 1px rgba(127,195,154,.4)}
|
|
101
|
+
.stage-wrap.v-fail{box-shadow:0 0 0 1px rgba(226,130,127,.55)}
|
|
98
102
|
.stage-wrap.v-blocked{box-shadow:0 0 0 1px rgba(255,255,255,.12)}
|
|
99
103
|
.titlebar{display:flex;align-items:center;gap:7px;padding:8px 12px;background:var(--bg-2);border-bottom:1px solid var(--border-soft)}
|
|
100
|
-
.tl-dot{width:11px;height:11px;border-radius:50%}.tl-dot.r{background
|
|
101
|
-
.tl-title{margin-left:8px;font-size:
|
|
104
|
+
.tl-dot{width:11px;height:11px;border-radius:50%}.tl-dot.r{background:var(--caught)}.tl-dot.y{background:var(--warn)}.tl-dot.g{background:var(--verified)}
|
|
105
|
+
.tl-title{margin-left:8px;font-size:12px;color:var(--fg-70)}.tl-title .dim{color:var(--fg-30);font-family:var(--mono)}
|
|
102
106
|
iframe#surface{width:100%;height:560px;border:0;display:block;background:var(--bg)}
|
|
103
|
-
.checklist{position:absolute;top:46px;right:14px;width:226px;background:rgba(0,0,0,.82);border:1px solid var(--border);border-radius:10px;padding:10px 12px;z-index:52;font-size:
|
|
107
|
+
.checklist{position:absolute;top:46px;right:14px;width:226px;background:rgba(0,0,0,.82);border:1px solid var(--border);border-radius:10px;padding:10px 12px;z-index:52;font-size:12px;backdrop-filter:blur(6px);opacity:0;transition:opacity .2s}
|
|
104
108
|
.checklist.on{opacity:1}
|
|
105
|
-
.checklist .cl-h{font-family:var(--mono);font-size:
|
|
109
|
+
.checklist .cl-h{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-40);margin-bottom:7px}
|
|
106
110
|
.checklist .cl-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;color:var(--fg-50);transition:color .2s}
|
|
107
111
|
.checklist .cl-row.run{color:var(--fg)}
|
|
108
112
|
.checklist .cl-row .cl-id{font-family:var(--mono);font-weight:700;width:22px}
|
|
109
113
|
.checklist .cl-row .cl-st{width:14px;text-align:center}
|
|
110
|
-
.checklist .cl-row.pass .cl-id{color:var(--
|
|
114
|
+
.checklist .cl-row.pass .cl-id{color:var(--verified)} .checklist .cl-row.fail .cl-id{color:var(--caught)} .checklist .cl-row.blocked .cl-id{color:var(--fg-40)}
|
|
111
115
|
.ring{position:fixed;border:2px solid var(--accent);border-radius:8px;box-shadow:0 0 0 4px rgba(255,154,118,.25),0 0 18px rgba(255,154,118,.6);pointer-events:none;z-index:50;animation:ringpulse 1s infinite;transition:all .25s ease}
|
|
112
116
|
@keyframes ringpulse{0%,100%{opacity:1}50%{opacity:.55}}
|
|
113
|
-
.caption{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.88);border:1px solid var(--border);border-radius:999px;padding:7px 16px;font-size:
|
|
117
|
+
.caption{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.88);border:1px solid var(--border);border-radius:999px;padding:7px 16px;font-size:12px;color:var(--fg);font-family:var(--mono);white-space:nowrap;z-index:51;opacity:0;transition:opacity .2s}
|
|
114
118
|
.caption.on{opacity:1}
|
|
115
119
|
.framestatus{padding:10px 14px;border-top:1px solid var(--border-soft);font-size:12px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
|
|
116
120
|
.framestatus .badge{font-family:var(--mono);font-weight:700;padding:2px 9px;border-radius:5px}
|
|
117
|
-
.badge.pass{color:var(--
|
|
118
|
-
.framestatus .issues{color:var(--
|
|
121
|
+
.badge.pass{color:var(--verified);background:rgba(127,195,154,.12)} .badge.fail{color:var(--caught);background:rgba(226,130,127,.14)} .badge.blocked{color:var(--fg-40);background:rgba(255,255,255,.05)}
|
|
122
|
+
.framestatus .issues{color:var(--caught);font-size:12px}
|
|
119
123
|
.framestatus .issues.blk{color:var(--fg-40)}
|
|
120
124
|
.replay-side h3{font-size:12px;color:var(--fg-70);margin:0 0 10px}
|
|
121
125
|
.ctrlrow{display:flex;gap:8px;align-items:center;margin-bottom:12px;flex-wrap:wrap}
|
|
122
126
|
.progress{height:5px;background:var(--bg-2);border-radius:3px;overflow:hidden;margin:8px 0 14px}
|
|
123
|
-
.progress>div{height:100%;width:0;background:
|
|
127
|
+
.progress>div{height:100%;width:0;background:var(--accent);transition:width var(--dur-max) var(--ease)}
|
|
124
128
|
.steplist{font-size:12px}
|
|
125
129
|
.steprow{display:flex;align-items:center;gap:8px;padding:5px 7px;border-radius:6px;cursor:pointer}
|
|
126
130
|
.steprow.active{background:var(--accent-wash)}
|
|
127
131
|
.steprow .st{width:16px;text-align:center}
|
|
128
|
-
.steprow .nm{font-family:var(--mono);font-size:
|
|
129
|
-
.steprow.fail .nm{color:var(--
|
|
130
|
-
.clipnote{margin-top:14px;font-size:
|
|
132
|
+
.steprow .nm{font-family:var(--mono);font-size:12px;color:var(--fg-70)}
|
|
133
|
+
.steprow.fail .nm{color:var(--caught)}
|
|
134
|
+
.clipnote{margin-top:14px;font-size:12px;color:var(--fg-40)}
|
|
131
135
|
.clipnote code{font-family:var(--mono);color:var(--info)}
|
|
132
136
|
details.manifest{margin-top:26px;border:1px solid var(--border-soft);border-radius:var(--radius);padding:10px 14px}
|
|
133
137
|
details.manifest summary{cursor:pointer;color:var(--fg-50);font-size:12px}
|
|
134
|
-
details.manifest pre{font-family:var(--mono);font-size:
|
|
138
|
+
details.manifest pre{font-family:var(--mono);font-size:12px;color:var(--fg-50);overflow:auto;margin:10px 0 0;max-height:340px}
|
|
135
139
|
.hidden{display:none !important}
|
|
140
|
+
/* S17 · prefers-reduced-motion: ALL motion collapses, information stays
|
|
141
|
+
(same rule the product page ships). */
|
|
142
|
+
@media (prefers-reduced-motion: reduce){
|
|
143
|
+
*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}
|
|
144
|
+
}
|
|
136
145
|
</style>
|
|
137
146
|
</head>
|
|
138
147
|
<body data-verify-page="dashboard">
|
|
139
148
|
<header class="top">
|
|
140
|
-
<span class="logo"><span class="
|
|
149
|
+
<span class="logo"><span class="name">codehere</span></span>
|
|
141
150
|
<span class="route">/verify</span>
|
|
142
151
|
<span class="pill local">● local · zero telemetry</span>
|
|
143
152
|
<span class="spacer"></span>
|
|
@@ -152,10 +161,10 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
152
161
|
</div>
|
|
153
162
|
<div class="verdict-line" id="verdict-line"></div>
|
|
154
163
|
<div class="legend">
|
|
155
|
-
<span><b style="color:var(--
|
|
156
|
-
<span><b style="color:var(--
|
|
164
|
+
<span><b style="color:var(--verified)">PASS</b> observed & honest</span>
|
|
165
|
+
<span><b style="color:var(--caught)">FAIL</b> observed & lying</span>
|
|
157
166
|
<span><b style="color:var(--fg-40)">BLOCKED</b> 0 units to observe — never a silent green</span>
|
|
158
|
-
<span><b
|
|
167
|
+
<span><b>probe</b> designed-to-fail self-test</span>
|
|
159
168
|
</div>
|
|
160
169
|
|
|
161
170
|
<h2 class="section">Pluggable verifiers</h2>
|
|
@@ -169,16 +178,16 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
169
178
|
<table class="matrix" id="matrix"><thead></thead><tbody></tbody></table>
|
|
170
179
|
<p class="note" id="probe-note"></p>
|
|
171
180
|
|
|
172
|
-
<h2 class="section">Feature coverage · v0
|
|
181
|
+
<h2 class="section">Feature coverage · v0 › v1 MVP — does each PRODUCT_STATE claim actually render?</h2>
|
|
173
182
|
<div class="actions">
|
|
174
183
|
<button class="run" id="run-cov">▶ Run coverage</button>
|
|
175
184
|
<span class="tally" id="cov-tally"><span class="t">not run yet</span></span>
|
|
176
185
|
</div>
|
|
177
186
|
<div class="verdict-line" id="cov-verdict"></div>
|
|
178
187
|
<div class="legend">
|
|
179
|
-
<span><b style="color:var(--
|
|
180
|
-
<span><b style="color:var(--
|
|
181
|
-
<span><b style="color:var(--warn)">PARTIAL-GAP</b>
|
|
188
|
+
<span><b style="color:var(--verified)">OBSERVED</b> claimed shipped & rendered</span>
|
|
189
|
+
<span><b style="color:var(--caught)">MISSING</b> claimed shipped, NOT rendered (doc lies)</span>
|
|
190
|
+
<span><b style="color:var(--warn)">PARTIAL-GAP</b> ◐ honest, not yet visible</span>
|
|
182
191
|
<span><b style="color:var(--fg-40)">SKIP</b> roadmap / future (correctly absent)</span>
|
|
183
192
|
</div>
|
|
184
193
|
<table class="matrix" id="cov-matrix"><thead><tr><th style="width:52%">Feature (PRODUCT_STATE id)</th><th class="needs" style="width:12%">tier</th><th class="needs" style="width:12%">surface</th><th>state</th></tr></thead><tbody></tbody></table>
|
|
@@ -200,13 +209,13 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
200
209
|
<h3>Replay controls</h3>
|
|
201
210
|
<div class="ctrlrow">
|
|
202
211
|
<button class="linkbtn" id="play">▶ Play</button>
|
|
203
|
-
<button class="linkbtn" id="skip"
|
|
204
|
-
<button class="linkbtn" id="record">&#
|
|
212
|
+
<button class="linkbtn" id="skip">Skip ›</button>
|
|
213
|
+
<button class="linkbtn" id="record">● Record clips</button>
|
|
205
214
|
<span class="hint" id="dwell-label">dwell 1500ms</span>
|
|
206
215
|
</div>
|
|
207
216
|
<div class="progress"><div id="prog"></div></div>
|
|
208
217
|
<div class="steplist" id="steplist"></div>
|
|
209
|
-
<div class="clipnote">Each fixture
|
|
218
|
+
<div class="clipnote">Each fixture gets its own <code>.webm</code> (in-browser, pick this tab). Headless: <code>node scripts/record-replay.mjs</code> hands you one webm, no setup.</div>
|
|
210
219
|
</div>
|
|
211
220
|
</div>
|
|
212
221
|
</div>
|
|
@@ -221,7 +230,11 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
221
230
|
(function(){
|
|
222
231
|
"use strict";
|
|
223
232
|
|
|
224
|
-
/* -------- fixtures: the seeded sessions (the product's real surfaces) --------
|
|
233
|
+
/* -------- fixtures: the seeded sessions (the product's real surfaces) --------
|
|
234
|
+
S20 · the two lens columns re-observe the FAILED fixture with the Review /
|
|
235
|
+
Status lens active (sid = the session, lens = applied via the page's own
|
|
236
|
+
applyLens before reading): every invariant — H9/H13/H14/H15 included —
|
|
237
|
+
must hold under each lens. Honesty must survive the recomposition. */
|
|
225
238
|
var FIXTURES = [
|
|
226
239
|
{ id:"demo-rate-limit-shipped-v0.9", label:"rate-limit-shipped" },
|
|
227
240
|
{ id:"demo-recovery-test-failed-v0.9", label:"recovery-failed" },
|
|
@@ -230,17 +243,25 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
230
243
|
{ id:"demo-stalled-opencode-hung-v1", label:"stalled-hung" },
|
|
231
244
|
{ id:"demo-non-git-honest-v1", label:"non-git-honest" },
|
|
232
245
|
{ id:"demo-running-streaming-v0.9", label:"running-stream" },
|
|
246
|
+
{ id:"lens-review-failed", sid:"demo-recovery-test-failed-v0.9", lens:"review", label:"review lens \\u00b7 failed" },
|
|
247
|
+
{ id:"lens-status-failed", sid:"demo-recovery-test-failed-v0.9", lens:"status", label:"status lens \\u00b7 failed" },
|
|
233
248
|
{ id:"demo-probe-lie-v1", label:"probe-lie", probe:true, expectFail:"H5" }
|
|
234
249
|
];
|
|
235
250
|
|
|
236
251
|
/* -------- invariants: the honesty contracts (the "units") --------
|
|
237
252
|
needs(s) -> can this invariant observe anything here? (else BLOCKED)
|
|
238
253
|
check(s) -> [] when honest, or violation strings when lying (FAIL) */
|
|
254
|
+
/* LOW #23 (atomic audit) · there is no H4 — it was never built; numbering
|
|
255
|
+
jumps H3->H5 and is kept STABLE because H-ids are referenced by docs,
|
|
256
|
+
fixtures (expectFail) and evidence bundles. Do not renumber. */
|
|
239
257
|
var INVARIANTS = [
|
|
240
258
|
{ id:"H1", title:"no-verified-without-git", needs:"verifier",
|
|
241
259
|
desc:"verdict is not 'verified' when git is untracked",
|
|
242
260
|
can:function(s){ return s.verifiers.length>0; },
|
|
243
|
-
|
|
261
|
+
/* R11/M2 · the rule itself is the VerdictService's, interpolated at
|
|
262
|
+
build time — this harness EXECUTES verify-claim's own predicate
|
|
263
|
+
instead of re-encoding it, so engine and harness can't drift. */
|
|
264
|
+
check:function(s){ var violates=(${violatesGitInvariant.toString()}); return s.verifiers.filter(function(v){ return violates(v.verdict, v.gittracked!=="false"); }).map(function(){ return "verifier verdict=verified while git untracked (overclaim)"; }); } },
|
|
244
265
|
{ id:"H2", title:"say-never-bare", needs:"say",
|
|
245
266
|
desc:"every reasoning bubble has real words, not the bare label",
|
|
246
267
|
can:function(s){ return s.says.length>0; },
|
|
@@ -256,7 +277,97 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
256
277
|
{ id:"H6", title:"fail-has-honest-reason", needs:"terminal-fail",
|
|
257
278
|
desc:"a failed / killed / crashed run shows a plain reason, never a bare dot",
|
|
258
279
|
can:function(s){ return s.terminalFail; },
|
|
259
|
-
check:function(s){ return s.hasExplanation ? [] : ["failed session shows no recovery / crash / stall explanation"]; } }
|
|
280
|
+
check:function(s){ return s.hasExplanation ? [] : ["failed session shows no recovery / crash / stall explanation"]; } },
|
|
281
|
+
{ id:"H8", title:"observer-honest", needs:"observer rail",
|
|
282
|
+
desc:"the observer never shows a stale session as live, never renders a blank box, never seeds fake rows (Thariq loop-2 design B)",
|
|
283
|
+
can:function(s){ return !!s.observer; },
|
|
284
|
+
check:function(s){
|
|
285
|
+
var o=s.observer, out=[];
|
|
286
|
+
if(o.mode==='empty'){
|
|
287
|
+
if(o.rowStates.length>0) out.push("empty observer rendered "+o.rowStates.length+" row(s) · should seed NO fake rows");
|
|
288
|
+
if(o.teachLen<20) out.push("empty observer is a blank box · it must teach the 60-second path");
|
|
289
|
+
} else if(o.mode==='active'){
|
|
290
|
+
var liveRows=o.rowStates.filter(function(st){ return st==='live'; }).length;
|
|
291
|
+
if(o.liveClaimed!==liveRows) out.push("header claims "+o.liveClaimed+" live but "+liveRows+" row(s) are marked live (stale-as-live)");
|
|
292
|
+
if(o.textLen<10) out.push("active observer rendered with no content (empty container)");
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
} },
|
|
296
|
+
{ id:"H12", title:"follow-up-never-lies", needs:"detail composer",
|
|
297
|
+
desc:"the follow-up box is enabled only when the server can deliver the message; a disabled box carries a plain reason \u00b7 never an invitation to send into a void (S1)",
|
|
298
|
+
can:function(s){ return !!s.followUp && !!s.followUp.state; },
|
|
299
|
+
check:function(s){
|
|
300
|
+
var f=s.followUp, out=[];
|
|
301
|
+
if(f.state==='disabled-honest' && f.placeholderLen<20) out.push("follow-up disabled but the reason placeholder is missing/too thin");
|
|
302
|
+
if(f.state==='disabled-honest' && !f.disabled) out.push("composer claims disabled-honest but the box is still enabled (the invitation lie)");
|
|
303
|
+
if(f.state==='enabled' && f.disabled) out.push("composer stamped enabled but the box is disabled (silent dead composer)");
|
|
304
|
+
return out;
|
|
305
|
+
} },
|
|
306
|
+
{ id:"H11", title:"running-task-stoppable", needs:"a running session",
|
|
307
|
+
desc:"a running task shows a Stop control on its detail · you can stop an agent the moment you stop trusting it (S2)",
|
|
308
|
+
can:function(s){ return s.status==='running'; },
|
|
309
|
+
check:function(s){ return s.hasStopControl ? [] : ["running task renders no Stop control on the detail (unstoppable runaway)"]; } },
|
|
310
|
+
{ id:"H10", title:"cost-estimate-honest", needs:"composer cost chip",
|
|
311
|
+
desc:"the cost chip is computed (free / recent-real / typical), labeled as what it is, and its tooltip never claims a computation that didn't happen (S7)",
|
|
312
|
+
can:function(s){ return s.costEst && s.costEst.length>0; },
|
|
313
|
+
check:function(s){
|
|
314
|
+
var out=[];
|
|
315
|
+
s.costEst.forEach(function(c){
|
|
316
|
+
if(!c.mode) out.push("cost chip rendered with no computed mode (estimator never ran) \\u00b7 text: '"+c.text+"'");
|
|
317
|
+
else if(c.mode==='free' && c.text.indexOf('$0')===-1) out.push("free-mode chip does not say $0");
|
|
318
|
+
else if(c.mode==='typical' && c.text.indexOf('typical')===-1) out.push("flat-fallback chip not labeled 'typical' \\u00b7 a flat figure dressed as a computation");
|
|
319
|
+
else if(c.mode==='typical' && /prompt length/i.test(c.title)) out.push("typical-mode tooltip claims prompt-length computation that did not happen");
|
|
320
|
+
else if(c.mode==='recent' && c.text.indexOf('~$')===-1) out.push("recent-mode chip missing the computed ~$ figure");
|
|
321
|
+
});
|
|
322
|
+
return out;
|
|
323
|
+
} },
|
|
324
|
+
{ id:"H9", title:"no-green-on-failed", needs:"verifier on a failed run",
|
|
325
|
+
desc:"a failed / killed / crashed task never wears the green 'verified' block · an honest report about a failure is not a success (S5)",
|
|
326
|
+
can:function(s){ return s.terminalFail && s.verifiers.length>0; },
|
|
327
|
+
check:function(s){ return s.verifiers.filter(function(v){ return v.verdict==="verified"; }).map(function(){ return "status=failed task renders a green verdict=verified block (fake-green on the proof surface)"; }); } },
|
|
328
|
+
{ id:"H13", title:"hero-numbers-honest", needs:"home hero",
|
|
329
|
+
desc:"the home hero's claimed counts (agents/verified/caught/running) equal the counts derivable from the task list the page itself holds; the first-run hero never hides real work (S12)",
|
|
330
|
+
can:function(s){ return !!(s.hero && s.hero.expected); },
|
|
331
|
+
check:function(s){
|
|
332
|
+
var h=s.hero, e=h.expected, out=[];
|
|
333
|
+
if(h.mode==="first-run"){
|
|
334
|
+
if(e.today>0) out.push("hero shows the first-run story while "+e.today+" task(s) started today (real work hidden)");
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
["agents","verified","caught","stopped","running"].forEach(function(k){
|
|
338
|
+
if(h[k]!==e[k]) out.push("hero claims "+k+"="+h[k]+" but the task list derives "+e[k]);
|
|
339
|
+
});
|
|
340
|
+
return out;
|
|
341
|
+
} },
|
|
342
|
+
{ id:"H14", title:"decisions-honest", needs:"decision-queue state",
|
|
343
|
+
desc:"the NEEDS-YOUR-DECISION queue's claimed N equals the count derivable from the page's own state (pending write-passes + verifier-broken verdicts today + failed runs with retry available today); zero derivable decisions means the section is absent entirely (S13)",
|
|
344
|
+
can:function(s){ return !!(s.decisions && s.decisions.expected!==null); },
|
|
345
|
+
check:function(s){
|
|
346
|
+
var d=s.decisions, out=[];
|
|
347
|
+
if(d.expected===0){
|
|
348
|
+
if(d.present) out.push("decision queue renders ("+d.rows+" row(s), claims N="+d.n+") while 0 decisions are derivable \\u00b7 the section must be absent when nothing needs you");
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
if(!d.present){ out.push(d.expected+" decision(s) derivable from page state but the queue is absent (real decisions hidden)"); return out; }
|
|
352
|
+
if(d.n!==d.expected) out.push("queue claims N="+d.n+" but the page state derives "+d.expected);
|
|
353
|
+
if(d.rows!==d.n) out.push("queue claims N="+d.n+" but renders "+d.rows+" row(s)");
|
|
354
|
+
return out;
|
|
355
|
+
} },
|
|
356
|
+
{ id:"H15", title:"glyph-discipline", needs:"rendered surface",
|
|
357
|
+
desc:"no emoji/pictograph codepoint anywhere in the rendered CHROME, observed in the default AND expanded history/practice states \\u00b7 status and meaning are carried by the sanctioned geometric glyph set (\\u2713 \\u2715 \\u25CF \\u25CB \\u2022 \\u2298 \\u203A), words and color, never emoji-as-icon; user/run content ([data-user-content]) is the user's own text, not chrome, and is excluded (S19)",
|
|
358
|
+
can:function(s){ return !!(s.glyphScan && s.glyphScan.scanned); },
|
|
359
|
+
check:function(s){ return s.glyphScan.hits; } },
|
|
360
|
+
{ id:"H7", title:"recovery-buttons-must-not-lie", needs:"recovery card",
|
|
361
|
+
desc:"every ENABLED recovery action does a real thing; a disabled one carries its reason · no button narrates an action it doesn't perform (Thariq loop-1)",
|
|
362
|
+
can:function(s){ return s.recoveryButtons && s.recoveryButtons.length>0; },
|
|
363
|
+
check:function(s){
|
|
364
|
+
var out = [];
|
|
365
|
+
s.recoveryButtons.forEach(function(b){
|
|
366
|
+
if(b.bareToast) out.push("recovery '"+b.action+"' is enabled but only fires a toast · it lies about acting");
|
|
367
|
+
if(b.disabled && !b.hasReason) out.push("recovery '"+b.action+"' is disabled with no reason (silent dead button)");
|
|
368
|
+
});
|
|
369
|
+
return out;
|
|
370
|
+
} }
|
|
260
371
|
];
|
|
261
372
|
|
|
262
373
|
var PROTO_VERSION = "codehere-verify/1";
|
|
@@ -264,75 +375,344 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
264
375
|
/* -------- feature coverage: every granular PRODUCT_STATE claim, v0->v1 --------
|
|
265
376
|
Turns the ~150-row inventory's claims into watchable checks against the REAL
|
|
266
377
|
surface. status drives the verdict:
|
|
267
|
-
shipped (
|
|
268
|
-
partial (
|
|
269
|
-
roadmap (
|
|
270
|
-
future (
|
|
378
|
+
shipped ([shipped]/[green]) -> element MUST render -> OBSERVED, else MISSING (the claim is a lie)
|
|
379
|
+
partial ([partial]) -> render if present -> OBSERVED, else PARTIAL-GAP (honest, not a lie)
|
|
380
|
+
roadmap ([roadmap]) -> SKIP-ROADMAP (planned, not built — correctly absent)
|
|
381
|
+
future ([future]/cut) -> SKIP-FUTURE (deferred by design / cut)
|
|
271
382
|
surface: 'list' = dashboard · 'detail' = opens demo-rate-limit-shipped-v0.9.
|
|
272
383
|
sel = real CSS selector confirmed against the live DOM (not invented). */
|
|
273
384
|
var COV_FIXTURE = "demo-rate-limit-shipped-v0.9";
|
|
385
|
+
/* Inject a synthetic 3-task batch into the page's task STATE (the banner-
|
|
386
|
+
drive honesty bar: state occurs -> the real render path must produce the
|
|
387
|
+
surface; never DOM injection). Restored by __unseedCovBatch. Without this,
|
|
388
|
+
a home that never ran the batch seed reported the two v1.0 rows MISSING
|
|
389
|
+
even though the render path is real (found in the It-4 joint ceremony). */
|
|
390
|
+
function __seedCovBatch(w){
|
|
391
|
+
try{
|
|
392
|
+
var tk = w._cached && w._cached.tasks; if(!tk) return false;
|
|
393
|
+
if (w.__covSavedPersisted !== undefined) return true; // already seeded this run
|
|
394
|
+
w.__covSavedPersisted = tk.persisted;
|
|
395
|
+
var now = Date.now()-300000;
|
|
396
|
+
var mk = function(id, ag, st){ return { sessionId:id, agent:ag, prompt:"coverage probe "+id, cwd:"/cov-batch-probe", startedAt:now, status:st, batchId:"cov-batch-1", steps:[], artifacts:{}, provenance:"persisted", durationMs:1000, completedAt: st==="running"?0:now+1000 }; };
|
|
397
|
+
tk.persisted = (tk.persisted||[]).concat([ mk("cov-b1","claude-code","completed"), mk("cov-b2","codex","running"), mk("cov-b3","gemini","failed") ]);
|
|
398
|
+
return true;
|
|
399
|
+
}catch(e){ return false; }
|
|
400
|
+
}
|
|
401
|
+
function __unseedCovBatch(w){
|
|
402
|
+
try{
|
|
403
|
+
if (w.__covSavedPersisted === undefined) return;
|
|
404
|
+
if (w._cached && w._cached.tasks) w._cached.tasks.persisted = w.__covSavedPersisted;
|
|
405
|
+
delete w.__covSavedPersisted;
|
|
406
|
+
}catch(e){}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* It-4 V1-MULTITASK/V1-FANOUT-ALL drive helper. Finds a cwd in the
|
|
410
|
+
/api/tasks data (real or coverage-injected) whose turns include a batchId
|
|
411
|
+
shared by >=2 tasks, then drives the real openRoom() so renderRoom()
|
|
412
|
+
produces the live .project-rollup + .fanout-all surface. Returns the cwd
|
|
413
|
+
it opened, or null if no batch is present. */
|
|
414
|
+
function __openBatchRoom(w){
|
|
415
|
+
try{
|
|
416
|
+
var all = (w.allS ? w.allS(w._cached && w._cached.tasks || {}) : []);
|
|
417
|
+
var byBatch = {};
|
|
418
|
+
for(var i=0;i<all.length;i++){
|
|
419
|
+
/* S18 · practice (mockRun) batches collapse behind the practice row in
|
|
420
|
+
rooms — they no longer render a rollup, so the drive must open a
|
|
421
|
+
REAL batch room (the seeded cov-batch qualifies). Same honest rule
|
|
422
|
+
as renderRoom, applied to both sides. */
|
|
423
|
+
var t=all[i]; if(!t.batchId || t.mockRun) continue;
|
|
424
|
+
(byBatch[t.batchId]=byBatch[t.batchId]||[]).push(t);
|
|
425
|
+
}
|
|
426
|
+
for(var bid in byBatch){
|
|
427
|
+
if(byBatch[bid].length>=2){
|
|
428
|
+
var cwd=byBatch[bid][0].cwd; if(!cwd) continue;
|
|
429
|
+
if(w.openRoom){ w.openRoom(encodeURIComponent(cwd)); return cwd; }
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}catch(e){}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
274
435
|
var FEATURES = [
|
|
275
436
|
{ id:"F-AUDIT-FOOTER", name:"Audit chain footer", surface:"list", sel:".audit-chain-panel", status:"shipped", tier:"v0.7" },
|
|
276
|
-
{ id:"F-BUDGET-CHIP", name:"Budget pill", surface:"list", sel:".budget", status:"shipped", tier:"v0.7"
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
437
|
+
{ id:"F-BUDGET-CHIP", name:"Budget pill", surface:"list", sel:".budget", status:"shipped", tier:"v0.7",
|
|
438
|
+
/* .budget fills its text only when a daily budget is set (upBud). Real
|
|
439
|
+
~/.codehere often has no cap set, so it flaps empty. Drive the REAL
|
|
440
|
+
upBud() over a set-budget pricing state (60% spent · safe), restore. */
|
|
441
|
+
drive:function(w,d){ try{ w.__covBud=w._cached.pricing; w._cached.pricing={dailyBudgetCents:500,spentTodayCents:300}; w.upBud(); }catch(e){} },
|
|
442
|
+
undrive:function(w,d){ try{ w._cached.pricing=w.__covBud; w.upBud(); }catch(e){} } },
|
|
443
|
+
{ id:"F-BUDGET-WARN-BANNER", name:"Budget warning banner", surface:"list", sel:".budget-banner", status:"partial", tier:"v0.6",
|
|
444
|
+
/* drive the REAL upBud() over an over-budget pricing state, observe the
|
|
445
|
+
banner, restore. Same legitimacy as F-CUSTOM-VIEW-BANNER's class drive. */
|
|
446
|
+
drive:function(w,d){ try{ w.__covPricing=w._cached.pricing; w._cached.pricing={dailyBudgetCents:500,spentTodayCents:500}; w.upBud(); }catch(e){} },
|
|
447
|
+
undrive:function(w,d){ try{ w._cached.pricing=w.__covPricing; w.upBud(); }catch(e){} } },
|
|
448
|
+
/* S13 · at rest the home shows TODAY one-line rows only; demos + earlier
|
|
449
|
+
work collapse behind the history row. Card rows are observed by
|
|
450
|
+
expanding history through the REAL toggle (window.__histExpand), the
|
|
451
|
+
same path the user's click takes — never DOM injection. */
|
|
452
|
+
{ id:"F-CARD-AGENT-BORDER", name:"Per-vendor class on task row (S12: identity is the monochrome name, no hue)", surface:"list", sel:".task-card.agent-claude-code", status:"shipped", tier:"v0.9",
|
|
453
|
+
drive:function(w,d){ try{ w.__histExpand(); }catch(e){} },
|
|
454
|
+
undrive:function(w,d){ try{ w.__histCollapse(); }catch(e){} } },
|
|
455
|
+
{ id:"F-CARD-CLICK-DETAIL", name:"Click card/row -> detail view", surface:"list", sel:".task-card", status:"shipped", tier:"v0.6",
|
|
456
|
+
drive:function(w,d){ try{ w.__histExpand(); }catch(e){} },
|
|
457
|
+
undrive:function(w,d){ try{ w.__histCollapse(); }catch(e){} } },
|
|
458
|
+
{ id:"F-CARD-OUTCOME-CHIP", name:"Outcome on the card (the .tc-stat pill: verdict word + checks, right-aligned with cost)", surface:"list", sel:".tcard .tc-stat", status:"shipped", tier:"v0.7",
|
|
459
|
+
drive:function(w,d){ try{ w.__histExpand(); }catch(e){} },
|
|
460
|
+
undrive:function(w,d){ try{ w.__histCollapse(); }catch(e){} } },
|
|
461
|
+
{ id:"F-CUSTOM-VIEW-BANNER", name:"Custom view banner", surface:"list", sel:".custom-view-banner", status:"shipped", tier:"v0.8",
|
|
462
|
+
drive:function(w,d){ d.body.classList.add("custom-view"); },
|
|
463
|
+
undrive:function(w,d){ d.body.classList.remove("custom-view"); } },
|
|
282
464
|
{ id:"F-DAEMON-HEARTBEAT", name:"Local pill auto-update", surface:"list", sel:".local-pill", status:"shipped", tier:"v0.6" },
|
|
283
|
-
{ id:"F-FAILOVER-BANNER", name:"Failover banner", surface:"list", sel:".failover-banner", status:"partial", tier:"v0.7"
|
|
284
|
-
|
|
285
|
-
|
|
465
|
+
{ id:"F-FAILOVER-BANNER", name:"Failover banner", surface:"list", sel:".failover-banner.show", status:"partial", tier:"v0.7",
|
|
466
|
+
/* drive the banner's OWN render (window.__fbRender) with a real offer
|
|
467
|
+
shape — same object /api/failover-offers returns — so we observe the
|
|
468
|
+
live render path, not a fake DOM injection. */
|
|
469
|
+
drive:function(w,d){ try{ w.__fbRender([{ sessionId:"cov-failover", target:"codex", signal:{ fromAgent:"claude-code", errorCode:"rate_limited" } }]); }catch(e){} },
|
|
470
|
+
undrive:function(w,d){ try{ w.__fbRender([]); }catch(e){} } },
|
|
471
|
+
/* S13 · filters moved into the ⌘K palette (rule 9). Observed by opening
|
|
472
|
+
the REAL palette (window.openPalette — the same function ⌘K runs) and
|
|
473
|
+
asserting the filter commands render; the command's Enter runs the
|
|
474
|
+
same setFilter() the tabs ran. */
|
|
475
|
+
{ id:"F-FILTER-PILLS", name:"All/Running/Done/Failed filters (S13: \u2318K command EXECUTED \u203A the removable token is the observed effect)", surface:"list", sel:".ftoken", status:"shipped", tier:"v0.7",
|
|
476
|
+
/* Refuter (S13) · presence-only re-pointing was vacuous: a lobotomized
|
|
477
|
+
command element would still pass. This drive runs the REAL command
|
|
478
|
+
(palette open \u2192 click filter-failed) and the row observes its
|
|
479
|
+
CONSEQUENCE (.ftoken) \u2014 a broken run closure now reads MISSING. */
|
|
480
|
+
drive:function(w,d){ try{ w.openPalette(); var c=d.querySelector("[data-verify-cmd='filter-failed']"); if(c) c.click(); }catch(e){} },
|
|
481
|
+
undrive:function(w,d){ try{ var x=d.querySelector(".ftoken .x"); if(x) x.click(); w.closePalette(); }catch(e){} } },
|
|
482
|
+
{ id:"F-INDEXING-BANNER", name:"First-run indexing banner", surface:"list", sel:".indexing-banner", status:"partial", tier:"v0.7",
|
|
483
|
+
/* partial by nature: this banner only shows on a genuinely fresh home
|
|
484
|
+
(<3 sessions). The drive reproduces that fresh-home state — empties
|
|
485
|
+
_cached.tasks + clears the sessionStorage dismissal — runs the REAL
|
|
486
|
+
upIdx(), observes, then restores. */
|
|
487
|
+
drive:function(w,d){ try{ w.__covTasks=w._cached.tasks; w._cached.tasks={spawned:[],persisted:[],sessions:[]}; try{w.sessionStorage.removeItem("codehere_indexing_dismissed");}catch(e){} w.upIdx(); }catch(e){} },
|
|
488
|
+
undrive:function(w,d){ try{ w._cached.tasks=w.__covTasks; w.upIdx(); }catch(e){} } },
|
|
286
489
|
{ id:"F-LOCAL-PILL", name:"Local-first / zero-telemetry pill", surface:"list", sel:".local-pill", status:"shipped", tier:"v0.7" },
|
|
287
490
|
{ id:"F-LOGO", name:"Brand wordmark", surface:"list", sel:".logo .name", status:"shipped", tier:"v0.7" },
|
|
288
|
-
{ id:"F-NEW-TASK-BTN", name:"
|
|
289
|
-
|
|
491
|
+
{ id:"F-NEW-TASK-BTN", name:"New task (S13: first ⌘K command, focuses the composer)", surface:"list", sel:"[data-verify-cmd='new-task']", status:"shipped", tier:"v0.7",
|
|
492
|
+
drive:function(w,d){ try{ w.openPalette(); }catch(e){} },
|
|
493
|
+
undrive:function(w,d){ try{ w.closePalette(); }catch(e){} } },
|
|
494
|
+
{ id:"F-OBSERVED-THREAD", name:"Observed session as thread", surface:"list", sel:".obs-row", status:"shipped", tier:"v0.7",
|
|
495
|
+
/* S9 made practice mode honest: the mock chain starts EMPTY, so the
|
|
496
|
+
observer correctly renders its teach-state and this row has no .obs-row
|
|
497
|
+
to observe — it used to pass only because past mock runs had POLLUTED
|
|
498
|
+
the real chain with host sessions. Banner-drive honesty bar (same as
|
|
499
|
+
__seedCovBatch): seed the STATE through the REAL render path (the
|
|
500
|
+
exact function /api/host-sessions feeds), clearly labeled as a
|
|
501
|
+
coverage probe, restored from the real API after. Never DOM. */
|
|
502
|
+
/* S13 · the observer rests as one calm line; .obs-row renders in the
|
|
503
|
+
EXPANDED rail — drive the real expand flag with the probe session. */
|
|
504
|
+
drive:function(w,d){ w._obsOpen=true; w.renderHostSessions({ sessions:[{ sessionId:"coverage-probe-host-session", shortId:"cov-probe", lastSeen:new Date().toISOString(), toolMix:{ Bash:3, Read:2 }, blockedCalls:0, criticalCalls:0, totalCalls:5, durationMs:60000, risk:"low" }] }); },
|
|
505
|
+
undrive:function(w,d){ try{ w._obsOpen=false; fetch("/api/host-sessions").then(function(r){ return r.json(); }).then(function(h){ w.renderHostSessions(h); }); }catch(e){} } },
|
|
290
506
|
{ id:"F-OSS-FOOTER", name:"OSS footer", surface:"list", sel:".oss-footer", status:"shipped", tier:"v0.7" },
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
{ id:"
|
|
302
|
-
|
|
303
|
-
|
|
507
|
+
/* S20 · the five personas died; three lenses replace them. Each row is
|
|
508
|
+
EFFECT-observing (the F-FILTER-PILLS bar): the drive EXECUTES the real
|
|
509
|
+
⌘K lens command (palette open -> click -> applyLens) and the sel pins
|
|
510
|
+
the RECOMPOSITION that command must produce — a lobotomized command, a
|
|
511
|
+
missing body[data-lens], or a lens that failed to recompose all read
|
|
512
|
+
MISSING. State captured + restored in undrive. */
|
|
513
|
+
// F-LENS-DEFAULT/REVIEW/STATUS removed 2026-06-20 — the lens mode-switch was
|
|
514
|
+
// cut for ONE consistent conversation-first view (no more UI reshaping
|
|
515
|
+
// between visits). The detail's honest verdict + evidence disclosure carry
|
|
516
|
+
// what Review/Status used to surface.
|
|
517
|
+
{ id:"F-PIN-DEMO", name:"Demo-shape sessions pinned to FEATURED (S13: inside expanded history)", surface:"list", sel:".task-card.tc-pinned", status:"shipped", tier:"v0.9",
|
|
518
|
+
drive:function(w,d){ try{ w.__histExpand(); }catch(e){} },
|
|
519
|
+
undrive:function(w,d){ try{ w.__histCollapse(); }catch(e){} } },
|
|
520
|
+
{ id:"F-PORT-FAILOVER-NOTICE", name:"Port-collision UI surface (banner render path shared with failover; the PORT-collision trigger itself is not simulated)", surface:"list", sel:".failover-banner.show", status:"partial", tier:"v0.7", // shares the failover-banner surface; driven via window.__fbRender (see F-FAILOVER-BANNER). Honest = partial: the port-collision PATH itself isn't simulated, only the shared banner render.
|
|
521
|
+
drive:function(w,d){ try{ w.__fbRender([{ sessionId:"cov-port", target:"codex", signal:{ fromAgent:"claude-code", errorCode:"port_in_use" } }]); }catch(e){} },
|
|
522
|
+
undrive:function(w,d){ try{ w.__fbRender([]); }catch(e){} } },
|
|
523
|
+
// F-RESHAPE-BTN removed 2026-06-20 — Reshape mode cut for one consistent view.
|
|
524
|
+
{ id:"F-SETTINGS-COG", name:"Settings drawer trigger (S13: ⌘K Settings command)", surface:"list", sel:"[data-verify-cmd='settings']", status:"shipped", tier:"v0.8",
|
|
525
|
+
drive:function(w,d){ try{ w.openPalette(); }catch(e){} },
|
|
526
|
+
undrive:function(w,d){ try{ w.closePalette(); }catch(e){} } },
|
|
527
|
+
{ id:"F-SHARE-BTN", name:"Share thread (S13: ⌘K Share command -> same signed-link modal)", surface:"list", sel:"[data-verify-cmd='share']", status:"shipped", tier:"v0.8",
|
|
528
|
+
drive:function(w,d){ try{ w.openPalette(); }catch(e){} },
|
|
529
|
+
undrive:function(w,d){ try{ w.closePalette(); }catch(e){} } },
|
|
530
|
+
{ id:"F-SQUAD-ROW", name:"Agent readiness (S13: strip died · ⌘K Agents command carries the real readiness)", surface:"list", sel:"[data-verify-cmd='agents']", status:"shipped", tier:"v0.7",
|
|
531
|
+
drive:function(w,d){ try{ w.openPalette(); }catch(e){} },
|
|
532
|
+
undrive:function(w,d){ try{ w.closePalette(); }catch(e){} } },
|
|
533
|
+
{ id:"F-TASK-CARD", name:"Task row (one-line, keeps .task-card contract)", surface:"list", sel:".task-card", status:"shipped", tier:"v0.7",
|
|
534
|
+
drive:function(w,d){ try{ w.__histExpand(); }catch(e){} },
|
|
535
|
+
undrive:function(w,d){ try{ w.__histCollapse(); }catch(e){} } },
|
|
536
|
+
{ id:"F-TIME-GAP-PILL", name:"Brand pill + relative-time sub", surface:"list", sel:".time-gap", status:"shipped", tier:"v0.7",
|
|
537
|
+
/* Data-dependent (same hazard verify-page already calls out for goal groups):
|
|
538
|
+
.time-gap renders only BETWEEN tasks >2h apart (renderL). Real ~/.codehere
|
|
539
|
+
can legitimately have no such gap — a busy session clusters every task into
|
|
540
|
+
one <2h window (this exact flap, 2026-06-08) — so the live-DOM check flaps
|
|
541
|
+
MISSING on honest data. Observe it DETERMINISTICALLY instead, the same way
|
|
542
|
+
F-WORKTREE-ALERT does: inject two same-goal tasks 6h apart, render, observe,
|
|
543
|
+
restore. The render path is the REAL renderL — not a faked element. */
|
|
544
|
+
/* S12 · both fixtures sit 3 days back so they land in ONE chapter
|
|
545
|
+
(EARLIER) deterministically — the home now splits NOW/TODAY/EARLIER
|
|
546
|
+
and a now/now-6h pair could straddle midnight and split chapters. */
|
|
547
|
+
/* S13 · the gap rows live inside the EARLIER goal groups, which sit
|
|
548
|
+
behind the history row now — expand through the REAL toggle. */
|
|
549
|
+
/* S21 · same-title runs now collapse into ONE thread (groupByTitle), so a
|
|
550
|
+
time-gap between two runs of the SAME task no longer renders (they're one
|
|
551
|
+
thread) — correct. The .time-gap feature renders between DISTINCT tasks
|
|
552
|
+
>2h apart; the fixture uses two distinct-title tasks 6h apart to exercise
|
|
553
|
+
it (still same goal/cwd, so still one goal group). */
|
|
554
|
+
drive:function(w,d){ try{ w.__covTG=w._cached.tasks; var now=Date.now(); var mk=function(id,ts,p){return{sessionId:id,status:"completed",cwd:"/repo/cov-tg",startedAt:ts,prompt:p,agent:"claude-code",steps:[]};}; var base=now-3*24*3600*1000; w._cached.tasks={spawned:[],persisted:[mk("cov-tg-new",base,"coverage fixture new"),mk("cov-tg-old",base-6*3600*1000,"coverage fixture old")],sessions:[]}; w.__histExpand(); w.renderL(); }catch(e){} },
|
|
555
|
+
undrive:function(w,d){ try{ w._cached.tasks=w.__covTG; w.__histCollapse(); w.renderL(); }catch(e){} } },
|
|
556
|
+
{ id:"F-WORKTREE-ALERT", name:"Worktree alert", surface:"list", sel:".worktree-alert", status:"partial", tier:"v0.7",
|
|
557
|
+
/* upWk() fires when >=2 running sessions span different cwds and the odd
|
|
558
|
+
one has been running >5min. Drive that exact predicate over the REAL
|
|
559
|
+
upWk() with a synthetic 2-running-session state, observe, restore. */
|
|
560
|
+
drive:function(w,d){ try{ w.__covTasks=w._cached.tasks; var old=Date.now()-10*60*1000; w._cached.tasks={spawned:[],persisted:[{sessionId:"cov-wk-main",status:"running",cwd:"/repo/main",startedAt:old},{sessionId:"cov-wk-odd",status:"running",cwd:"/repo/feature-x",startedAt:old}],sessions:[]}; w.upWk(); }catch(e){} },
|
|
561
|
+
undrive:function(w,d){ try{ w._cached.tasks=w.__covTasks; w.upWk(); }catch(e){} } },
|
|
562
|
+
/* goal-first home + goal room (v1 spec · shared-room hand-off). Goal groups
|
|
563
|
+
render from REAL non-demo tasks (same data dependency as F-TIME-GAP-PILL);
|
|
564
|
+
room features are observed by driving the REAL openRoom() on the first
|
|
565
|
+
goal-head's data-goal-cwd, then closeRoom() — same pattern as showD(). */
|
|
566
|
+
/* S13 · goal groups own the EXPANDED history (today is flat one-line
|
|
567
|
+
rows); observed deterministically with the same injected-state drive
|
|
568
|
+
F-TIME-GAP-PILL uses — real renderL, real toggle, restored after. */
|
|
569
|
+
{ id:"F-GOAL-GROUP", name:"Goal grouping (S13: inside expanded history; heads still open rooms)", surface:"list", sel:".goal-group", status:"shipped", tier:"v1",
|
|
570
|
+
drive:function(w,d){ try{ w.__covGG=w._cached.tasks; var now=Date.now(); var mk=function(id,ts){return{sessionId:id,status:"completed",cwd:"/repo/cov-gg",startedAt:ts,prompt:"coverage fixture",agent:"claude-code",steps:[]};}; var base=now-3*24*3600*1000; w._cached.tasks={spawned:[],persisted:[mk("cov-gg-a",base),mk("cov-gg-b",base-3600*1000)],sessions:[]}; w.__histExpand(); w.renderL(); }catch(e){} },
|
|
571
|
+
undrive:function(w,d){ try{ w._cached.tasks=w.__covGG; w.__histCollapse(); w.renderL(); }catch(e){} } },
|
|
572
|
+
{ id:"F-GOAL-HEAD-OPEN", name:"Goal head opens its room (carries cwd)", surface:"list", sel:".goal-head[data-goal-cwd]", status:"shipped", tier:"v1",
|
|
573
|
+
drive:function(w,d){ try{ w.__covGH=w._cached.tasks; var now=Date.now(); var mk=function(id,ts){return{sessionId:id,status:"completed",cwd:"/repo/cov-gh",startedAt:ts,prompt:"coverage fixture",agent:"claude-code",steps:[]};}; var base=now-3*24*3600*1000; w._cached.tasks={spawned:[],persisted:[mk("cov-gh-a",base),mk("cov-gh-b",base-3600*1000)],sessions:[]}; w.__histExpand(); w.renderL(); }catch(e){} },
|
|
574
|
+
undrive:function(w,d){ try{ w._cached.tasks=w.__covGH; w.__histCollapse(); w.renderL(); }catch(e){} } },
|
|
575
|
+
{ id:"F-AGENT-PICK", name:"Composer agent picker (Auto + 5 agents)", surface:"list", sel:"button#agentpick-btn", status:"shipped", tier:"v1" },
|
|
576
|
+
/* S13 · the two new surfaces. The palette is driven through the REAL
|
|
577
|
+
opener (the same function ⌘K runs); the decision queue renders ONLY
|
|
578
|
+
when real decisions exist, so it's observed by injecting a failed-task
|
|
579
|
+
state through the real renderDecisions(), then restored. */
|
|
580
|
+
{ id:"F-CMDK-PALETTE", name:"⌘K command palette · grouped Act/View/Trust/Go, Enter runs the real function", surface:"list", sel:"[data-verify-unit='palette'] .prow[data-verify-cmd]", status:"shipped", tier:"v1",
|
|
581
|
+
drive:function(w,d){ try{ w.openPalette(); }catch(e){} },
|
|
582
|
+
undrive:function(w,d){ try{ w.closePalette(); }catch(e){} } },
|
|
583
|
+
{ id:"F-DECISION-QUEUE", name:"Needs-your-decision queue · absent when nothing needs you (H14 pins the count; this drive pins the PRESENT branch deterministically through the real renderDecisions)", surface:"list", sel:"#decision-queue .dq-row", status:"shipped", tier:"v1",
|
|
584
|
+
drive:function(w,d){ try{ w.__covDQ=w._cached.tasks; var now=Date.now()-600000; w._cached.tasks={spawned:[],persisted:[{sessionId:"cov-dq-fail",status:"failed",cwd:"/repo/cov-dq",startedAt:now,prompt:"coverage decision probe",agent:"claude-code",steps:[]}],sessions:[]}; w.renderDecisions(); }catch(e){} },
|
|
585
|
+
undrive:function(w,d){ try{ w._cached.tasks=w.__covDQ; w.renderDecisions(); }catch(e){} } },
|
|
586
|
+
/* S18 · the practice group's own regression net (the board must be able to
|
|
587
|
+
catch a regression in the exact story it ships). Two assertions in ONE
|
|
588
|
+
drive, both through the REAL render path:
|
|
589
|
+
(1) absent <=> 0 practice — with zero practice fixtures renderL must
|
|
590
|
+
render NO .prac-row; a phantom row aborts the drive, sel stays
|
|
591
|
+
unmatched, the row reads MISSING;
|
|
592
|
+
(2) with 2 mockRun fixtures, the REAL window.__pracExpand must render a
|
|
593
|
+
row whose claimed data-verify-practice-n=2 AND exactly 2 rows inside
|
|
594
|
+
.practice-rows (sel pins both via :nth-child(2):last-child) — a
|
|
595
|
+
"practice runs · 2" label over 0 rows, or practice flooding back
|
|
596
|
+
into the real list, reads MISSING. State restored in undrive. */
|
|
597
|
+
{ id:"F-PRACTICE-ROW", name:"Practice-runs group · quiet collapsed row, claimed N == rendered rows, absent when no practice exists (S18)", surface:"list", sel:"#task-list .prac-row[data-verify-practice-n='2'][aria-expanded='true'] + .practice-rows > .task-card:nth-child(2):last-child", status:"shipped", tier:"v1",
|
|
598
|
+
drive:function(w,d){ try{ w.__covPRAC=w._cached.tasks; w.__pracCollapse(); w._cached.tasks={spawned:[],persisted:[],sessions:[]}; w.renderL(); if(d.querySelector("#task-list .prac-row")) return; var base=Date.now()-3*24*3600*1000; var mkp=function(id,ts){return{sessionId:id,status:"completed",cwd:"/repo/cov-prac",startedAt:ts,prompt:"coverage practice fixture "+id,agent:"claude-code",steps:[],mockRun:true,provenance:"persisted",durationMs:1000,completedAt:ts+1000};}; w._cached.tasks={spawned:[],persisted:[mkp("cov-prac-a",base),mkp("cov-prac-b",base-3600*1000)],sessions:[]}; w.__pracExpand(); }catch(e){} },
|
|
599
|
+
undrive:function(w,d){ try{ w._cached.tasks=w.__covPRAC; w.__pracCollapse(); w.renderL(); }catch(e){} } },
|
|
600
|
+
/* S18 · H13's aggregation path, pinned. Under CODEHERE_MOCK every live run
|
|
601
|
+
is practice, so the board's hero compare is honestly 0==0 — vacuous.
|
|
602
|
+
This drive injects deterministic NON-practice fixtures (one running ·
|
|
603
|
+
one completed-verified · one caught, three distinct agents, started
|
|
604
|
+
today) through the REAL upHero(); sel pins every claimed
|
|
605
|
+
data-verify-hero-* attr to the fixture-derived counts. A double-counted
|
|
606
|
+
agent or mis-bucketed running session reads MISSING. */
|
|
607
|
+
{ id:"F-HERO-COUNTS", name:"Day-line hero · real aggregation path pinned (agents=3 · verified=1 · caught=1 · stopped=0 · running=1) (S18)", surface:"list", sel:"[data-verify-unit='home-hero'][data-verify-hero-mode='counts'][data-verify-hero-agents='3'][data-verify-hero-verified='1'][data-verify-hero-caught='1'][data-verify-hero-stopped='0'][data-verify-hero-running='1']", status:"shipped", tier:"v1",
|
|
608
|
+
drive:function(w,d){ try{ w.__covHERO=w._cached.tasks; var hnow=Date.now(); var mkh=function(id,ag,st,vd){ var t={sessionId:id,status:st,cwd:"/repo/cov-hero",startedAt:hnow,prompt:"coverage hero fixture "+id,agent:ag,steps:[],provenance:"persisted"}; if(vd){ t.verdict={claim:{status:vd}}; t.durationMs=1000; t.completedAt=hnow; } return t; }; w._cached.tasks={spawned:[],persisted:[mkh("cov-hero-run","claude-code","running",null),mkh("cov-hero-ok","codex","completed","verified"),mkh("cov-hero-bad","gemini","completed","broken")],sessions:[]}; w.upHero(); }catch(e){} },
|
|
609
|
+
undrive:function(w,d){ try{ w._cached.tasks=w.__covHERO; w.upHero(); }catch(e){} } },
|
|
610
|
+
/* room selectors are scoped to #task-list: the hidden detail view's DOM
|
|
611
|
+
persists (display:none still matches querySelectorAll) and also has an
|
|
612
|
+
.audit-line — unscoped, a dead room would false-OBSERVE off it. */
|
|
613
|
+
{ id:"F-ROOM-HEAD", name:"Goal room header (title + outcome sub)", surface:"room", sel:"#task-list .room-head .room-title", status:"shipped", tier:"v1" },
|
|
614
|
+
// F-ROOM-BACK removed 2026-07-07 — the '← Projects' back-link died for the
|
|
615
|
+
// rail's Today row (nav option B); the rail + Esc + the logo are the way out.
|
|
616
|
+
{ id:"F-ROOM-TURNS", name:"Turns in order (how agents worked the goal)", surface:"room", sel:"#task-list .turns-label", status:"partial", tier:"v1" },
|
|
617
|
+
{ id:"F-ROOM-AUDIT", name:"Room audit line (steps in local chain)", surface:"room", sel:"#task-list .audit-line", status:"partial", tier:"v1" },
|
|
618
|
+
/* partial by nature: renders ONLY when the daemon reports project memory
|
|
619
|
+
OFF for the opened room (initialized projects correctly show nothing). */
|
|
620
|
+
{ id:"F-ROOM-MEM-NUDGE", name:"Project-memory init nudge (turn on memory)", surface:"room", sel:"#task-list .mem-nudge", status:"partial", tier:"v1" },
|
|
621
|
+
/* on-demand surfaces: hidden until a state class the product itself
|
|
622
|
+
toggles. The harness DRIVES that exact state (same honesty bar as the
|
|
623
|
+
seeded fixtures: state occurs -> surface renders), then restores it.
|
|
624
|
+
Before AUD-05 these were "observed" while display:none — a blind spot. */
|
|
625
|
+
{ id:"M-OVERLAY-LINEAR", name:"Overlay-renderer Linear", surface:"list", sel:".or-overlay", status:"shipped", tier:"v0.9",
|
|
626
|
+
drive:function(w,d){ d.getElementById("or-overlay").classList.add("open"); },
|
|
627
|
+
undrive:function(w,d){ d.getElementById("or-overlay").classList.remove("open"); } },
|
|
628
|
+
{ id:"M-PRIMITIVES-NOTE", name:"Primitives note", surface:"list", sel:".primitives-note .prims .p", status:"shipped", tier:"v0.8",
|
|
629
|
+
drive:function(w,d){ w.openReshape(); },
|
|
630
|
+
undrive:function(w,d){ var m=d.getElementById("reshape-modal"); if(m) m.classList.remove("open"); } },
|
|
631
|
+
{ id:"M-RESHAPE-AGENT-COMPOSE", name:"Agent-compose animation", surface:"list", sel:".agent-compose .line", status:"shipped", tier:"v0.8",
|
|
632
|
+
drive:function(w,d){ w.openReshape(); d.getElementById("agent-compose").classList.add("show"); },
|
|
633
|
+
undrive:function(w,d){ d.getElementById("agent-compose").classList.remove("show"); var m=d.getElementById("reshape-modal"); if(m) m.classList.remove("open"); } },
|
|
304
634
|
{ id:"M-SETTINGS-DRAWER", name:"Settings drawer", surface:"list", sel:".drawer", status:"shipped", tier:"v0.8" },
|
|
305
635
|
{ id:"M-SHARE-TOAST", name:"Share toast notification", surface:"list", sel:".share-toast", status:"shipped", tier:"v0.8" },
|
|
306
|
-
{ id:"M-WPASS-APPROVAL", name:"W-PASS approval modal", surface:"list", sel:".wp-modal", status:"shipped", tier:"v0.9"
|
|
307
|
-
|
|
308
|
-
|
|
636
|
+
{ id:"M-WPASS-APPROVAL", name:"W-PASS approval modal", surface:"list", sel:".wp-modal", status:"shipped", tier:"v0.9",
|
|
637
|
+
drive:function(w,d){ d.getElementById("wp-modal").classList.add("open"); },
|
|
638
|
+
undrive:function(w,d){ d.getElementById("wp-modal").classList.remove("open"); } },
|
|
639
|
+
{ id:"P-AB-BADGE", name:"Agent identity badge (S13: on history cards + detail; rows carry the mono name)", surface:"list", sel:".ab", status:"shipped", tier:"v0.6",
|
|
640
|
+
drive:function(w,d){ try{ w.__histExpand(); }catch(e){} },
|
|
641
|
+
undrive:function(w,d){ try{ w.__histCollapse(); }catch(e){} } },
|
|
642
|
+
{ id:"P-STATUS-PILL", name:"Per-status pill on the card (.tc-stat: running pulse / verified / reported / caught)", surface:"list", sel:".tcard .tc-stat", status:"shipped", tier:"v0.7",
|
|
643
|
+
drive:function(w,d){ try{ w.__histExpand(); }catch(e){} },
|
|
644
|
+
undrive:function(w,d){ try{ w.__histCollapse(); }catch(e){} } },
|
|
309
645
|
{ id:"F-DETAIL-HEADER", name:"Detail view header (back button + title + agent badge + status line)", surface:"detail", sel:".back-btn", status:"shipped", tier:"v0.7" },
|
|
310
646
|
{ id:"F-DETAIL-KICKER", name:"Detail kicker + failover context", surface:"detail", sel:".detail-kicker", status:"shipped", tier:"v0.7" },
|
|
311
647
|
{ id:"F-OUTCOME-PANEL", name:"Outcome section (files/steps/Final Report)", surface:"detail", sel:"[data-verify-unit='outcome']", status:"shipped", tier:"v0.7" },
|
|
312
648
|
{ id:"F-SHIMMER-TITLE", name:"Animated gradient title", surface:"detail", sel:".detail-title", status:"shipped", tier:"v0.7" },
|
|
313
649
|
{ id:"P-CODE-CARD", name:"Inline syntax-tinted code preview", surface:"detail", sel:".code-card", status:"shipped", tier:"v0.7" },
|
|
314
650
|
{ id:"P-CTX-URL-CHIP", name:"Clickable context-source URL preview chip", surface:"detail", sel:".ctx-url-chip", status:"shipped", tier:"v0.8" },
|
|
315
|
-
|
|
316
|
-
|
|
651
|
+
/* S14 · the celebration card died with the calm redesign — the verified
|
|
652
|
+
state is now the dot+word verdict line (the only green). Honest
|
|
653
|
+
re-point: observe the SAME state (verdict=verified) on its new dress. */
|
|
654
|
+
{ id:"P-VERIFIED-BLOCK", name:"Verified verdict line (S14: dot + word, the only green)", surface:"detail", sel:".vline[data-verify-verdict='verified']", status:"shipped", tier:"v0.9",
|
|
655
|
+
/* the verified verdict line renders in the detail for a completed,
|
|
656
|
+
git-tracked, non-mock task with a verified claim (verdictState==='verified').
|
|
657
|
+
Mock seeds are practice/reported. Mutate the ALREADY-OPEN detail task
|
|
658
|
+
in place (no navigate/no _cached swap — that corrupts the shared detail
|
|
659
|
+
other detail probes observe), re-render, observe, restore. */
|
|
660
|
+
drive:function(w,d){ try{ var t=w.findS?w.findS(COV_FIXTURE):null; if(t){ w.__covVB={v:t.verdict,m:t.mockRun,s:t.status,g:(t.artifacts?t.artifacts.noGitTracking:undefined),r:t.finalReport}; t.verdict={claim:{status:"verified"}}; t.mockRun=false; t.status="completed"; if(t.artifacts)t.artifacts.noGitTracking=false; t.finalReport={summary:"coverage fixture \\u00b7 claims match reality",source:"agent-summary"}; w.renderD(t); } }catch(e){} },
|
|
661
|
+
undrive:function(w,d){ try{ var t=w.findS?w.findS(COV_FIXTURE):null; if(t&&w.__covVB){ t.verdict=w.__covVB.v; t.mockRun=w.__covVB.m; t.status=w.__covVB.s; if(t.artifacts)t.artifacts.noGitTracking=w.__covVB.g; t.finalReport=w.__covVB.r; w.renderD(t); } }catch(e){} } },
|
|
662
|
+
{ id:"P-WRITE-PASS", name:"Write-pass inline card (blue-accented)", surface:"detail", sel:".write-pass .head + h4", status:"shipped", tier:"v0.9",
|
|
663
|
+
drive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=true; }catch(e){} },
|
|
664
|
+
undrive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=false; }catch(e){} } },
|
|
317
665
|
{ id:"F-RECOVERY-CARD", name:"failed-task recovery card", surface:"detail", sel:".recovery-card", status:"shipped", tier:"v0.7", fixture:"demo-recovery-test-failed-v0.9" },
|
|
318
666
|
{ id:"F-CRASH-RECOVERY", name:"orphaned-session banner", surface:"detail", sel:".crash-recovery", status:"shipped", tier:"v0.7", fixture:"demo-crash-orphaned-mid-write-v0.9" },
|
|
319
667
|
{ id:"F-HOOK-BLOCK-CARD", name:"hook-block card + override", surface:"detail", sel:".hook-block-card", status:"shipped", tier:"v0.7", fixture:"demo-hook-block-bulk-rm-v0.9" },
|
|
320
668
|
{ id:"F-OVERRIDE-FLOW", name:"override + log reason", surface:"detail", sel:".hook-block-actions", status:"shipped", tier:"v0.7", fixture:"demo-hook-block-bulk-rm-v0.9" },
|
|
321
|
-
{ id:"F-GIT-NOTE", name:"non-git honest note", surface:"detail", sel:".git-note,.
|
|
322
|
-
|
|
669
|
+
{ id:"F-GIT-NOTE", name:"non-git honest note (S14: .verifier-panel died · the partial verdict line carries it)", surface:"detail", sel:".git-note,.vline[data-verify-verdict='partial']", status:"shipped", tier:"v0.7", fixture:"demo-non-git-honest-v1" },
|
|
670
|
+
/* S14 · the conversation rests as one collapsed row ("conversation · N
|
|
671
|
+
turns · every tool call recorded"); these rows drive the REAL expand
|
|
672
|
+
(details.conv-wrap.open — the same toggle the user's click flips),
|
|
673
|
+
observe inside, then collapse back. */
|
|
674
|
+
{ id:"F-CONVERSATION", name:"multi-speaker conversation (S14: behind the collapsed conversation row)", surface:"detail", sel:".conv-turn,.turn,.speaker", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9",
|
|
675
|
+
drive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=true; }catch(e){} },
|
|
676
|
+
undrive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=false; }catch(e){} } },
|
|
323
677
|
{ id:"P-ARTIFACTS-STRUCTURED", name:"structured artifacts", surface:"detail", sel:".art-cmp,.artifact,.art-card,.struct-art", status:"partial", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
|
|
324
678
|
{ id:"P-PANEL-TITLE", name:"uppercase panel title", surface:"detail", sel:".panel-title", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
|
|
325
|
-
{ id:"F-THREAD-PHASED", name:"phased narrative timeline", surface:"detail", sel:".tl-div,.chapter-card,.phase-pos", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9"
|
|
679
|
+
{ id:"F-THREAD-PHASED", name:"phased narrative timeline", surface:"detail", sel:".tl-div,.chapter-card,.phase-pos", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9",
|
|
680
|
+
drive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=true; }catch(e){} },
|
|
681
|
+
undrive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=false; }catch(e){} } },
|
|
326
682
|
{ id:"F-FILES-TESTS", name:"files & tests panel", surface:"detail", sel:".file-tree", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
|
|
327
|
-
{ id:"P-TOOL-CARD", name:"tool-call activity card", surface:"detail", sel:".tl-acts", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9"
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
{ id:"
|
|
683
|
+
{ id:"P-TOOL-CARD", name:"tool-call activity card", surface:"detail", sel:".tl-acts", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9",
|
|
684
|
+
drive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=true; }catch(e){} },
|
|
685
|
+
undrive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=false; }catch(e){} } },
|
|
686
|
+
{ id:"F-THINKING-EMITTER", name:"live thinking dots (needs SSE token stream)", surface:"detail", sel:".thinking", status:"partial", tier:"v0.7", fixture:"demo-running-streaming-v0.9",
|
|
687
|
+
drive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=true; }catch(e){} },
|
|
688
|
+
undrive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=false; }catch(e){} } },
|
|
689
|
+
{ id:"P-REPL-PRIMITIVES", name:"streaming repl-beat (needs SSE token stream)", surface:"detail", sel:".repl-beat", status:"partial", tier:"v0.7", fixture:"demo-running-streaming-v0.9",
|
|
690
|
+
drive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=true; }catch(e){} },
|
|
691
|
+
undrive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=false; }catch(e){} } },
|
|
692
|
+
{ id:"P-AUDIT-LINE", name:"inline audit beat (host-thread / live audit)", surface:"detail", sel:".audit-line", status:"partial", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9",
|
|
693
|
+
drive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=true; }catch(e){} },
|
|
694
|
+
undrive:function(w,d){ try{ var c=d.querySelector("details.conv-wrap"); if(c) c.open=false; }catch(e){} } },
|
|
331
695
|
{ id:"F-TOKEN-TABLE", name:"per-agent token table (needs artifacts.usage)", surface:"detail", sel:".token-table", status:"partial", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
696
|
+
/* overlay-renderer per-source cards · the drive renders the REAL per-source
|
|
697
|
+
render branch over a fixture payload (same shape the adapter unit fixtures
|
|
698
|
+
produce) so .overlay-figma/.overlay-drawio are observable without a live
|
|
699
|
+
Figma token or network fetch. */
|
|
700
|
+
{ id:"M-OVERLAY-FIGMA", name:"overlay-renderer · Figma", surface:"list", sel:".overlay-figma", status:"shipped", tier:"v1.0",
|
|
701
|
+
drive:function(w,d){ w.codehereRenderOverlayPayload({ source:"figma", url:"figma://file/abc123", text:"Figma file · API Design\\nCANVAS · Mobile\\n FRAME · Home 375×812\\n TEXT · Title · \\"Welcome to the app\\"", metadata:{ fileKey:"abc123", name:"API Design", lastModified:"2026-05-21T09:00:00Z", frames:[{ name:"Home", width:375, height:812, text:"Welcome to the app" }] } }); },
|
|
702
|
+
undrive:function(w,d){ var o=d.getElementById("or-overlay"); if(o) o.classList.remove("open"); } },
|
|
703
|
+
{ id:"M-OVERLAY-DRAWIO", name:"overlay-renderer · draw.io", surface:"list", sel:".overlay-drawio", status:"shipped", tier:"v1.0",
|
|
704
|
+
drive:function(w,d){ w.codehereRenderOverlayPayload({ source:"drawio", url:"drawio://gh/owner/repo/docs/arch.drawio", text:"draw.io diagram · github.com/owner/repo/docs/arch.drawio\\n\\nstructure: 5 cells · 1 edges · 1 page\\n--- raw xml (truncated) ---\\n<mxfile><diagram><mxGraphModel>...", metadata:{ label:"github.com/owner/repo/docs/arch.drawio", bytes:842, cellCount:5, edgeCount:1, pageCount:1 } }); },
|
|
705
|
+
undrive:function(w,d){ var o=d.getElementById("or-overlay"); if(o) o.classList.remove("open"); } },
|
|
706
|
+
{ id:"V1-MULTITASK", name:"multi-task projects · parent/child", surface:"list", sel:".project-rollup", status:"shipped", tier:"v1.0",
|
|
707
|
+
/* It-4 · banner-drive precedent: inject a known 3-task batch STATE
|
|
708
|
+
(__seedCovBatch, never DOM), then the REAL openRoom() -> renderRoom()
|
|
709
|
+
derives the .project-rollup row live. Restored in undrive. */
|
|
710
|
+
drive:function(w,d){ try{ __seedCovBatch(w); w.__covBatchCwd=__openBatchRoom(w); }catch(e){} },
|
|
711
|
+
undrive:function(w,d){ try{ if(w.closeRoom) w.closeRoom(); __unseedCovBatch(w); }catch(e){} } },
|
|
712
|
+
{ id:"V1-FANOUT-ALL", name:"@all fanout to ready adapters", surface:"list", sel:".fanout-all", status:"shipped", tier:"v1.0",
|
|
713
|
+
/* Same injected batch room renders the .fanout-all grid (one column/adapter). */
|
|
714
|
+
drive:function(w,d){ try{ __seedCovBatch(w); __openBatchRoom(w); }catch(e){} },
|
|
715
|
+
undrive:function(w,d){ try{ if(w.closeRoom) w.closeRoom(); __unseedCovBatch(w); }catch(e){} } },
|
|
336
716
|
{ id:"F-AGENT-SWITCHER", name:"'reply as' agent picker", surface:"detail", sel:".agent-switcher", status:"future", tier:"cut" }
|
|
337
717
|
];
|
|
338
718
|
|
|
@@ -347,13 +727,230 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
347
727
|
return { verdict: el.getAttribute("data-verify-verdict"), gittracked: el.getAttribute("data-verify-gittracked") };
|
|
348
728
|
});
|
|
349
729
|
var says = Array.prototype.slice.call(doc.querySelectorAll('.tl-say')).map(function(el){
|
|
350
|
-
|
|
730
|
+
// A glance turn carries its words in .gl-line (the one-sentence glance);
|
|
731
|
+
// a plain reasoning bubble in .tl-say-text. First match in document
|
|
732
|
+
// order wins — an actually-wordless bubble still reads len 0 and FAILS.
|
|
733
|
+
var w = el.querySelector(".tl-say-text, .gl-line");
|
|
734
|
+
var t = (w ? w.textContent : "") || "";
|
|
351
735
|
t = t.replace(/^\\s+|\\s+$/g, "");
|
|
352
736
|
return { len:t.length, text:t };
|
|
353
737
|
});
|
|
738
|
+
// Thariq loop-1 · recovery-card buttons. Capture each action button's
|
|
739
|
+
// enabled state + whether its onclick is a bare al() toast — so the
|
|
740
|
+
// recovery-buttons-must-not-lie probe can FAIL on a relapse.
|
|
741
|
+
var recoveryButtons = Array.prototype.slice.call(doc.querySelectorAll('.recovery-card [data-verify-action]')).map(function(el){
|
|
742
|
+
var oc = el.getAttribute("onclick") || "";
|
|
743
|
+
return {
|
|
744
|
+
action: el.getAttribute("data-verify-action"),
|
|
745
|
+
disabled: el.hasAttribute("disabled"),
|
|
746
|
+
// a "lie" = an ENABLED button whose only behavior is a toast
|
|
747
|
+
bareToast: !el.hasAttribute("disabled") && /^\\s*al\\(/.test(oc),
|
|
748
|
+
hasReason: !!el.getAttribute("title"),
|
|
749
|
+
};
|
|
750
|
+
});
|
|
751
|
+
// Thariq loop-2 · the observer rail. Capture its mode (empty/active), the
|
|
752
|
+
// claimed live count, per-row states, and whether the empty state actually
|
|
753
|
+
// teaches (non-blank) — so H8 can pin: never a stale "live", never a blank
|
|
754
|
+
// empty box, never fake rows.
|
|
755
|
+
var obsRail = doc.querySelector('[data-verify-unit="observer"]');
|
|
756
|
+
var observer = obsRail ? {
|
|
757
|
+
mode: obsRail.getAttribute("data-verify-observer"),
|
|
758
|
+
liveClaimed: Number(obsRail.getAttribute("data-verify-observer-live") || 0),
|
|
759
|
+
rowStates: Array.prototype.slice.call(obsRail.querySelectorAll('[data-verify-obs-state]')).map(function(r){ return r.getAttribute("data-verify-obs-state"); }),
|
|
760
|
+
teachLen: (obsRail.querySelector('.obs-teach') ? (obsRail.querySelector('.obs-teach').textContent||"").trim().length : 0),
|
|
761
|
+
textLen: (obsRail.textContent||"").trim().length
|
|
762
|
+
} : null;
|
|
763
|
+
// S7 · the composer cost chips. Capture each chip's computed mode + what
|
|
764
|
+
// it says, so H10 can pin: the chip never claims a computation that
|
|
765
|
+
// didn't happen (the pre-S7 chip was a hardcoded ~$0.02 under a
|
|
766
|
+
// "based on prompt length + pricing" tooltip).
|
|
767
|
+
var costEst = Array.prototype.slice.call(doc.querySelectorAll('[data-verify-unit="cost-est"]')).map(function(el){
|
|
768
|
+
return {
|
|
769
|
+
mode: el.getAttribute("data-verify-mode"),
|
|
770
|
+
text: (el.textContent||"").trim(),
|
|
771
|
+
title: el.getAttribute("title") || ""
|
|
772
|
+
};
|
|
773
|
+
});
|
|
354
774
|
var found = !!(meta && visible);
|
|
775
|
+
// S12 · H13 — the home hero's CLAIMED counts (stamped data-verify-hero-*)
|
|
776
|
+
// vs an INDEPENDENT re-derivation from the same task state the page holds
|
|
777
|
+
// (_cached via the iframe window). The hero element persists in the DOM
|
|
778
|
+
// while a detail is open, so the check is readable from any fixture.
|
|
779
|
+
var heroEl = doc.querySelector('[data-verify-unit="home-hero"]');
|
|
780
|
+
var hero = null;
|
|
781
|
+
if (heroEl){
|
|
782
|
+
hero = {
|
|
783
|
+
mode: heroEl.getAttribute("data-verify-hero-mode"),
|
|
784
|
+
agents: Number(heroEl.getAttribute("data-verify-hero-agents")||0),
|
|
785
|
+
verified: Number(heroEl.getAttribute("data-verify-hero-verified")||0),
|
|
786
|
+
caught: Number(heroEl.getAttribute("data-verify-hero-caught")||0),
|
|
787
|
+
stopped: Number(heroEl.getAttribute("data-verify-hero-stopped")||0),
|
|
788
|
+
running: Number(heroEl.getAttribute("data-verify-hero-running")||0),
|
|
789
|
+
expected: null
|
|
790
|
+
};
|
|
791
|
+
try {
|
|
792
|
+
var hw = doc.defaultView;
|
|
793
|
+
var hAll = (hw && typeof hw.allS==="function") ? hw.allS((hw._cached && hw._cached.tasks)||{}) : [];
|
|
794
|
+
/* S18 · PAIRED with ui-page-v2 heroCounts' top filter: practice runs
|
|
795
|
+
(mockRun) never feed the day-line. Both sides exclude them
|
|
796
|
+
identically — the honest-rule update, not a weakened probe. */
|
|
797
|
+
var hReal = hAll.filter(function(t){ return !/^demo-/.test(t.sessionId||"") && !t.mockRun; });
|
|
798
|
+
var hSod = new Date(); hSod.setHours(0,0,0,0);
|
|
799
|
+
var hToday = hReal.filter(function(t){ return (t.startedAt||0) >= hSod.getTime(); });
|
|
800
|
+
var hLive = ["running","queued","paused","blocked"];
|
|
801
|
+
// Refuter C2 · this used to copy heroCounts' own (then-broken)
|
|
802
|
+
// expressions — a probe that re-derives the claim pipeline is probe
|
|
803
|
+
// theater. Derive from the VERDICT ground truth: verified = the
|
|
804
|
+
// verdict says claims match reality, on a real (non-mock) completed
|
|
805
|
+
// git-tracked run; caught = the verdict says broken; stopped =
|
|
806
|
+
// terminal-unclean statuses; running ignores the day boundary.
|
|
807
|
+
var hLiveAll = hReal.filter(function(t){ return hLive.indexOf(t.status)>=0; });
|
|
808
|
+
var hAg = {};
|
|
809
|
+
hToday.concat(hLiveAll).forEach(function(t){ var a=String(t.agent||""); if(a) hAg[a]=1; });
|
|
810
|
+
hero.expected = {
|
|
811
|
+
today: hToday.length,
|
|
812
|
+
agents: Object.keys(hAg).length,
|
|
813
|
+
// PAIRED with heroCounts' composite-grade rule (2.2): green needs an
|
|
814
|
+
// INDEPENDENT signal when a grade is attached; the legacy claim check
|
|
815
|
+
// applies only to verdicts without one. The sides drifted when the
|
|
816
|
+
// hero adopted grades and this stayed on claim-status — first exposed
|
|
817
|
+
// 2026-07-02 by a real --ship run (claim verified, self-report only):
|
|
818
|
+
// hero said 1, this derived 2. Change BOTH sides identically.
|
|
819
|
+
verified: hToday.filter(function(t){
|
|
820
|
+
return !!(t.verdict && (t.verdict.grade ? t.verdict.grade.green : (t.verdict.claim && t.verdict.claim.status==="verified")))
|
|
821
|
+
&& !t.mockRun
|
|
822
|
+
&& ["completed","done"].indexOf(t.status)>=0
|
|
823
|
+
&& !(t.artifacts && t.artifacts.noGitTracking);
|
|
824
|
+
}).length,
|
|
825
|
+
caught: hToday.filter(function(t){ var v=t.verdict; return !!(v && (v.grade ? v.grade.headline==="broken" : (v.claim && v.claim.status==="broken"))); }).length,
|
|
826
|
+
stopped: hToday.filter(function(t){ return ["failed","error","crashed","killed"].indexOf(t.status)>=0; }).length,
|
|
827
|
+
running: hLiveAll.length
|
|
828
|
+
};
|
|
829
|
+
} catch(e){ /* state unreachable — H13 reports BLOCKED, never a silent green */ }
|
|
830
|
+
}
|
|
831
|
+
// S13 · H14 — the decision queue's CLAIMED N (data-verify-decisions-n)
|
|
832
|
+
// vs an INDEPENDENT re-derivation from the same state the page holds:
|
|
833
|
+
// pending write-passes (window._wpPendingAll, the list wpPoll feeds the
|
|
834
|
+
// queue AND the modal from) + verifier-broken verdicts today + failed
|
|
835
|
+
// runs with the prompt recorded (Retry available), non-demo, today.
|
|
836
|
+
// Like the hero, the container persists in the DOM while a detail is
|
|
837
|
+
// open, so the check is readable from any fixture.
|
|
838
|
+
var dqEl = doc.querySelector('[data-verify-unit="decisions"]');
|
|
839
|
+
var decisions = {
|
|
840
|
+
present: !!dqEl,
|
|
841
|
+
n: dqEl ? Number(dqEl.getAttribute("data-verify-decisions-n")||0) : 0,
|
|
842
|
+
rows: dqEl ? dqEl.querySelectorAll(".dq-row").length : 0,
|
|
843
|
+
expected: null
|
|
844
|
+
};
|
|
845
|
+
try {
|
|
846
|
+
var dw = doc.defaultView;
|
|
847
|
+
if (dw && typeof dw.allS === "function"){
|
|
848
|
+
/* S18 · PAIRED with ui-page-v2 decisionItems' top filter: a practice
|
|
849
|
+
(mockRun) failure never demands a real decision. Both sides exclude
|
|
850
|
+
them identically — the honest-rule update, not a weakened probe. */
|
|
851
|
+
var dAll = dw.allS((dw._cached && dw._cached.tasks)||{}).filter(function(t){ return !/^demo-/.test(t.sessionId||"") && !t.mockRun; });
|
|
852
|
+
var dSod = new Date(); dSod.setHours(0,0,0,0);
|
|
853
|
+
var dToday = function(t){ return (t.startedAt||0) >= dSod.getTime(); };
|
|
854
|
+
var dBrokenIs = function(t){ return !!(t.verdict && t.verdict.claim && t.verdict.claim.status==="broken"); };
|
|
855
|
+
var dPasses = (dw._wpPendingAll||[]).length;
|
|
856
|
+
var dBroken = dAll.filter(function(t){ return dToday(t) && dBrokenIs(t); }).length;
|
|
857
|
+
var dFailed = dAll.filter(function(t){ return dToday(t) && ["failed","error","crashed","killed"].indexOf(t.status)>=0 && t.prompt && !dBrokenIs(t); }).length;
|
|
858
|
+
decisions.expected = dPasses + dBroken + dFailed;
|
|
859
|
+
}
|
|
860
|
+
} catch(e){ /* state unreachable — H14 reports BLOCKED, never a silent green */ }
|
|
861
|
+
// S12 · the detail verdict hero stamps which staging it chose; it ALSO
|
|
862
|
+
// carries data-verify-unit="verifier" so H1/H9 already read it above.
|
|
863
|
+
var vhEl = doc.querySelector("[data-verify-verdict-hero]");
|
|
864
|
+
var verdictHero = vhEl ? vhEl.getAttribute("data-verify-verdict-hero") : null;
|
|
865
|
+
// S2 · a running task must carry a Stop control on its detail.
|
|
866
|
+
var hasStopControl = !!doc.querySelector('.detail-meta [data-verify-action="stop"]');
|
|
867
|
+
// S1 · the detail composer's follow-up honesty: enabled means the server
|
|
868
|
+
// says the message can be DELIVERED; disabled must carry a plain reason.
|
|
869
|
+
var dta = doc.getElementById('detail-ta');
|
|
870
|
+
var followUp = dta ? {
|
|
871
|
+
state: dta.getAttribute('data-verify-followup'),
|
|
872
|
+
disabled: dta.disabled,
|
|
873
|
+
placeholderLen: (dta.getAttribute('placeholder')||'').trim().length
|
|
874
|
+
} : null;
|
|
875
|
+
// S19 · glyph discipline — scan the REAL rendered surface (visible text
|
|
876
|
+
// nodes + human-facing attributes, script/style excluded) for emoji /
|
|
877
|
+
// pictograph codepoints outside the sanctioned geometric glyph set
|
|
878
|
+
// (\\u2713 \\u2715 \\u25CF \\u25CB \\u2022 \\u2298 \\u203A \\u26A0 \\u2014 \\u00B7, \\u2191 send, \\u2190 back,
|
|
879
|
+
// \\u2318/\\u23CE keyboard hints). Effect-observing: a re-introduced emoji
|
|
880
|
+
// anywhere in the driven chrome FAILS the board via H15.
|
|
881
|
+
var glyphScan = { scanned:false, hits:[] };
|
|
882
|
+
try {
|
|
883
|
+
// S19 R2 · the token contract bans emoji-AS-ICON (chrome), never emoji
|
|
884
|
+
// a user typed: [data-user-content] containers (prompts, say/think
|
|
885
|
+
// text, reports, step labels/outputs) are run CONTENT and excluded —
|
|
886
|
+
// an honest "fix the bug" prompt with an emoji must not void the board.
|
|
887
|
+
var gIsContent = function(el){
|
|
888
|
+
while (el && el.nodeType===1){
|
|
889
|
+
if (el.hasAttribute && el.hasAttribute("data-user-content")) return true;
|
|
890
|
+
el = el.parentNode;
|
|
891
|
+
}
|
|
892
|
+
return false;
|
|
893
|
+
};
|
|
894
|
+
var gParts = [];
|
|
895
|
+
var gCollect = function(){
|
|
896
|
+
var gWalk = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT, { acceptNode: function(nd){
|
|
897
|
+
var pn = nd.parentNode && nd.parentNode.nodeName;
|
|
898
|
+
if (pn==="SCRIPT"||pn==="STYLE") return NodeFilter.FILTER_REJECT;
|
|
899
|
+
return gIsContent(nd.parentNode) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
|
|
900
|
+
} });
|
|
901
|
+
while (gWalk.nextNode()) gParts.push(gWalk.currentNode.nodeValue);
|
|
902
|
+
Array.prototype.slice.call(doc.body.querySelectorAll("*")).forEach(function(el){
|
|
903
|
+
if (el.nodeName==="SCRIPT"||el.nodeName==="STYLE") return;
|
|
904
|
+
if (gIsContent(el)) return;
|
|
905
|
+
["title","placeholder","aria-label","alt","value"].forEach(function(at){
|
|
906
|
+
var v = el.getAttribute && el.getAttribute(at);
|
|
907
|
+
if (v) gParts.push(v);
|
|
908
|
+
});
|
|
909
|
+
});
|
|
910
|
+
};
|
|
911
|
+
gCollect();
|
|
912
|
+
// S19 R2 · the chrome claim must be OBSERVED in the one-click-away
|
|
913
|
+
// collapse states too: drive the REAL history/practice toggles (the
|
|
914
|
+
// same render path the S18 coverage drives use), collect again, then
|
|
915
|
+
// restore whichever state the surface was in. Pure UI state — no data
|
|
916
|
+
// is changed, and the duplicate text just re-feeds the dedup'd regex.
|
|
917
|
+
var gw = doc.defaultView;
|
|
918
|
+
var gHistBtn = doc.querySelector(".hist-row:not(.prac-row)");
|
|
919
|
+
var gPracBtn = doc.querySelector(".prac-row");
|
|
920
|
+
var gHistWas = gHistBtn ? gHistBtn.getAttribute("aria-expanded")==="true" : false;
|
|
921
|
+
var gPracWas = gPracBtn ? gPracBtn.getAttribute("aria-expanded")==="true" : false;
|
|
922
|
+
try {
|
|
923
|
+
if (gw && gw.__histExpand) gw.__histExpand();
|
|
924
|
+
if (gw && gw.__pracExpand) gw.__pracExpand();
|
|
925
|
+
gCollect();
|
|
926
|
+
} finally {
|
|
927
|
+
try { if (gw && !gHistWas && gw.__histCollapse) gw.__histCollapse(); } catch(e2){}
|
|
928
|
+
try { if (gw && !gPracWas && gw.__pracCollapse) gw.__pracCollapse(); } catch(e2){}
|
|
929
|
+
}
|
|
930
|
+
var gText = gParts.join("\\n");
|
|
931
|
+
var gRe = /[\\u2192-\\u21FF]|[\\u2300-\\u2317]|[\\u2319-\\u23CD]|[\\u23CF-\\u23FF]|[\\u2600-\\u269F]|[\\u26A1-\\u26FF]|[\\u2700-\\u2712]|\\u2714|[\\u2716-\\u27BF]|[\\u2B00-\\u2BFF]|\\uFE0F|[\\uD83A-\\uD83F][\\uDC00-\\uDFFF]|[\\uD83C-\\uD83E][\\uDC00-\\uDFFF]/g;
|
|
932
|
+
var gm, gSeen = {};
|
|
933
|
+
while ((gm = gRe.exec(gText))){
|
|
934
|
+
var gc = gm[0];
|
|
935
|
+
if (gSeen[gc]) continue;
|
|
936
|
+
gSeen[gc] = 1;
|
|
937
|
+
var gcp = gc.codePointAt(0).toString(16).toUpperCase();
|
|
938
|
+
glyphScan.hits.push("U+"+gcp+" ('"+gc+"') rendered in the chrome \\u00b7 emoji/pictograph outside the sanctioned glyph set");
|
|
939
|
+
}
|
|
940
|
+
glyphScan.scanned = true;
|
|
941
|
+
} catch(e){ /* unscannable -> H15 reports BLOCKED, never a silent green */ }
|
|
355
942
|
return {
|
|
943
|
+
glyphScan: glyphScan,
|
|
356
944
|
found: found,
|
|
945
|
+
hero: hero,
|
|
946
|
+
decisions: decisions,
|
|
947
|
+
verdictHero: verdictHero,
|
|
948
|
+
observer: observer,
|
|
949
|
+
costEst: costEst,
|
|
950
|
+
hasStopControl: hasStopControl,
|
|
951
|
+
followUp: followUp,
|
|
952
|
+
recoveryButtons: recoveryButtons,
|
|
953
|
+
recoveryState: (doc.querySelector('.recovery-card') ? doc.querySelector('.recovery-card').getAttribute('data-verify-recovery') : null),
|
|
357
954
|
status: meta ? meta.getAttribute("data-verify-status") : null,
|
|
358
955
|
terminalFail: meta ? meta.getAttribute("data-verify-terminal-fail")==="true" : false,
|
|
359
956
|
hasExplanation: meta ? meta.getAttribute("data-verify-has-explanation")==="true" : false,
|
|
@@ -385,9 +982,18 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
385
982
|
tries++;
|
|
386
983
|
try {
|
|
387
984
|
var w = frameWin(), d = frameDoc();
|
|
388
|
-
|
|
985
|
+
/* S13 · at rest the home may legitimately have NO task-cards (today
|
|
986
|
+
empty -> first-run hero; demos collapsed behind the history row).
|
|
987
|
+
Ready = task state loaded AND renderL produced a real at-rest
|
|
988
|
+
surface (rows, observer, the history row, or the empty state). */
|
|
989
|
+
if (w && typeof w.showD === "function" && w._cached && w._cached.tasks && d && d.querySelectorAll(".task-card, .obs-row, .hist-row, .empty-state").length > 0) return resolve();
|
|
990
|
+
// The dashboard auto-opens a running task's detail on load, which
|
|
991
|
+
// hides the list and left runAll dead-on-arrival ("surface never
|
|
992
|
+
// became ready") whenever anything was running — found cold by the
|
|
993
|
+
// S5-S8 acceptance walk. Close the auto-opened detail and re-poll.
|
|
994
|
+
if (w && typeof w.hideD === "function" && d && d.body && d.body.classList.contains("detail-open")) { try { w.hideD(); } catch(e2){} }
|
|
389
995
|
} catch(e){ /* not loaded yet */ }
|
|
390
|
-
if (tries > 80) return reject(new Error("surface never became ready (no showD / no task cards)
|
|
996
|
+
if (tries > 80) return reject(new Error("surface never became ready (no showD / no task cards) · is the daemon serving seeded sessions?"));
|
|
391
997
|
setTimeout(poll, 150);
|
|
392
998
|
})();
|
|
393
999
|
});
|
|
@@ -395,11 +1001,23 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
395
1001
|
|
|
396
1002
|
// Navigate the REAL surface to a session and read its contract. Honest about
|
|
397
1003
|
// the not-seeded case: returns found=false rather than inventing a result.
|
|
398
|
-
|
|
1004
|
+
// S20 · a fixture may carry { sid, lens }: the lens is applied through the
|
|
1005
|
+
// page's OWN applyLens (the same function the ⌘K command runs — a real
|
|
1006
|
+
// recomposition, never harness CSS), observed, then restored to default.
|
|
1007
|
+
function observe(fx){
|
|
1008
|
+
var sid = (fx && typeof fx === "object") ? (fx.sid || fx.id) : fx;
|
|
1009
|
+
var lens = (fx && typeof fx === "object") ? fx.lens : null;
|
|
399
1010
|
var w = frameWin(), d = frameDoc();
|
|
400
1011
|
try { if (typeof w.hideD === "function") w.hideD(); } catch(e){}
|
|
401
|
-
try {
|
|
402
|
-
|
|
1012
|
+
try {
|
|
1013
|
+
if (lens && typeof w.applyLens === "function") w.applyLens(lens);
|
|
1014
|
+
w.showD(sid);
|
|
1015
|
+
} catch(e){ return Promise.resolve({ found:false, error:String(e), verifiers:[], says:[] }); }
|
|
1016
|
+
return sleep(140).then(function(){
|
|
1017
|
+
var s = readSurface(frameDoc());
|
|
1018
|
+
if (lens){ try { var w2 = frameWin(); if (typeof w2.applyLens === "function") w2.applyLens("default"); } catch(e){} }
|
|
1019
|
+
return s;
|
|
1020
|
+
});
|
|
403
1021
|
}
|
|
404
1022
|
|
|
405
1023
|
/* -------- run one (invariant × fixture) into a structured result -------- */
|
|
@@ -432,7 +1050,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
432
1050
|
var tbody = document.querySelector("#matrix tbody");
|
|
433
1051
|
var hrow = "<tr><th style=\\"width:40%\\">Invariant (unit)</th><th class=\\"needs\\" style=\\"width:13%\\">needs</th>";
|
|
434
1052
|
FIXTURES.forEach(function(fx){
|
|
435
|
-
hrow += "<th><div class=\\"fixhdr"+(fx.probe?" probe":"")+"\\">"+(fx.probe?"
|
|
1053
|
+
hrow += "<th><div class=\\"fixhdr"+(fx.probe?" probe":"")+"\\">"+(fx.probe?"probe \\u00b7 ":"")+fx.label+"</div></th>";
|
|
436
1054
|
});
|
|
437
1055
|
hrow += "</tr>";
|
|
438
1056
|
thead.innerHTML = hrow;
|
|
@@ -487,17 +1105,17 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
487
1105
|
|
|
488
1106
|
function paintTally(c){
|
|
489
1107
|
document.getElementById("tally").innerHTML =
|
|
490
|
-
"<span class=\\"p\\">\\
|
|
1108
|
+
"<span class=\\"p\\">\\u2713 "+c.pass+"</span><span class=\\"f\\">\\u2715 "+c.fail+"</span><span class=\\"b\\">\\u2298 "+c.blocked+"</span><span class=\\"t\\">/ "+c.total+"</span>";
|
|
491
1109
|
var line = document.getElementById("verdict-line");
|
|
492
1110
|
if (c.ok){
|
|
493
|
-
line.className = "verdict-line green";
|
|
1111
|
+
line.className = "verdict-line green"; line.setAttribute("data-evidence","measured");
|
|
494
1112
|
line.textContent = "\\u2713 honest: every PASS was observed, and the probe FAILED exactly as designed (the harness can see a lie).";
|
|
495
1113
|
} else {
|
|
496
|
-
line.className = "verdict-line red";
|
|
1114
|
+
line.className = "verdict-line red"; line.setAttribute("data-evidence","measured");
|
|
497
1115
|
var parts = [];
|
|
498
1116
|
if (c.unexpectedFail.length) parts.push("unexpected FAIL: "+c.unexpectedFail.join(", "));
|
|
499
|
-
if (c.probeMissed.length) parts.push("PROBE DID NOT FAIL ("+c.probeMissed.join(", ")+") \\
|
|
500
|
-
line.textContent = "\\
|
|
1117
|
+
if (c.probeMissed.length) parts.push("PROBE DID NOT FAIL ("+c.probeMissed.join(", ")+") \\u00b7 the harness is blind, treat the whole board as void");
|
|
1118
|
+
line.textContent = "\\u2715 " + parts.join(" \\u00b7 ");
|
|
501
1119
|
}
|
|
502
1120
|
}
|
|
503
1121
|
|
|
@@ -510,15 +1128,35 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
510
1128
|
});
|
|
511
1129
|
}
|
|
512
1130
|
|
|
1131
|
+
// S20 · the board observes the DEFAULT lens unless a fixture says otherwise:
|
|
1132
|
+
// pin it before driving (the iframe shares localStorage with the dashboard,
|
|
1133
|
+
// so a user's saved lens would silently reshape every fixture) and restore
|
|
1134
|
+
// the user's saved lens after.
|
|
1135
|
+
function pinLensDefault(){
|
|
1136
|
+
try {
|
|
1137
|
+
var w = frameWin(), d = frameDoc();
|
|
1138
|
+
if (window.__verifyLensSaved === undefined) window.__verifyLensSaved = (d.body.getAttribute("data-lens")) || "default";
|
|
1139
|
+
if (typeof w.applyLens === "function") w.applyLens("default");
|
|
1140
|
+
} catch(e){}
|
|
1141
|
+
}
|
|
1142
|
+
function restoreLens(){
|
|
1143
|
+
try {
|
|
1144
|
+
var w = frameWin();
|
|
1145
|
+
if (typeof w.applyLens === "function") w.applyLens(window.__verifyLensSaved || "default");
|
|
1146
|
+
delete window.__verifyLensSaved;
|
|
1147
|
+
} catch(e){}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
513
1150
|
function runAll(){
|
|
514
1151
|
var btn = document.getElementById("run-all");
|
|
515
1152
|
btn.disabled = true; btn.textContent = "Running\\u2026";
|
|
516
1153
|
var results = [];
|
|
517
1154
|
return surfaceReady().then(function(){
|
|
1155
|
+
pinLensDefault();
|
|
518
1156
|
// iterate fixtures, observe each once, run all invariants against it
|
|
519
1157
|
return FIXTURES.reduce(function(p, fx){
|
|
520
1158
|
return p.then(function(){
|
|
521
|
-
return observe(fx
|
|
1159
|
+
return observe(fx).then(function(s){
|
|
522
1160
|
INVARIANTS.forEach(function(inv){
|
|
523
1161
|
var r = runOne(inv, fx, s);
|
|
524
1162
|
resultMap[key(inv.id, fx.id)] = r;
|
|
@@ -529,6 +1167,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
529
1167
|
});
|
|
530
1168
|
}, Promise.resolve());
|
|
531
1169
|
}).then(function(){
|
|
1170
|
+
restoreLens();
|
|
532
1171
|
var c = classify(results);
|
|
533
1172
|
paintTally(c);
|
|
534
1173
|
lastSingle = results[results.length-1] || null;
|
|
@@ -537,9 +1176,10 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
537
1176
|
btn.disabled = false; btn.textContent = "\\u25b6 Run all";
|
|
538
1177
|
return results;
|
|
539
1178
|
}).catch(function(err){
|
|
1179
|
+
restoreLens();
|
|
540
1180
|
btn.disabled = false; btn.textContent = "\\u25b6 Run all";
|
|
541
1181
|
document.getElementById("verdict-line").className = "verdict-line red";
|
|
542
|
-
document.getElementById("verdict-line").textContent = "\\
|
|
1182
|
+
document.getElementById("verdict-line").textContent = "\\u2298 BLOCKED: "+String(err && err.message || err);
|
|
543
1183
|
throw err;
|
|
544
1184
|
});
|
|
545
1185
|
}
|
|
@@ -571,7 +1211,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
571
1211
|
var tb = document.querySelector("#cov-matrix tbody");
|
|
572
1212
|
if (!tb) return;
|
|
573
1213
|
tb.innerHTML = FEATURES.map(function(f){
|
|
574
|
-
var statusTag = f.status==="shipped"?"":f.status==="partial"?"<span class=\\"probe-tag\\" style=\\"border-color:rgba(
|
|
1214
|
+
var statusTag = f.status==="shipped"?"":f.status==="partial"?"<span class=\\"probe-tag\\" style=\\"border-color:rgba(217,164,65,.5);color:var(--warn)\\">partial</span>":f.status==="roadmap"?"<span class=\\"probe-tag\\" style=\\"border-color:var(--border);color:var(--fg-40)\\">roadmap</span>":"<span class=\\"probe-tag\\" style=\\"border-color:var(--border);color:var(--fg-40)\\">"+(f.tier==="cut"?"cut":"future")+"</span>";
|
|
575
1215
|
return "<tr><td><span class=\\"unit-id\\">"+f.id+"</span> <span class=\\"unit-desc\\">"+f.name+"</span>"+statusTag+"</td>"+
|
|
576
1216
|
"<td class=\\"needs\\">"+f.tier+"</td>"+
|
|
577
1217
|
"<td class=\\"needs\\">"+f.surface+"</td>"+
|
|
@@ -605,12 +1245,36 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
605
1245
|
var line = document.getElementById("cov-verdict");
|
|
606
1246
|
if (!line) return;
|
|
607
1247
|
if (c.ok){
|
|
608
|
-
line.className = "verdict-line green";
|
|
1248
|
+
line.className = "verdict-line green"; line.setAttribute("data-evidence","measured");
|
|
609
1249
|
line.textContent = "\\u2713 every feature claimed shipped was OBSERVED on the real surface · "+c.shipped+" shipped checked · "+(c.roadmap+c.future)+" correctly absent (roadmap/future).";
|
|
610
1250
|
} else {
|
|
611
|
-
line.className = "verdict-line red";
|
|
612
|
-
line.textContent = "\\
|
|
1251
|
+
line.className = "verdict-line red"; line.setAttribute("data-evidence","measured");
|
|
1252
|
+
line.textContent = "\\u2715 "+c.missing+" feature(s) claimed shipped but NOT rendered: "+c.missingIds.join(", ")+" · PRODUCT_STATE overclaims, fix the doc or the code.";
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// OBSERVED requires a VISIBLE element, not just a DOM match — display:none
|
|
1257
|
+
// matched querySelectorAll and green-lit four dead state banners for weeks
|
|
1258
|
+
// (2026-06-07 audit, AUD-05). NOTE: layout-based checks (getClientRects /
|
|
1259
|
+
// offsetParent) cannot work here — the observed iframe sits inside the
|
|
1260
|
+
// harness's own hidden replay panel, so the product surface never has
|
|
1261
|
+
// layout. Instead walk computed styles WITHIN the iframe document: the
|
|
1262
|
+
// product hiding its own element is what we test; harness chrome isn't.
|
|
1263
|
+
function visibleInDoc(el, d){
|
|
1264
|
+
var node = el;
|
|
1265
|
+
while (node && node !== d.documentElement) {
|
|
1266
|
+
var cs = (d.defaultView || window).getComputedStyle(node);
|
|
1267
|
+
if (cs.display === "none" || cs.visibility === "hidden") return false;
|
|
1268
|
+
node = node.parentElement;
|
|
613
1269
|
}
|
|
1270
|
+
return true;
|
|
1271
|
+
}
|
|
1272
|
+
function anyVisible(d, sel){
|
|
1273
|
+
var els = d.querySelectorAll(sel);
|
|
1274
|
+
for (var i = 0; i < els.length; i++) {
|
|
1275
|
+
if (visibleInDoc(els[i], d)) return true;
|
|
1276
|
+
}
|
|
1277
|
+
return false;
|
|
614
1278
|
}
|
|
615
1279
|
|
|
616
1280
|
function runCoverage(){
|
|
@@ -619,17 +1283,32 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
619
1283
|
covResults = [];
|
|
620
1284
|
return surfaceReady().then(function(){
|
|
621
1285
|
var w = frameWin();
|
|
1286
|
+
pinLensDefault();
|
|
622
1287
|
try { if (typeof w.hideD==="function") w.hideD(); } catch(e){}
|
|
623
1288
|
return sleep(140);
|
|
624
1289
|
}).then(function(){
|
|
625
1290
|
// list-surface features (dashboard visible)
|
|
626
1291
|
var d = frameDoc();
|
|
627
|
-
FEATURES.filter(function(f){ return f.surface==="list" && f.status!=="roadmap" && f.status!=="future"; })
|
|
628
|
-
|
|
1292
|
+
var listFeats = FEATURES.filter(function(f){ return f.surface==="list" && f.status!=="roadmap" && f.status!=="future"; });
|
|
1293
|
+
listFeats.filter(function(f){ return !f.drive; }).forEach(function(f){
|
|
1294
|
+
var present = anyVisible(d, f.sel);
|
|
629
1295
|
var r = { id:f.id, present:present, verdict:covVerdict(f, present) };
|
|
630
1296
|
covResults.push(r); paintCov(r);
|
|
631
1297
|
});
|
|
632
|
-
|
|
1298
|
+
// driven on-demand surfaces: enter the state, observe, restore.
|
|
1299
|
+
return listFeats.filter(function(f){ return !!f.drive; }).reduce(function(p, f){
|
|
1300
|
+
return p.then(function(){
|
|
1301
|
+
var w = frameWin(), dd = frameDoc();
|
|
1302
|
+
try { f.drive(w, dd); } catch(e){}
|
|
1303
|
+
return sleep(120).then(function(){
|
|
1304
|
+
var present = anyVisible(frameDoc(), f.sel);
|
|
1305
|
+
var r = { id:f.id, present:present, verdict:covVerdict(f, present) };
|
|
1306
|
+
covResults.push(r); paintCov(r);
|
|
1307
|
+
try { f.undrive(w, frameDoc()); } catch(e){}
|
|
1308
|
+
return sleep(60);
|
|
1309
|
+
});
|
|
1310
|
+
});
|
|
1311
|
+
}, Promise.resolve());
|
|
633
1312
|
}).then(function(){
|
|
634
1313
|
// detail-surface features, grouped by the fixture that TRIGGERS them.
|
|
635
1314
|
// Conditional surfaces (recovery/crash/hook-block/...) only render on the
|
|
@@ -644,25 +1323,70 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
644
1323
|
return sleep(200).then(function(){
|
|
645
1324
|
var d = frameDoc();
|
|
646
1325
|
byFix[fx].forEach(function(f){
|
|
647
|
-
|
|
1326
|
+
/* S14 · detail rows gained drive/undrive support (same contract
|
|
1327
|
+
as list rows): the conversation rests collapsed, so timeline
|
|
1328
|
+
rows drive the REAL expand (the user's click on the row),
|
|
1329
|
+
observe, then restore. Never DOM injection of content. */
|
|
1330
|
+
var wd = frameWin();
|
|
1331
|
+
if (f.drive) { try { f.drive(wd, d); } catch(e){} }
|
|
1332
|
+
var present = anyVisible(d, f.sel);
|
|
648
1333
|
var r = { id:f.id, present:present, verdict:covVerdict(f, present), fixture:fx };
|
|
649
1334
|
covResults.push(r); paintCov(r);
|
|
1335
|
+
if (f.undrive) { try { f.undrive(wd, d); } catch(e){} }
|
|
650
1336
|
});
|
|
651
1337
|
});
|
|
652
1338
|
});
|
|
653
1339
|
}, Promise.resolve());
|
|
1340
|
+
}).then(function(){
|
|
1341
|
+
// room-surface features · drive the REAL openRoom() (same pattern as
|
|
1342
|
+
// showD above) on the first goal-head's data-goal-cwd, observe, close.
|
|
1343
|
+
// No goal-head on the home = no real tasks; the room genuinely cannot
|
|
1344
|
+
// be opened, so room features go MISSING/PARTIAL-GAP per their status —
|
|
1345
|
+
// honest, and F-GOAL-GROUP fails alongside so the cause is visible.
|
|
1346
|
+
var room = FEATURES.filter(function(f){ return f.surface==="room" && f.status!=="roadmap" && f.status!=="future"; });
|
|
1347
|
+
if (!room.length) return;
|
|
1348
|
+
var w0 = frameWin();
|
|
1349
|
+
/* S13 · goal-heads sit inside the EXPANDED history now — expand through
|
|
1350
|
+
the real toggle first. If the home has no earlier real tasks at all,
|
|
1351
|
+
fall back to the seeded coverage batch (same precedent as
|
|
1352
|
+
V1-MULTITASK) so the room render path is still observed for real. */
|
|
1353
|
+
try { if (w0.__histExpand) w0.__histExpand(); } catch(e){}
|
|
1354
|
+
var gh = frameDoc().querySelector(".goal-head[data-goal-cwd]");
|
|
1355
|
+
var check = function(){
|
|
1356
|
+
var d = frameDoc();
|
|
1357
|
+
room.forEach(function(f){
|
|
1358
|
+
var present = anyVisible(d, f.sel);
|
|
1359
|
+
var r = { id:f.id, present:present, verdict:covVerdict(f, present) };
|
|
1360
|
+
covResults.push(r); paintCov(r);
|
|
1361
|
+
});
|
|
1362
|
+
};
|
|
1363
|
+
var seededRoom = false;
|
|
1364
|
+
if (gh){ try { w0.openRoom(gh.getAttribute("data-goal-cwd")); } catch(e){} }
|
|
1365
|
+
else {
|
|
1366
|
+
try { if (__seedCovBatch(w0) && __openBatchRoom(w0)) seededRoom = true; } catch(e){}
|
|
1367
|
+
if (!seededRoom){ check(); try{ if(w0.__histCollapse) w0.__histCollapse(); }catch(e){} return; }
|
|
1368
|
+
}
|
|
1369
|
+
return sleep(200).then(function(){
|
|
1370
|
+
check();
|
|
1371
|
+
try { if (w0.closeRoom) w0.closeRoom(); } catch(e){}
|
|
1372
|
+
try { if (seededRoom) __unseedCovBatch(w0); } catch(e){}
|
|
1373
|
+
try { if (w0.__histCollapse) w0.__histCollapse(); } catch(e){}
|
|
1374
|
+
return sleep(120);
|
|
1375
|
+
});
|
|
654
1376
|
}).then(function(){
|
|
655
1377
|
// roadmap / future: SKIP (correctly absent), no observation
|
|
656
1378
|
FEATURES.filter(function(f){ return f.status==="roadmap" || f.status==="future"; }).forEach(function(f){
|
|
657
1379
|
var r = { id:f.id, present:false, verdict:covVerdict(f, false) };
|
|
658
1380
|
covResults.push(r); paintCov(r);
|
|
659
1381
|
});
|
|
1382
|
+
restoreLens();
|
|
660
1383
|
var c = classifyCov(covResults);
|
|
661
1384
|
paintCovTally(c);
|
|
662
1385
|
window.__verify_coverage = { results:covResults, summary:c };
|
|
663
1386
|
if (btn){ btn.disabled = false; btn.textContent = "\\u25b6 Run coverage"; }
|
|
664
1387
|
return covResults;
|
|
665
1388
|
}).catch(function(err){
|
|
1389
|
+
restoreLens();
|
|
666
1390
|
if (btn){ btn.disabled = false; btn.textContent = "\\u25b6 Run coverage"; }
|
|
667
1391
|
throw err;
|
|
668
1392
|
});
|
|
@@ -695,7 +1419,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
695
1419
|
function openReplay(){ replayOpen = true; document.getElementById("replay").classList.remove("hidden"); buildStepList(); }
|
|
696
1420
|
function buildStepList(){
|
|
697
1421
|
document.getElementById("steplist").innerHTML = FIXTURES.map(function(fx,i){
|
|
698
|
-
return "<div class=\\"steprow"+(fx.probe?" fail":"")+"\\" data-i=\\""+i+"\\"><span class=\\"st\\">\\u25cb</span><span class=\\"nm\\">"+(fx.probe?"
|
|
1422
|
+
return "<div class=\\"steprow"+(fx.probe?" fail":"")+"\\" data-i=\\""+i+"\\"><span class=\\"st\\">\\u25cb</span><span class=\\"nm\\">"+(fx.probe?"probe \\u00b7 ":"")+fx.label+"</span></div>";
|
|
699
1423
|
}).join("");
|
|
700
1424
|
Array.prototype.slice.call(document.querySelectorAll(".steprow")).forEach(function(row){
|
|
701
1425
|
row.addEventListener("click", function(){ jumpTo(Number(row.getAttribute("data-i"))); });
|
|
@@ -706,7 +1430,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
706
1430
|
if (!row) return;
|
|
707
1431
|
Array.prototype.slice.call(document.querySelectorAll(".steprow")).forEach(function(r){ r.classList.remove("active"); });
|
|
708
1432
|
if (verdict==null){ row.classList.add("active"); row.querySelector(".st").textContent="\\u25b8"; return; }
|
|
709
|
-
row.querySelector(".st").textContent = verdict==="PASS"?"\\
|
|
1433
|
+
row.querySelector(".st").textContent = verdict==="PASS"?"\\u2713":verdict==="FAIL"?"\\u2715":"\\u2298";
|
|
710
1434
|
}
|
|
711
1435
|
|
|
712
1436
|
var caption = document.getElementById("caption");
|
|
@@ -738,7 +1462,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
738
1462
|
var pass = results.filter(function(r){ return r.verdict==="PASS"; });
|
|
739
1463
|
var verdict = fails.length ? "FAIL" : (pass.length ? "PASS" : "BLOCKED");
|
|
740
1464
|
var html = "<span class=\\"badge "+verdict.toLowerCase()+"\\">"+verdict+"</span>";
|
|
741
|
-
html += "<span class=\\"sub\\" style=\\"color:var(--fg-50)\\">\\
|
|
1465
|
+
html += "<span class=\\"sub\\" style=\\"color:var(--fg-50)\\">\\u2713 "+pass.length+" \\u00b7 \\u2715 "+fails.length+" \\u00b7 \\u2298 "+blocked.length+"</span>";
|
|
742
1466
|
if (fails.length) html += "<span class=\\"issues\\">"+fails.map(function(f){ return f.unitId+": "+(f.checks[0]?f.checks[0].label:""); }).join(" \\u00b7 ")+"</span>";
|
|
743
1467
|
else if (!pass.length && blocked.length) html += "<span class=\\"issues blk\\">"+blocked[0].blockedReason+"</span>";
|
|
744
1468
|
fs.innerHTML = html;
|
|
@@ -751,7 +1475,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
751
1475
|
var d = frameDoc(); if (!d || staged) return;
|
|
752
1476
|
var st = d.createElement("style");
|
|
753
1477
|
st.textContent =
|
|
754
|
-
".header,.custom-view-banner,.failover-banner,.indexing-banner,.budget-banner
|
|
1478
|
+
".header,.custom-view-banner,.failover-banner,.indexing-banner,.budget-banner,#task-detail-view .input-area{display:none!important}" +
|
|
755
1479
|
"body{overflow:auto!important;height:auto!important}" +
|
|
756
1480
|
".content{padding:0!important}#task-detail-view{padding:18px 22px!important}";
|
|
757
1481
|
d.head.appendChild(st);
|
|
@@ -770,7 +1494,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
770
1494
|
var row = document.getElementById("cl-"+invId);
|
|
771
1495
|
if (!row) return;
|
|
772
1496
|
row.className = "cl-row run " + verdict.toLowerCase();
|
|
773
|
-
row.querySelector(".cl-st").textContent = verdict==="PASS"?"\\
|
|
1497
|
+
row.querySelector(".cl-st").textContent = verdict==="PASS"?"\\u2713":verdict==="FAIL"?"\\u2715":"\\u2298";
|
|
774
1498
|
if (text) row.querySelector(".cl-tx").textContent = text;
|
|
775
1499
|
}
|
|
776
1500
|
|
|
@@ -781,6 +1505,8 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
781
1505
|
if (invId==="H3") return d.querySelector('[data-verify-unit="outcome"]');
|
|
782
1506
|
if (invId==="H5") return d.querySelector('[data-verify-unit="filetree"]') || d.querySelector('[data-verify-unit="outcome"]');
|
|
783
1507
|
if (invId==="H6") return d.querySelector('.recovery-card,.crash-recovery,.hook-block-card') || d.querySelector('[data-verify-unit="detail"]');
|
|
1508
|
+
if (invId==="H13") return d.querySelector('[data-verify-unit="home-hero"]') || d.querySelector('[data-verify-unit="detail"]');
|
|
1509
|
+
if (invId==="H14") return d.querySelector('[data-verify-unit="decisions"]') || d.querySelector('[data-verify-unit="home-hero"]') || d.querySelector('[data-verify-unit="detail"]');
|
|
784
1510
|
return d.querySelector('[data-verify-unit="detail"]');
|
|
785
1511
|
}
|
|
786
1512
|
// A granular, human-readable line for what THIS check observed.
|
|
@@ -791,6 +1517,8 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
791
1517
|
if (inv.id==="H3") return "H3 \\u00b7 outcome renders "+s.outcomeItems+" item(s)";
|
|
792
1518
|
if (inv.id==="H5") return "H5 \\u00b7 claims "+s.filesClaimed+" files, renders "+s.filetreeRendered+" row(s)";
|
|
793
1519
|
if (inv.id==="H6") return "H6 \\u00b7 status='"+s.status+"', reason shown="+s.hasExplanation;
|
|
1520
|
+
if (inv.id==="H13" && s.hero && s.hero.expected) return "H13 \\u00b7 hero claims a="+s.hero.agents+" v="+s.hero.verified+" c="+s.hero.caught+" r="+s.hero.running+" \\u00b7 derived a="+s.hero.expected.agents+" v="+s.hero.expected.verified+" c="+s.hero.expected.caught+" r="+s.hero.expected.running;
|
|
1521
|
+
if (inv.id==="H14" && s.decisions) return "H14 \\u00b7 queue "+(s.decisions.present?("claims N="+s.decisions.n+", renders "+s.decisions.rows+" row(s)"):"absent")+" \\u00b7 derived "+s.decisions.expected+" decision(s)";
|
|
794
1522
|
return inv.desc;
|
|
795
1523
|
}
|
|
796
1524
|
|
|
@@ -803,19 +1531,19 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
803
1531
|
var d = frameDoc();
|
|
804
1532
|
if (r.verdict==="BLOCKED"){
|
|
805
1533
|
// brief, dim — honest "nothing to observe", no theatrics
|
|
806
|
-
clSet(inv.id, "BLOCKED", inv.title+" \\
|
|
807
|
-
showCaption("\\
|
|
1534
|
+
clSet(inv.id, "BLOCKED", inv.title+" \\u00b7 n/a");
|
|
1535
|
+
showCaption("\\u2298 " + narrate(inv, s, r));
|
|
808
1536
|
return sleep(Math.round(KEY_HOLD*0.45)).then(function(){ showCaption(null); return r; });
|
|
809
1537
|
}
|
|
810
1538
|
var el = targetFor(inv.id, d);
|
|
811
1539
|
if (el && el.scrollIntoView) try { el.scrollIntoView({block:"center"}); } catch(e){}
|
|
812
1540
|
return sleep(120).then(function(){
|
|
813
1541
|
drawRing(el);
|
|
814
|
-
showCaption("\\
|
|
1542
|
+
showCaption("\\u25CF " + narrate(inv, s, r));
|
|
815
1543
|
return sleep(KEY_HOLD);
|
|
816
1544
|
}).then(function(){
|
|
817
1545
|
removeRing();
|
|
818
|
-
var verdictWord = r.verdict==="FAIL" ? " \\
|
|
1546
|
+
var verdictWord = r.verdict==="FAIL" ? " \\u203A \\u2715 FAIL" : " \\u203A \\u2713 PASS";
|
|
819
1547
|
showCaption((r.verdict==="FAIL"?"\\u26a0 ":"") + narrate(inv, s, r) + verdictWord);
|
|
820
1548
|
clSet(inv.id, r.verdict, inv.title);
|
|
821
1549
|
return sleep(Math.round(KEY_HOLD*0.7));
|
|
@@ -829,7 +1557,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
829
1557
|
idx = i;
|
|
830
1558
|
markStep(i, null);
|
|
831
1559
|
clReset();
|
|
832
|
-
document.getElementById("stage-title").innerHTML = "case " + (i+1) + "/" + FIXTURES.length + " · <strong>" + (fx.probe?"
|
|
1560
|
+
document.getElementById("stage-title").innerHTML = "case " + (i+1) + "/" + FIXTURES.length + " · <strong>" + (fx.probe?"probe \\u00b7 ":"") + fx.label + "</strong> <span class=\\"dim\\">/ "+fx.id+"</span>";
|
|
833
1561
|
document.getElementById("framestatus").innerHTML = "<span class=\\"sub\\" style=\\"color:var(--fg-40)\\">opening case\\u2026</span>";
|
|
834
1562
|
setStageVerdict(null);
|
|
835
1563
|
document.getElementById("prog").style.width = ((i)/FIXTURES.length*100)+"%";
|
|
@@ -839,8 +1567,15 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
839
1567
|
return sleep(120);
|
|
840
1568
|
}).then(function(){
|
|
841
1569
|
stageClean();
|
|
842
|
-
showCaption("
|
|
843
|
-
|
|
1570
|
+
showCaption("open case \\u203A showD('"+fx.label+"')");
|
|
1571
|
+
// S20 · lens fixtures replay through the page's own applyLens — the
|
|
1572
|
+
// human watches the recomposed surface, not the default one.
|
|
1573
|
+
try {
|
|
1574
|
+
var w = frameWin();
|
|
1575
|
+
if (fx.lens && typeof w.applyLens === "function") w.applyLens(fx.lens);
|
|
1576
|
+
else pinLensDefault();
|
|
1577
|
+
w.showD(fx.sid || fx.id);
|
|
1578
|
+
} catch(e){}
|
|
844
1579
|
return sleep(Math.round(KEY_HOLD*0.7));
|
|
845
1580
|
}).then(function(){
|
|
846
1581
|
showCaption(null);
|
|
@@ -863,15 +1598,16 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
863
1598
|
|
|
864
1599
|
function jumpTo(i){ playing=false; openReplay(); playFixture(i); }
|
|
865
1600
|
function advance(){
|
|
866
|
-
if (idx+1 >= FIXTURES.length){ playing=false; document.getElementById("play").innerHTML="&#
|
|
1601
|
+
if (idx+1 >= FIXTURES.length){ playing=false; document.getElementById("play").innerHTML="▶ Replay again"; finishReplay(); return; }
|
|
867
1602
|
playFixture(idx+1).then(function(){ if(playing) setTimeout(advance, DWELL); });
|
|
868
1603
|
}
|
|
869
1604
|
function startReplay(){
|
|
870
1605
|
replayResults = []; idx = 0; playing = true;
|
|
871
|
-
document.getElementById("play").innerHTML = "&#
|
|
1606
|
+
document.getElementById("play").innerHTML = "▮▮ Pause";
|
|
872
1607
|
playFixture(0).then(function(){ if(playing) setTimeout(advance, DWELL); });
|
|
873
1608
|
}
|
|
874
1609
|
function finishReplay(){
|
|
1610
|
+
restoreLens();
|
|
875
1611
|
var c = classify(replayResults);
|
|
876
1612
|
window.__verify_replay = { done:true, results:replayResults, summary:c };
|
|
877
1613
|
}
|
|
@@ -887,11 +1623,11 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
887
1623
|
var recStream=null, recorder=null, recChunks=[], recClips=[];
|
|
888
1624
|
document.getElementById("record").addEventListener("click", function(){
|
|
889
1625
|
var btn = this;
|
|
890
|
-
if (recStream){ stopRecording(); btn.innerHTML="&#
|
|
1626
|
+
if (recStream){ stopRecording(); btn.innerHTML="● Record clips"; return; }
|
|
891
1627
|
navigator.mediaDevices.getDisplayMedia({ video:{frameRate:30}, audio:false, preferCurrentTab:true })
|
|
892
1628
|
.then(function(stream){
|
|
893
|
-
recStream = stream; btn.innerHTML="&#
|
|
894
|
-
stream.getVideoTracks()[0].addEventListener("ended", function(){ stopRecording(); btn.innerHTML="&#
|
|
1629
|
+
recStream = stream; btn.innerHTML="● Recording \\u00b7 stop";
|
|
1630
|
+
stream.getVideoTracks()[0].addEventListener("ended", function(){ stopRecording(); btn.innerHTML="● Record clips"; });
|
|
895
1631
|
// record one continuous clip across the whole replay (simple + reliable);
|
|
896
1632
|
// download offered on stop.
|
|
897
1633
|
recChunks = [];
|
|
@@ -905,7 +1641,7 @@ export const VERIFY_HTML = `<!doctype html>
|
|
|
905
1641
|
recorder.start(200);
|
|
906
1642
|
startReplay();
|
|
907
1643
|
})
|
|
908
|
-
.catch(function(e){ btn.innerHTML="&#
|
|
1644
|
+
.catch(function(e){ btn.innerHTML="● Record clips"; alert("recording cancelled: "+e); });
|
|
909
1645
|
});
|
|
910
1646
|
function stopRecording(){
|
|
911
1647
|
try { if(recorder && recorder.state!=="inactive") recorder.stop(); } catch(e){}
|