codehere 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/application/use-cases/domain-trust.js +110 -0
- package/dist/application/use-cases/generate-final-report.js +84 -5
- package/dist/application/use-cases/generate-narration.js +20 -3
- package/dist/application/use-cases/generate-thread-title.js +4 -0
- package/dist/application/use-cases/thread-export.js +155 -0
- package/dist/application/use-cases/verify-claim.js +274 -0
- package/dist/application/use-cases/write-pass.js +277 -0
- package/dist/index.js +218 -19
- package/dist/infrastructure/completion/bash-completion.js +2 -2
- package/dist/infrastructure/config/config-manager.js +1 -1
- package/dist/infrastructure/context/adapters/drawio-source.js +157 -0
- package/dist/infrastructure/context/adapters/figma-source.js +163 -0
- package/dist/infrastructure/context/adapters/github-issue-source.js +159 -0
- package/dist/infrastructure/context/adapters/linear-source.js +190 -0
- package/dist/infrastructure/context/context-resolver.js +144 -0
- package/dist/infrastructure/context/context-source.js +114 -0
- package/dist/infrastructure/context/oauth-flow.js +207 -0
- package/dist/infrastructure/context/oauth-store.js +119 -0
- package/dist/infrastructure/context/registry-bootstrap.js +99 -0
- package/dist/infrastructure/context/write-pass-resolver.js +106 -0
- package/dist/infrastructure/cost/budget-config.js +1 -1
- package/dist/infrastructure/cost/spend-aggregator.js +5 -5
- package/dist/infrastructure/hooks/claude-sdk-hook.js +79 -59
- package/dist/infrastructure/hooks/session-analyzer.js +65 -8
- package/dist/infrastructure/observability/execution-tracer.js +0 -5
- package/dist/infrastructure/orchestration/aider-adapter.js +45 -17
- package/dist/infrastructure/orchestration/artifacts-extractor.js +39 -5
- package/dist/infrastructure/orchestration/cassette-recorder.js +120 -0
- package/dist/infrastructure/orchestration/claude-code-adapter.js +33 -4
- package/dist/infrastructure/orchestration/codex-adapter.js +157 -19
- package/dist/infrastructure/orchestration/context-handoff.js +4 -3
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +99 -0
- package/dist/infrastructure/orchestration/control/control-surface.js +15 -0
- package/dist/infrastructure/orchestration/gemini-adapter.js +15 -17
- package/dist/infrastructure/orchestration/graphify-context.js +6 -13
- package/dist/infrastructure/orchestration/memory-injector.js +11 -16
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +233 -0
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +73 -0
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +83 -0
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +37 -0
- package/dist/infrastructure/orchestration/opencode-adapter.js +23 -3
- package/dist/infrastructure/orchestration/progress-aggregator.js +178 -5
- package/dist/infrastructure/orchestration/session-manager.js +142 -29
- package/dist/infrastructure/orchestration/session-persistence.js +139 -30
- package/dist/infrastructure/orchestration/thread-key-store.js +127 -0
- package/dist/infrastructure/orchestration/write-pass-store.js +46 -0
- package/dist/infrastructure/protocols/cloud-sync.js +10 -11
- package/dist/infrastructure/protocols/mcp-server-impl.js +1295 -17
- package/dist/infrastructure/protocols/thread-phase.js +105 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +3768 -75
- package/dist/infrastructure/protocols/ui-page.js +12 -3299
- package/dist/infrastructure/protocols/verify-page.js +941 -0
- package/dist/infrastructure/security/trust-scorer.js +76 -1
- package/dist/infrastructure/storage/cross-tool-agents.js +20 -3
- package/dist/infrastructure/storage/project-thread-migration.js +14 -9
- package/dist/infrastructure/storage/project-thread-reader.js +11 -1
- package/dist/infrastructure/storage/project-thread-title-backfill.js +7 -3
- package/dist/monitoring.js +1 -1
- package/dist/presentation/cli/commands/audit-command.js +126 -0
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/config-command.js +2 -2
- package/dist/presentation/cli/commands/context-source-command.js +198 -0
- package/dist/presentation/cli/commands/help-command.js +51 -1
- package/dist/presentation/cli/commands/init-command.js +5 -4
- package/dist/presentation/cli/commands/memory-command.js +29 -15
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/repo-map-builder.js +4 -4
- package/dist/presentation/cli/commands/report-command.js +6 -4
- package/dist/presentation/cli/commands/setup-command.js +71 -18
- package/dist/presentation/cli/commands/status-command.js +0 -4
- package/dist/presentation/cli/commands/thread-command.js +288 -0
- package/dist/presentation/cli/commands/verify-command.js +233 -0
- package/dist/presentation/cli/error-display.js +1 -1
- package/dist/presentation/cli/repl.js +52 -2
- package/dist/presentation/cli/task-deliverable.js +127 -0
- package/dist/testing/cassette.js +125 -0
- package/dist/testing/replay-adapter.js +166 -0
- package/dist/ui.js +54 -29
- package/dist/utils/typo-guard.js +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,941 @@
|
|
|
1
|
+
// Copyright 2026
|
|
2
|
+
/**
|
|
3
|
+
* /verify · verify-at-the-surface dashboard (the Claude-team way).
|
|
4
|
+
*
|
|
5
|
+
* Modeled on Anthropic's how-we-claude-code/phase-3-verify harness, adapted to
|
|
6
|
+
* codehere's reality: the product isn't React components, it's a server-rendered
|
|
7
|
+
* page. So the "unit × fixture" matrix becomes "honesty-invariant × seeded
|
|
8
|
+
* session", and the surface we observe is the REAL product page (`/`) loaded in
|
|
9
|
+
* a same-origin iframe — never a clone. We drive it via the real showD(), read
|
|
10
|
+
* the data-verify-* DOM contract back, and compute PASS / FAIL / BLOCKED.
|
|
11
|
+
*
|
|
12
|
+
* - window.__verify.manifest() — discover every invariant × fixture
|
|
13
|
+
* - window.__verify.runAll() — drive the whole matrix, return structured results
|
|
14
|
+
* - window.__verify.current() — the last single result (what replay is showing)
|
|
15
|
+
* Agent, human (Run all), and CI (scripts/verify.mjs) all read this one handle.
|
|
16
|
+
*
|
|
17
|
+
* The probe fixture (demo-probe-lie-v1) is DESIGNED to fail H5: its outcome
|
|
18
|
+
* headline claims 3 files but renders 1. A green there means the harness is
|
|
19
|
+
* blind, so CI treats a non-failing probe as a hard failure.
|
|
20
|
+
*
|
|
21
|
+
* Replay mode (?mode=replay) drives the real iframe in slow motion with a
|
|
22
|
+
* highlight ring + caption (the visibleAct pattern) and can record one .webm
|
|
23
|
+
* per fixture in-browser, or be driven headless by scripts/record-replay.mjs.
|
|
24
|
+
*/
|
|
25
|
+
export const VERIFY_HTML = `<!doctype html>
|
|
26
|
+
<html lang="en">
|
|
27
|
+
<head>
|
|
28
|
+
<meta charset="utf-8" />
|
|
29
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
30
|
+
<title>codehere · /verify</title>
|
|
31
|
+
<style>
|
|
32
|
+
:root{
|
|
33
|
+
--bg:hsl(0 0% 3.9%); --bg-1:hsl(0 0% 6%); --bg-2:hsl(0 0% 9%);
|
|
34
|
+
--fg:hsl(0 0% 98%); --fg-70:rgba(255,255,255,.70); --fg-50:rgba(255,255,255,.50);
|
|
35
|
+
--fg-40:rgba(255,255,255,.40); --fg-30:rgba(255,255,255,.30);
|
|
36
|
+
--border:rgba(255,255,255,.10); --border-soft:rgba(255,255,255,.06);
|
|
37
|
+
--accent:#ff9a76; --accent-wash:rgba(255,154,118,.10);
|
|
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
|
+
}
|
|
43
|
+
*{box-sizing:border-box}
|
|
44
|
+
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
|
+
background-image:radial-gradient(circle,rgba(74,144,226,.045) 1px,transparent 1px);background-size:24px 24px;}
|
|
46
|
+
a{color:inherit}
|
|
47
|
+
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:15px;font-weight:700;display:inline-flex;align-items:center;gap:7px;letter-spacing:-.01em}
|
|
49
|
+
.logo .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent);animation:pulse 2s infinite}
|
|
50
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
|
51
|
+
.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
|
+
.route{color:var(--fg-40);font-family:var(--mono);font-size:12px}
|
|
53
|
+
.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:rgba(40,200,64,.3);color:var(--allow)}
|
|
55
|
+
.spacer{flex:1}
|
|
56
|
+
.hint{color:var(--fg-40);font-size:11.5px}
|
|
57
|
+
.hint code{font-family:var(--mono);color:var(--info)}
|
|
58
|
+
.wrap{max-width:1180px;margin:0 auto;padding:22px 20px 80px}
|
|
59
|
+
.actions{display:flex;align-items:center;gap:12px;margin:18px 0 8px;flex-wrap:wrap}
|
|
60
|
+
button.run{background:linear-gradient(135deg,#ff9a76,#ff7a4d);color:#1a0f08;border:0;font-weight:700;font-size:13px;padding:9px 18px;border-radius:var(--radius);cursor:pointer;box-shadow:0 0 0 1px rgba(255,154,118,.3),0 8px 24px -8px rgba(255,154,118,.5)}
|
|
61
|
+
button.run:disabled{opacity:.6;cursor:default}
|
|
62
|
+
.linkbtn{color:var(--fg-70);text-decoration:none;border:1px solid var(--border);padding:8px 14px;border-radius:var(--radius);font-size:12.5px;cursor:pointer;background:transparent}
|
|
63
|
+
.linkbtn:hover{border-color:var(--accent);color:var(--fg)}
|
|
64
|
+
.tally{display:inline-flex;gap:14px;align-items:center;font-family:var(--mono);font-size:13px;margin-left:6px}
|
|
65
|
+
.tally .p{color:var(--allow)} .tally .f{color:var(--block)} .tally .b{color:var(--fg-40)} .tally .t{color:var(--fg-50)}
|
|
66
|
+
.verdict-line{font-size:12px;margin:4px 0 0}
|
|
67
|
+
.verdict-line.green{color:var(--allow)} .verdict-line.red{color:var(--block)}
|
|
68
|
+
.legend{display:flex;gap:18px;margin:8px 0 0;font-size:11.5px;color:var(--fg-50);flex-wrap:wrap}
|
|
69
|
+
.legend b{font-family:var(--mono)}
|
|
70
|
+
h2.section{font-size:11px;text-transform:uppercase;letter-spacing:.12em;color:var(--fg-40);margin:26px 0 10px;font-weight:600}
|
|
71
|
+
.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:11.5px;color:var(--fg-70)}
|
|
73
|
+
.vchip code{font-family:var(--mono);color:var(--accent)}
|
|
74
|
+
table.matrix{width:100%;border-collapse:collapse;font-size:12.5px;border:1px solid var(--border-soft);border-radius:var(--radius);overflow:hidden}
|
|
75
|
+
table.matrix th,table.matrix td{padding:9px 11px;text-align:left;border-bottom:1px solid var(--border-soft)}
|
|
76
|
+
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
|
+
table.matrix tbody tr:hover{background:rgba(255,255,255,.02)}
|
|
78
|
+
.unit-id{font-family:var(--mono);color:var(--fg)}
|
|
79
|
+
.unit-desc{color:var(--fg-50);font-size:11.5px}
|
|
80
|
+
.needs{font-family:var(--mono);color:var(--fg-30);font-size:11px}
|
|
81
|
+
.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(40,200,64,.12);color:var(--allow)}
|
|
83
|
+
.cell.fail{background:rgba(255,95,87,.16);color:var(--block);box-shadow:0 0 0 1px rgba(255,95,87,.5)}
|
|
84
|
+
.cell.fail.expected{animation:flag 1.6s infinite}
|
|
85
|
+
.cell.blocked{background:rgba(255,255,255,.05);color:var(--fg-40)}
|
|
86
|
+
@keyframes flag{0%,100%{box-shadow:0 0 0 1px rgba(255,95,87,.5)}50%{box-shadow:0 0 0 1px rgba(255,95,87,1),0 0 12px rgba(255,95,87,.5)}}
|
|
87
|
+
.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(--block)}
|
|
89
|
+
.probe-row td{background:rgba(255,95,87,.04)}
|
|
90
|
+
.probe-tag{font-size:9.5px;color:var(--block);border:1px solid rgba(255,95,87,.4);border-radius:4px;padding:1px 5px;margin-left:8px;font-family:var(--mono)}
|
|
91
|
+
.note{color:var(--fg-40);font-size:11.5px;margin-top:8px}
|
|
92
|
+
.note code{font-family:var(--mono);color:var(--info)}
|
|
93
|
+
/* replay */
|
|
94
|
+
.grid2{display:grid;grid-template-columns:1.4fr 1fr;gap:18px;margin-top:6px}
|
|
95
|
+
.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(40,200,64,.4)}
|
|
97
|
+
.stage-wrap.v-fail{box-shadow:0 0 0 1px rgba(255,95,87,.55)}
|
|
98
|
+
.stage-wrap.v-blocked{box-shadow:0 0 0 1px rgba(255,255,255,.12)}
|
|
99
|
+
.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:#ff5f57}.tl-dot.y{background:#febc2e}.tl-dot.g{background:#28c840}
|
|
101
|
+
.tl-title{margin-left:8px;font-size:11.5px;color:var(--fg-70)}.tl-title .dim{color:var(--fg-30);font-family:var(--mono)}
|
|
102
|
+
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:11.5px;backdrop-filter:blur(6px);opacity:0;transition:opacity .2s}
|
|
104
|
+
.checklist.on{opacity:1}
|
|
105
|
+
.checklist .cl-h{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-40);margin-bottom:7px}
|
|
106
|
+
.checklist .cl-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;color:var(--fg-50);transition:color .2s}
|
|
107
|
+
.checklist .cl-row.run{color:var(--fg)}
|
|
108
|
+
.checklist .cl-row .cl-id{font-family:var(--mono);font-weight:700;width:22px}
|
|
109
|
+
.checklist .cl-row .cl-st{width:14px;text-align:center}
|
|
110
|
+
.checklist .cl-row.pass .cl-id{color:var(--allow)} .checklist .cl-row.fail .cl-id{color:var(--block)} .checklist .cl-row.blocked .cl-id{color:var(--fg-40)}
|
|
111
|
+
.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
|
+
@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:12.5px;color:var(--fg);font-family:var(--mono);white-space:nowrap;z-index:51;opacity:0;transition:opacity .2s}
|
|
114
|
+
.caption.on{opacity:1}
|
|
115
|
+
.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
|
+
.framestatus .badge{font-family:var(--mono);font-weight:700;padding:2px 9px;border-radius:5px}
|
|
117
|
+
.badge.pass{color:var(--allow);background:rgba(40,200,64,.12)} .badge.fail{color:var(--block);background:rgba(255,95,87,.14)} .badge.blocked{color:var(--fg-40);background:rgba(255,255,255,.05)}
|
|
118
|
+
.framestatus .issues{color:var(--block);font-size:11.5px}
|
|
119
|
+
.framestatus .issues.blk{color:var(--fg-40)}
|
|
120
|
+
.replay-side h3{font-size:12px;color:var(--fg-70);margin:0 0 10px}
|
|
121
|
+
.ctrlrow{display:flex;gap:8px;align-items:center;margin-bottom:12px;flex-wrap:wrap}
|
|
122
|
+
.progress{height:5px;background:var(--bg-2);border-radius:3px;overflow:hidden;margin:8px 0 14px}
|
|
123
|
+
.progress>div{height:100%;width:0;background:linear-gradient(90deg,#ff9a76,#4a90e2);transition:width .3s}
|
|
124
|
+
.steplist{font-size:12px}
|
|
125
|
+
.steprow{display:flex;align-items:center;gap:8px;padding:5px 7px;border-radius:6px;cursor:pointer}
|
|
126
|
+
.steprow.active{background:var(--accent-wash)}
|
|
127
|
+
.steprow .st{width:16px;text-align:center}
|
|
128
|
+
.steprow .nm{font-family:var(--mono);font-size:11.5px;color:var(--fg-70)}
|
|
129
|
+
.steprow.fail .nm{color:var(--block)}
|
|
130
|
+
.clipnote{margin-top:14px;font-size:11.5px;color:var(--fg-40)}
|
|
131
|
+
.clipnote code{font-family:var(--mono);color:var(--info)}
|
|
132
|
+
details.manifest{margin-top:26px;border:1px solid var(--border-soft);border-radius:var(--radius);padding:10px 14px}
|
|
133
|
+
details.manifest summary{cursor:pointer;color:var(--fg-50);font-size:12px}
|
|
134
|
+
details.manifest pre{font-family:var(--mono);font-size:11px;color:var(--fg-50);overflow:auto;margin:10px 0 0;max-height:340px}
|
|
135
|
+
.hidden{display:none !important}
|
|
136
|
+
</style>
|
|
137
|
+
</head>
|
|
138
|
+
<body data-verify-page="dashboard">
|
|
139
|
+
<header class="top">
|
|
140
|
+
<span class="logo"><span class="dot"></span><span class="name">codehere</span></span>
|
|
141
|
+
<span class="route">/verify</span>
|
|
142
|
+
<span class="pill local">● local · zero telemetry</span>
|
|
143
|
+
<span class="spacer"></span>
|
|
144
|
+
<span class="hint">agent handle: <code>window.__verify</code> — <code>__verify.manifest()</code> · <code>await __verify.runAll()</code></span>
|
|
145
|
+
</header>
|
|
146
|
+
|
|
147
|
+
<div class="wrap">
|
|
148
|
+
<div class="actions">
|
|
149
|
+
<button class="run" id="run-all">▶ Run all</button>
|
|
150
|
+
<button class="linkbtn" id="toggle-replay">▶ Replay all (slow-mo)</button>
|
|
151
|
+
<span class="tally" id="tally"><span class="t">not run yet — click Run all</span></span>
|
|
152
|
+
</div>
|
|
153
|
+
<div class="verdict-line" id="verdict-line"></div>
|
|
154
|
+
<div class="legend">
|
|
155
|
+
<span><b style="color:var(--allow)">PASS</b> observed & honest</span>
|
|
156
|
+
<span><b style="color:var(--block)">FAIL</b> observed & lying</span>
|
|
157
|
+
<span><b style="color:var(--fg-40)">BLOCKED</b> 0 units to observe — never a silent green</span>
|
|
158
|
+
<span><b>🔍 probe</b> designed-to-fail self-test</span>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<h2 class="section">Pluggable verifiers</h2>
|
|
162
|
+
<div class="verifiers">
|
|
163
|
+
<span class="vchip"><code>contract</code> — reads data-verify-* off the rendered surface</span>
|
|
164
|
+
<span class="vchip"><code>invariants</code> — runs each honesty predicate against the DOM</span>
|
|
165
|
+
<span class="vchip"><code>counts</code> — claimed numbers must equal rendered elements</span>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<h2 class="section">Matrix · honesty invariant × seeded session</h2>
|
|
169
|
+
<table class="matrix" id="matrix"><thead></thead><tbody></tbody></table>
|
|
170
|
+
<p class="note" id="probe-note"></p>
|
|
171
|
+
|
|
172
|
+
<h2 class="section">Feature coverage · v0 → v1 MVP — does each PRODUCT_STATE claim actually render?</h2>
|
|
173
|
+
<div class="actions">
|
|
174
|
+
<button class="run" id="run-cov">▶ Run coverage</button>
|
|
175
|
+
<span class="tally" id="cov-tally"><span class="t">not run yet</span></span>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="verdict-line" id="cov-verdict"></div>
|
|
178
|
+
<div class="legend">
|
|
179
|
+
<span><b style="color:var(--allow)">OBSERVED</b> claimed shipped & rendered</span>
|
|
180
|
+
<span><b style="color:var(--block)">MISSING</b> claimed shipped, NOT rendered (doc lies)</span>
|
|
181
|
+
<span><b style="color:var(--warn)">PARTIAL-GAP</b> 🟡 not yet visible</span>
|
|
182
|
+
<span><b style="color:var(--fg-40)">SKIP</b> roadmap / future (correctly absent)</span>
|
|
183
|
+
</div>
|
|
184
|
+
<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>
|
|
185
|
+
|
|
186
|
+
<div id="replay" class="hidden">
|
|
187
|
+
<h2 class="section">Replay · drive the real UI in slow motion</h2>
|
|
188
|
+
<div class="grid2">
|
|
189
|
+
<div class="stage-wrap" id="stage-wrap">
|
|
190
|
+
<div class="titlebar">
|
|
191
|
+
<span class="tl-dot r"></span><span class="tl-dot y"></span><span class="tl-dot g"></span>
|
|
192
|
+
<span class="tl-title" id="stage-title">feature · <strong>session thread</strong> <span class="dim">/ —</span></span>
|
|
193
|
+
</div>
|
|
194
|
+
<iframe id="surface" title="codehere product surface" src="/"></iframe>
|
|
195
|
+
<div class="checklist" id="checklist"></div>
|
|
196
|
+
<div class="caption" id="caption"></div>
|
|
197
|
+
<div class="framestatus" id="framestatus"><span class="sub" style="color:var(--fg-40)">press Play</span></div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="replay-side">
|
|
200
|
+
<h3>Replay controls</h3>
|
|
201
|
+
<div class="ctrlrow">
|
|
202
|
+
<button class="linkbtn" id="play">▶ Play</button>
|
|
203
|
+
<button class="linkbtn" id="skip">⏭ Skip</button>
|
|
204
|
+
<button class="linkbtn" id="record">⏺ Record clips</button>
|
|
205
|
+
<span class="hint" id="dwell-label">dwell 1500ms</span>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="progress"><div id="prog"></div></div>
|
|
208
|
+
<div class="steplist" id="steplist"></div>
|
|
209
|
+
<div class="clipnote">Each fixture → 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
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<details class="manifest">
|
|
215
|
+
<summary>Raw manifest (what an agent / CI sees — same truth as this page)</summary>
|
|
216
|
+
<pre id="manifest-json"></pre>
|
|
217
|
+
</details>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<script>
|
|
221
|
+
(function(){
|
|
222
|
+
"use strict";
|
|
223
|
+
|
|
224
|
+
/* -------- fixtures: the seeded sessions (the product's real surfaces) -------- */
|
|
225
|
+
var FIXTURES = [
|
|
226
|
+
{ id:"demo-rate-limit-shipped-v0.9", label:"rate-limit-shipped" },
|
|
227
|
+
{ id:"demo-recovery-test-failed-v0.9", label:"recovery-failed" },
|
|
228
|
+
{ id:"demo-crash-orphaned-mid-write-v0.9",label:"crash-orphaned" },
|
|
229
|
+
{ id:"demo-hook-block-bulk-rm-v0.9", label:"hook-block-rm" },
|
|
230
|
+
{ id:"demo-stalled-opencode-hung-v1", label:"stalled-hung" },
|
|
231
|
+
{ id:"demo-non-git-honest-v1", label:"non-git-honest" },
|
|
232
|
+
{ id:"demo-running-streaming-v0.9", label:"running-stream" },
|
|
233
|
+
{ id:"demo-probe-lie-v1", label:"probe-lie", probe:true, expectFail:"H5" }
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
/* -------- invariants: the honesty contracts (the "units") --------
|
|
237
|
+
needs(s) -> can this invariant observe anything here? (else BLOCKED)
|
|
238
|
+
check(s) -> [] when honest, or violation strings when lying (FAIL) */
|
|
239
|
+
var INVARIANTS = [
|
|
240
|
+
{ id:"H1", title:"no-verified-without-git", needs:"verifier",
|
|
241
|
+
desc:"verdict is not 'verified' when git is untracked",
|
|
242
|
+
can:function(s){ return s.verifiers.length>0; },
|
|
243
|
+
check:function(s){ return s.verifiers.filter(function(v){ return v.gittracked==="false" && v.verdict==="verified"; }).map(function(){ return "verifier verdict=verified while git untracked (overclaim)"; }); } },
|
|
244
|
+
{ id:"H2", title:"say-never-bare", needs:"say",
|
|
245
|
+
desc:"every reasoning bubble has real words, not the bare label",
|
|
246
|
+
can:function(s){ return s.says.length>0; },
|
|
247
|
+
check:function(s){ return s.says.filter(function(x){ return x.len===0 || x.text.toLowerCase()==="say"; }).map(function(){ return "bare 'say' bubble with no agent words"; }); } },
|
|
248
|
+
{ id:"H3", title:"no-empty-outcome", needs:"terminal outcome",
|
|
249
|
+
desc:"a finished outcome panel renders observable content, not an empty box",
|
|
250
|
+
can:function(s){ return s.hasOutcome && s.terminal; },
|
|
251
|
+
check:function(s){ return s.outcomeItems>0 ? [] : ["terminal Outcome panel rendered 0 items (empty container)"]; } },
|
|
252
|
+
{ id:"H5", title:"counts-consistent", needs:"outcome+files",
|
|
253
|
+
desc:"the 'N files changed' headline equals the rendered file rows",
|
|
254
|
+
can:function(s){ return s.hasOutcome && s.filesClaimed>0 && s.hasFiletree; },
|
|
255
|
+
check:function(s){ return s.filesClaimed===s.filetreeRendered ? [] : ["claims "+s.filesClaimed+" files changed but renders "+s.filetreeRendered+" file row(s)"]; } },
|
|
256
|
+
{ id:"H6", title:"fail-has-honest-reason", needs:"terminal-fail",
|
|
257
|
+
desc:"a failed / killed / crashed run shows a plain reason, never a bare dot",
|
|
258
|
+
can:function(s){ return s.terminalFail; },
|
|
259
|
+
check:function(s){ return s.hasExplanation ? [] : ["failed session shows no recovery / crash / stall explanation"]; } }
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
var PROTO_VERSION = "codehere-verify/1";
|
|
263
|
+
|
|
264
|
+
/* -------- feature coverage: every granular PRODUCT_STATE claim, v0->v1 --------
|
|
265
|
+
Turns the ~150-row inventory's claims into watchable checks against the REAL
|
|
266
|
+
surface. status drives the verdict:
|
|
267
|
+
shipped (✅/🟢) -> element MUST render -> OBSERVED, else MISSING (the claim is a lie)
|
|
268
|
+
partial (🟡) -> render if present -> OBSERVED, else PARTIAL-GAP (honest, not a lie)
|
|
269
|
+
roadmap (⛔) -> SKIP-ROADMAP (planned, not built — correctly absent)
|
|
270
|
+
future (🟦/cut) -> SKIP-FUTURE (deferred by design / cut)
|
|
271
|
+
surface: 'list' = dashboard · 'detail' = opens demo-rate-limit-shipped-v0.9.
|
|
272
|
+
sel = real CSS selector confirmed against the live DOM (not invented). */
|
|
273
|
+
var COV_FIXTURE = "demo-rate-limit-shipped-v0.9";
|
|
274
|
+
var FEATURES = [
|
|
275
|
+
{ 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
|
+
{ id:"F-BUDGET-WARN-BANNER", name:"Budget warning banner", surface:"list", sel:".budget-banner", status:"partial", tier:"v0.6" },
|
|
278
|
+
{ id:"F-CARD-AGENT-BORDER", name:"Per-vendor left-border color on task-card", surface:"list", sel:".task-card.agent-claude-code", status:"shipped", tier:"v0.9" },
|
|
279
|
+
{ id:"F-CARD-CLICK-DETAIL", name:"Click card → detail view", surface:"list", sel:".task-card", status:"shipped", tier:"v0.6" },
|
|
280
|
+
{ id:"F-CARD-OUTCOME-CHIP", name:"Outcome chip right-aligned", surface:"list", sel:".card-outcome.ok", status:"shipped", tier:"v0.7" },
|
|
281
|
+
{ id:"F-CUSTOM-VIEW-BANNER", name:"Custom view banner", surface:"list", sel:".custom-view-banner", status:"shipped", tier:"v0.8" },
|
|
282
|
+
{ 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
|
+
{ id:"F-FILTER-PILLS", name:"All/Running/Done/Failed segmented filters", surface:"list", sel:".filter-tab", status:"shipped", tier:"v0.7" },
|
|
285
|
+
{ id:"F-INDEXING-BANNER", name:"First-run indexing banner", surface:"list", sel:".indexing-banner", status:"partial", tier:"v0.7" },
|
|
286
|
+
{ id:"F-LOCAL-PILL", name:"Local-first / zero-telemetry pill", surface:"list", sel:".local-pill", status:"shipped", tier:"v0.7" },
|
|
287
|
+
{ id:"F-LOGO", name:"Brand wordmark", surface:"list", sel:".logo .name", status:"shipped", tier:"v0.7" },
|
|
288
|
+
{ id:"F-NEW-TASK-BTN", name:"+ New Task primary button", surface:"list", sel:".new-btn", status:"shipped", tier:"v0.7" },
|
|
289
|
+
{ id:"F-OBSERVED-THREAD", name:"Observed session as thread", surface:"list", sel:".obs-row", status:"shipped", tier:"v0.7" },
|
|
290
|
+
{ id:"F-OSS-FOOTER", name:"OSS footer", surface:"list", sel:".oss-footer", status:"shipped", tier:"v0.7" },
|
|
291
|
+
{ id:"F-PERSONA-SELECTOR", name:"Header role selector", surface:"list", sel:".persona-select", status:"shipped", tier:"v0.7" },
|
|
292
|
+
{ id:"F-PIN-DEMO", name:"Demo-shape sessions pinned to FEATURED", surface:"list", sel:".task-card.tc-pinned", status:"shipped", tier:"v0.9" },
|
|
293
|
+
{ id:"F-PORT-FAILOVER-NOTICE", name:"Port-collision UI surface", surface:"list", sel:".failover-banner", status:"shipped", tier:"v0.7" },
|
|
294
|
+
{ id:"F-RESHAPE-BTN", name:"Reshape view button", surface:"list", sel:".reshape-btn", status:"shipped", tier:"v0.8" },
|
|
295
|
+
{ id:"F-SETTINGS-COG", name:"Settings drawer trigger", surface:"list", sel:".settings-btn", status:"shipped", tier:"v0.8" },
|
|
296
|
+
{ id:"F-SHARE-BTN", name:"Share thread button", surface:"list", sel:".share-btn-hdr", status:"shipped", tier:"v0.8" },
|
|
297
|
+
{ id:"F-SQUAD-ROW", name:"Agent picker chips + presence dots", surface:"list", sel:".squad-row", status:"shipped", tier:"v0.7" },
|
|
298
|
+
{ id:"F-TASK-CARD", name:"Task card", surface:"list", sel:".task-card", status:"shipped", tier:"v0.7" },
|
|
299
|
+
{ id:"F-TIME-GAP-PILL", name:"Brand pill + relative-time sub", surface:"list", sel:".time-gap", status:"shipped", tier:"v0.7" },
|
|
300
|
+
{ id:"F-WORKTREE-ALERT", name:"Worktree alert", surface:"list", sel:".worktree-alert", status:"partial", tier:"v0.7" },
|
|
301
|
+
{ id:"M-OVERLAY-LINEAR", name:"Overlay-renderer Linear", surface:"list", sel:".or-overlay", status:"shipped", tier:"v0.9" },
|
|
302
|
+
{ id:"M-PRIMITIVES-NOTE", name:"Primitives note", surface:"list", sel:".primitives-note .prims .p", status:"shipped", tier:"v0.8" },
|
|
303
|
+
{ id:"M-RESHAPE-AGENT-COMPOSE", name:"Agent-compose animation", surface:"list", sel:".agent-compose .line", status:"shipped", tier:"v0.8" },
|
|
304
|
+
{ id:"M-SETTINGS-DRAWER", name:"Settings drawer", surface:"list", sel:".drawer", status:"shipped", tier:"v0.8" },
|
|
305
|
+
{ 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
|
+
{ id:"P-AB-BADGE", name:"Agent identity badge", surface:"list", sel:".ab", status:"shipped", tier:"v0.6" },
|
|
308
|
+
{ id:"P-STATUS-PILL", name:"Per-status pill", surface:"list", sel:".status-icon.running", status:"shipped", tier:"v0.7" },
|
|
309
|
+
{ 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
|
+
{ id:"F-DETAIL-KICKER", name:"Detail kicker + failover context", surface:"detail", sel:".detail-kicker", status:"shipped", tier:"v0.7" },
|
|
311
|
+
{ id:"F-OUTCOME-PANEL", name:"Outcome section (files/steps/Final Report)", surface:"detail", sel:"[data-verify-unit='outcome']", status:"shipped", tier:"v0.7" },
|
|
312
|
+
{ id:"F-SHIMMER-TITLE", name:"Animated gradient title", surface:"detail", sel:".detail-title", status:"shipped", tier:"v0.7" },
|
|
313
|
+
{ id:"P-CODE-CARD", name:"Inline syntax-tinted code preview", surface:"detail", sel:".code-card", status:"shipped", tier:"v0.7" },
|
|
314
|
+
{ id:"P-CTX-URL-CHIP", name:"Clickable context-source URL preview chip", surface:"detail", sel:".ctx-url-chip", status:"shipped", tier:"v0.8" },
|
|
315
|
+
{ id:"P-VERIFIED-BLOCK", name:"Centered verification celebration card", surface:"detail", sel:".verified-block", status:"shipped", tier:"v0.9" },
|
|
316
|
+
{ id:"P-WRITE-PASS", name:"Write-pass inline card (blue-accented)", surface:"detail", sel:".write-pass .head + h4", status:"shipped", tier:"v0.9" },
|
|
317
|
+
{ 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
|
+
{ 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
|
+
{ 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
|
+
{ 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,.verifier-panel.partial", status:"shipped", tier:"v0.7", fixture:"demo-non-git-honest-v1" },
|
|
322
|
+
{ id:"F-CONVERSATION", name:"multi-speaker conversation", surface:"detail", sel:".conv-turn,.turn,.speaker", status:"shipped", tier:"v0.7", fixture:"demo-rate-limit-shipped-v0.9" },
|
|
323
|
+
{ 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
|
+
{ 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" },
|
|
326
|
+
{ 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
|
+
{ 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" },
|
|
329
|
+
{ 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" },
|
|
330
|
+
{ 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" },
|
|
331
|
+
{ 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
|
+
{ id:"M-OVERLAY-FIGMA", name:"overlay-renderer · Figma", surface:"detail", sel:".overlay-figma", status:"future", tier:"v1.0" },
|
|
333
|
+
{ id:"M-OVERLAY-DRAWIO", name:"overlay-renderer · draw.io", surface:"detail", sel:".overlay-drawio", status:"future", tier:"v1.0" },
|
|
334
|
+
{ id:"V1-MULTITASK", name:"multi-task projects · parent/child", surface:"list", sel:".project-rollup", status:"roadmap", tier:"v1.0" },
|
|
335
|
+
{ id:"V1-FANOUT-ALL", name:"@all fanout to ready adapters", surface:"list", sel:".fanout-all", status:"roadmap", tier:"v1.0" },
|
|
336
|
+
{ id:"F-AGENT-SWITCHER", name:"'reply as' agent picker", surface:"detail", sel:".agent-switcher", status:"future", tier:"cut" }
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
/* -------- read the data-verify-* contract off the real surface -------- */
|
|
340
|
+
function readSurface(doc){
|
|
341
|
+
var dv = doc.getElementById("task-detail-view");
|
|
342
|
+
var visible = dv && dv.style.display !== "none";
|
|
343
|
+
var meta = doc.querySelector('[data-verify-unit="detail"]');
|
|
344
|
+
var outcome = doc.querySelector('[data-verify-unit="outcome"]');
|
|
345
|
+
var filetree = doc.querySelector('[data-verify-unit="filetree"]');
|
|
346
|
+
var verifiers = Array.prototype.slice.call(doc.querySelectorAll('[data-verify-unit="verifier"]')).map(function(el){
|
|
347
|
+
return { verdict: el.getAttribute("data-verify-verdict"), gittracked: el.getAttribute("data-verify-gittracked") };
|
|
348
|
+
});
|
|
349
|
+
var says = Array.prototype.slice.call(doc.querySelectorAll('.tl-say')).map(function(el){
|
|
350
|
+
var t = (el.querySelector(".tl-say-text") ? el.querySelector(".tl-say-text").textContent : "") || "";
|
|
351
|
+
t = t.replace(/^\\s+|\\s+$/g, "");
|
|
352
|
+
return { len:t.length, text:t };
|
|
353
|
+
});
|
|
354
|
+
var found = !!(meta && visible);
|
|
355
|
+
return {
|
|
356
|
+
found: found,
|
|
357
|
+
status: meta ? meta.getAttribute("data-verify-status") : null,
|
|
358
|
+
terminalFail: meta ? meta.getAttribute("data-verify-terminal-fail")==="true" : false,
|
|
359
|
+
hasExplanation: meta ? meta.getAttribute("data-verify-has-explanation")==="true" : false,
|
|
360
|
+
hasOutcome: !!outcome,
|
|
361
|
+
outcomeItems: outcome ? outcome.querySelectorAll(".outcome-item").length : 0,
|
|
362
|
+
filesClaimed: outcome ? Number(outcome.getAttribute("data-verify-files-claimed")||0) : 0,
|
|
363
|
+
terminal: outcome ? outcome.getAttribute("data-verify-terminal")==="true" : false,
|
|
364
|
+
hasReport: outcome ? outcome.getAttribute("data-verify-has-report")==="true" : false,
|
|
365
|
+
steps: outcome ? Number(outcome.getAttribute("data-verify-steps")||0) : 0,
|
|
366
|
+
hasFiletree: !!filetree,
|
|
367
|
+
filetreeRendered: filetree ? Number(filetree.getAttribute("data-verify-rendered")||0) : 0,
|
|
368
|
+
verifiers: verifiers,
|
|
369
|
+
says: says
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* -------- iframe surface plumbing -------- */
|
|
374
|
+
var iframe = document.getElementById("surface");
|
|
375
|
+
function frameWin(){ return iframe.contentWindow; }
|
|
376
|
+
function frameDoc(){ return iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); }
|
|
377
|
+
|
|
378
|
+
function sleep(ms){ return new Promise(function(r){ setTimeout(r, ms); }); }
|
|
379
|
+
|
|
380
|
+
// Wait until the product page inside the iframe is ready to drive.
|
|
381
|
+
function surfaceReady(){
|
|
382
|
+
return new Promise(function(resolve, reject){
|
|
383
|
+
var tries = 0;
|
|
384
|
+
(function poll(){
|
|
385
|
+
tries++;
|
|
386
|
+
try {
|
|
387
|
+
var w = frameWin(), d = frameDoc();
|
|
388
|
+
if (w && typeof w.showD === "function" && d && d.querySelectorAll(".task-card, .obs-row").length > 0) return resolve();
|
|
389
|
+
} catch(e){ /* not loaded yet */ }
|
|
390
|
+
if (tries > 80) return reject(new Error("surface never became ready (no showD / no task cards) — is the daemon serving seeded sessions?"));
|
|
391
|
+
setTimeout(poll, 150);
|
|
392
|
+
})();
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Navigate the REAL surface to a session and read its contract. Honest about
|
|
397
|
+
// the not-seeded case: returns found=false rather than inventing a result.
|
|
398
|
+
function observe(sid){
|
|
399
|
+
var w = frameWin(), d = frameDoc();
|
|
400
|
+
try { if (typeof w.hideD === "function") w.hideD(); } catch(e){}
|
|
401
|
+
try { w.showD(sid); } catch(e){ return Promise.resolve({ found:false, error:String(e), verifiers:[], says:[] }); }
|
|
402
|
+
return sleep(140).then(function(){ return readSurface(frameDoc()); });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* -------- run one (invariant × fixture) into a structured result -------- */
|
|
406
|
+
function runOne(inv, fx, s){
|
|
407
|
+
var base = { unitId: inv.id, fixtureId: fx.id, fixtureLabel: fx.label, probe: !!fx.probe, timestamp: new Date().toISOString(), domSnapshot: snapshot(s) };
|
|
408
|
+
if (!s.found){
|
|
409
|
+
return Object.assign(base, { verdict:"BLOCKED", checks:[], blockedReason: s.error ? ("surface error: "+s.error) : "session not rendered (not seeded?)" });
|
|
410
|
+
}
|
|
411
|
+
if (!inv.can(s)){
|
|
412
|
+
return Object.assign(base, { verdict:"BLOCKED", checks:[], blockedReason: "0 units to observe (no "+inv.needs+")" });
|
|
413
|
+
}
|
|
414
|
+
var violations = inv.check(s);
|
|
415
|
+
if (violations.length){
|
|
416
|
+
return Object.assign(base, { verdict:"FAIL", checks: violations.map(function(v){ return { status:"fail", label:v }; }) });
|
|
417
|
+
}
|
|
418
|
+
return Object.assign(base, { verdict:"PASS", checks: [{ status:(fx.probe?"probe":"ok"), label: inv.desc }] });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function snapshot(s){
|
|
422
|
+
if (!s.found) return {};
|
|
423
|
+
return { status:s.status, terminalFail:s.terminalFail, hasExplanation:s.hasExplanation, filesClaimed:s.filesClaimed, filetreeRendered:s.filetreeRendered, outcomeItems:s.outcomeItems, steps:s.steps, verifiers:s.verifiers.length, says:s.says.length };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* -------- the matrix UI -------- */
|
|
427
|
+
var resultMap = {}; // key "H1::sid" -> result
|
|
428
|
+
function key(u,f){ return u+"::"+f; }
|
|
429
|
+
|
|
430
|
+
function buildMatrix(){
|
|
431
|
+
var thead = document.querySelector("#matrix thead");
|
|
432
|
+
var tbody = document.querySelector("#matrix tbody");
|
|
433
|
+
var hrow = "<tr><th style=\\"width:40%\\">Invariant (unit)</th><th class=\\"needs\\" style=\\"width:13%\\">needs</th>";
|
|
434
|
+
FIXTURES.forEach(function(fx){
|
|
435
|
+
hrow += "<th><div class=\\"fixhdr"+(fx.probe?" probe":"")+"\\">"+(fx.probe?"🔍 ":"")+fx.label+"</div></th>";
|
|
436
|
+
});
|
|
437
|
+
hrow += "</tr>";
|
|
438
|
+
thead.innerHTML = hrow;
|
|
439
|
+
tbody.innerHTML = INVARIANTS.map(function(inv){
|
|
440
|
+
var cells = FIXTURES.map(function(fx){
|
|
441
|
+
return "<td><button class=\\"cell\\" id=\\"c-"+inv.id+"-"+cssId(fx.id)+"\\" data-u=\\""+inv.id+"\\" data-f=\\""+fx.id+"\\">·</button></td>";
|
|
442
|
+
}).join("");
|
|
443
|
+
var probeTag = inv.id==="H5" ? "<span class=\\"probe-tag\\">self-test</span>" : "";
|
|
444
|
+
return "<tr"+(inv.id==="H5"?" class=\\"probe-row\\"":"")+"><td><span class=\\"unit-id\\">"+inv.id+" · "+inv.title+"</span>"+probeTag+"<div class=\\"unit-desc\\">"+inv.desc+"</div></td><td class=\\"needs\\">"+inv.needs+"</td>"+cells+"</tr>";
|
|
445
|
+
}).join("");
|
|
446
|
+
// clicking a cell jumps replay to that fixture
|
|
447
|
+
Array.prototype.slice.call(document.querySelectorAll(".cell")).forEach(function(btn){
|
|
448
|
+
btn.addEventListener("click", function(){
|
|
449
|
+
var fid = btn.getAttribute("data-f");
|
|
450
|
+
var i = FIXTURES.map(function(f){return f.id;}).indexOf(fid);
|
|
451
|
+
if (i>=0){ openReplay(); jumpTo(i); }
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function cssId(s){ return s.replace(/[^a-zA-Z0-9_-]/g,"_"); }
|
|
457
|
+
|
|
458
|
+
function paintCell(r){
|
|
459
|
+
var el = document.getElementById("c-"+r.unitId+"-"+cssId(r.fixtureId));
|
|
460
|
+
if (!el) return;
|
|
461
|
+
el.className = "cell " + r.verdict.toLowerCase();
|
|
462
|
+
if (r.verdict==="FAIL" && r.probe) el.className += " expected";
|
|
463
|
+
el.textContent = r.verdict==="PASS" ? "\\u2713" : r.verdict==="FAIL" ? "\\u2715" : r.verdict==="BLOCKED" ? "\\u00B7" : "\\u2013";
|
|
464
|
+
el.title = r.blockedReason || (r.checks[0] && r.checks[0].label) || r.verdict;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/* -------- expected-vs-actual: the honesty self-check on the harness -------- */
|
|
468
|
+
function classify(results){
|
|
469
|
+
var pass=0, fail=0, blocked=0, unexpectedFail=[], probeMissed=[];
|
|
470
|
+
results.forEach(function(r){
|
|
471
|
+
if (r.verdict==="PASS") pass++;
|
|
472
|
+
else if (r.verdict==="BLOCKED") blocked++;
|
|
473
|
+
else if (r.verdict==="FAIL"){
|
|
474
|
+
fail++;
|
|
475
|
+
if (!r.probe) unexpectedFail.push(r.unitId+"\\u00d7"+r.fixtureLabel);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
// every fixture that declared expectFail MUST have produced that FAIL
|
|
479
|
+
FIXTURES.forEach(function(fx){
|
|
480
|
+
if (!fx.expectFail) return;
|
|
481
|
+
var r = resultMap[key(fx.expectFail, fx.id)];
|
|
482
|
+
if (!r || r.verdict!=="FAIL") probeMissed.push(fx.expectFail+"\\u00d7"+fx.label);
|
|
483
|
+
});
|
|
484
|
+
var ok = unexpectedFail.length===0 && probeMissed.length===0;
|
|
485
|
+
return { pass:pass, fail:fail, blocked:blocked, total:results.length, ok:ok, unexpectedFail:unexpectedFail, probeMissed:probeMissed };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function paintTally(c){
|
|
489
|
+
document.getElementById("tally").innerHTML =
|
|
490
|
+
"<span class=\\"p\\">\\u2705 "+c.pass+"</span><span class=\\"f\\">\\u274c "+c.fail+"</span><span class=\\"b\\">\\u26d4 "+c.blocked+"</span><span class=\\"t\\">/ "+c.total+"</span>";
|
|
491
|
+
var line = document.getElementById("verdict-line");
|
|
492
|
+
if (c.ok){
|
|
493
|
+
line.className = "verdict-line green";
|
|
494
|
+
line.textContent = "\\u2713 honest: every PASS was observed, and the probe FAILED exactly as designed (the harness can see a lie).";
|
|
495
|
+
} else {
|
|
496
|
+
line.className = "verdict-line red";
|
|
497
|
+
var parts = [];
|
|
498
|
+
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(", ")+") \\u2014 the harness is blind, treat the whole board as void");
|
|
500
|
+
line.textContent = "\\u2717 " + parts.join(" \\u00b7 ");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* -------- the agent / CI handle -------- */
|
|
505
|
+
var lastSingle = null;
|
|
506
|
+
function buildManifest(){
|
|
507
|
+
return INVARIANTS.map(function(inv){
|
|
508
|
+
return { unit: inv.id+"-"+inv.title, needs: inv.needs, description: inv.desc,
|
|
509
|
+
fixtures: FIXTURES.map(function(fx){ return { id:fx.id, label:fx.label, probe:!!fx.probe, expectFail:fx.expectFail||null }; }) };
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function runAll(){
|
|
514
|
+
var btn = document.getElementById("run-all");
|
|
515
|
+
btn.disabled = true; btn.textContent = "Running\\u2026";
|
|
516
|
+
var results = [];
|
|
517
|
+
return surfaceReady().then(function(){
|
|
518
|
+
// iterate fixtures, observe each once, run all invariants against it
|
|
519
|
+
return FIXTURES.reduce(function(p, fx){
|
|
520
|
+
return p.then(function(){
|
|
521
|
+
return observe(fx.id).then(function(s){
|
|
522
|
+
INVARIANTS.forEach(function(inv){
|
|
523
|
+
var r = runOne(inv, fx, s);
|
|
524
|
+
resultMap[key(inv.id, fx.id)] = r;
|
|
525
|
+
results.push(r);
|
|
526
|
+
paintCell(r);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
}, Promise.resolve());
|
|
531
|
+
}).then(function(){
|
|
532
|
+
var c = classify(results);
|
|
533
|
+
paintTally(c);
|
|
534
|
+
lastSingle = results[results.length-1] || null;
|
|
535
|
+
window.__verify_lastRun = results;
|
|
536
|
+
window.__verify_summary = c;
|
|
537
|
+
btn.disabled = false; btn.textContent = "\\u25b6 Run all";
|
|
538
|
+
return results;
|
|
539
|
+
}).catch(function(err){
|
|
540
|
+
btn.disabled = false; btn.textContent = "\\u25b6 Run all";
|
|
541
|
+
document.getElementById("verdict-line").className = "verdict-line red";
|
|
542
|
+
document.getElementById("verdict-line").textContent = "\\u26d4 BLOCKED: "+String(err && err.message || err);
|
|
543
|
+
throw err;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* ===================== feature coverage ===================== */
|
|
548
|
+
function covVerdict(feat, present){
|
|
549
|
+
if (feat.status==="shipped") return present ? "OBSERVED" : "MISSING";
|
|
550
|
+
if (feat.status==="partial") return present ? "OBSERVED" : "PARTIAL-GAP";
|
|
551
|
+
if (feat.status==="roadmap") return "SKIP-ROADMAP";
|
|
552
|
+
return "SKIP-FUTURE"; // future / cut
|
|
553
|
+
}
|
|
554
|
+
function covClass(v){
|
|
555
|
+
return v==="OBSERVED"?"pass":v==="MISSING"?"fail":v==="PARTIAL-GAP"?"warn":"blocked";
|
|
556
|
+
}
|
|
557
|
+
function covGlyph(v){
|
|
558
|
+
return v==="OBSERVED"?"\\u2713":v==="MISSING"?"\\u2715":v==="PARTIAL-GAP"?"\\u25d0":"\\u00b7";
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
var covResults = [];
|
|
562
|
+
function paintCov(r){
|
|
563
|
+
var el = document.getElementById("cov-"+cssId(r.id));
|
|
564
|
+
if (!el) return;
|
|
565
|
+
el.className = "cell " + covClass(r.verdict);
|
|
566
|
+
el.textContent = covGlyph(r.verdict);
|
|
567
|
+
el.title = r.verdict + (r.present?" · rendered":" · not in DOM");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function buildCovMatrix(){
|
|
571
|
+
var tb = document.querySelector("#cov-matrix tbody");
|
|
572
|
+
if (!tb) return;
|
|
573
|
+
tb.innerHTML = FEATURES.map(function(f){
|
|
574
|
+
var statusTag = f.status==="shipped"?"":f.status==="partial"?"<span class=\\"probe-tag\\" style=\\"border-color:rgba(254,188,46,.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
|
+
return "<tr><td><span class=\\"unit-id\\">"+f.id+"</span> <span class=\\"unit-desc\\">"+f.name+"</span>"+statusTag+"</td>"+
|
|
576
|
+
"<td class=\\"needs\\">"+f.tier+"</td>"+
|
|
577
|
+
"<td class=\\"needs\\">"+f.surface+"</td>"+
|
|
578
|
+
"<td><button class=\\"cell\\" id=\\"cov-"+cssId(f.id)+"\\">·</button></td></tr>";
|
|
579
|
+
}).join("");
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function classifyCov(results){
|
|
583
|
+
var observed=0, missing=0, partialGap=0, roadmap=0, future=0;
|
|
584
|
+
var missingIds=[];
|
|
585
|
+
results.forEach(function(r){
|
|
586
|
+
if (r.verdict==="OBSERVED") observed++;
|
|
587
|
+
else if (r.verdict==="MISSING"){ missing++; missingIds.push(r.id); }
|
|
588
|
+
else if (r.verdict==="PARTIAL-GAP") partialGap++;
|
|
589
|
+
else if (r.verdict==="SKIP-ROADMAP") roadmap++;
|
|
590
|
+
else future++;
|
|
591
|
+
});
|
|
592
|
+
var shipped = observed + missing;
|
|
593
|
+
return { observed:observed, missing:missing, partialGap:partialGap, roadmap:roadmap, future:future,
|
|
594
|
+
shipped:shipped, total:results.length, ok: missing===0, missingIds:missingIds };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function paintCovTally(c){
|
|
598
|
+
var t = document.getElementById("cov-tally");
|
|
599
|
+
if (t) t.innerHTML =
|
|
600
|
+
"<span class=\\"p\\">\\u2713 "+c.observed+" observed</span>"+
|
|
601
|
+
"<span class=\\"f\\">\\u2715 "+c.missing+" missing</span>"+
|
|
602
|
+
"<span style=\\"color:var(--warn)\\">\\u25d0 "+c.partialGap+" partial-gap</span>"+
|
|
603
|
+
"<span class=\\"b\\">\\u00b7 "+(c.roadmap+c.future)+" roadmap/future</span>"+
|
|
604
|
+
"<span class=\\"t\\">/ "+c.total+"</span>";
|
|
605
|
+
var line = document.getElementById("cov-verdict");
|
|
606
|
+
if (!line) return;
|
|
607
|
+
if (c.ok){
|
|
608
|
+
line.className = "verdict-line green";
|
|
609
|
+
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
|
+
} else {
|
|
611
|
+
line.className = "verdict-line red";
|
|
612
|
+
line.textContent = "\\u2717 "+c.missing+" feature(s) claimed shipped but NOT rendered: "+c.missingIds.join(", ")+" — PRODUCT_STATE overclaims, fix the doc or the code.";
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function runCoverage(){
|
|
617
|
+
var btn = document.getElementById("run-cov");
|
|
618
|
+
if (btn){ btn.disabled = true; btn.textContent = "Running\\u2026"; }
|
|
619
|
+
covResults = [];
|
|
620
|
+
return surfaceReady().then(function(){
|
|
621
|
+
var w = frameWin();
|
|
622
|
+
try { if (typeof w.hideD==="function") w.hideD(); } catch(e){}
|
|
623
|
+
return sleep(140);
|
|
624
|
+
}).then(function(){
|
|
625
|
+
// list-surface features (dashboard visible)
|
|
626
|
+
var d = frameDoc();
|
|
627
|
+
FEATURES.filter(function(f){ return f.surface==="list" && f.status!=="roadmap" && f.status!=="future"; }).forEach(function(f){
|
|
628
|
+
var present = d.querySelectorAll(f.sel).length > 0;
|
|
629
|
+
var r = { id:f.id, present:present, verdict:covVerdict(f, present) };
|
|
630
|
+
covResults.push(r); paintCov(r);
|
|
631
|
+
});
|
|
632
|
+
return sleep(40);
|
|
633
|
+
}).then(function(){
|
|
634
|
+
// detail-surface features, grouped by the fixture that TRIGGERS them.
|
|
635
|
+
// Conditional surfaces (recovery/crash/hook-block/...) only render on the
|
|
636
|
+
// right seeded session — open each fixture once, check its group.
|
|
637
|
+
var detail = FEATURES.filter(function(f){ return f.surface==="detail" && f.status!=="roadmap" && f.status!=="future"; });
|
|
638
|
+
var byFix = {};
|
|
639
|
+
detail.forEach(function(f){ var fx = f.fixture || COV_FIXTURE; (byFix[fx] = byFix[fx] || []).push(f); });
|
|
640
|
+
var fixtures = Object.keys(byFix);
|
|
641
|
+
return fixtures.reduce(function(p, fx){
|
|
642
|
+
return p.then(function(){
|
|
643
|
+
try { var w = frameWin(); if (w.hideD) w.hideD(); w.showD(fx); } catch(e){}
|
|
644
|
+
return sleep(200).then(function(){
|
|
645
|
+
var d = frameDoc();
|
|
646
|
+
byFix[fx].forEach(function(f){
|
|
647
|
+
var present = d.querySelectorAll(f.sel).length > 0;
|
|
648
|
+
var r = { id:f.id, present:present, verdict:covVerdict(f, present), fixture:fx };
|
|
649
|
+
covResults.push(r); paintCov(r);
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
}, Promise.resolve());
|
|
654
|
+
}).then(function(){
|
|
655
|
+
// roadmap / future: SKIP (correctly absent), no observation
|
|
656
|
+
FEATURES.filter(function(f){ return f.status==="roadmap" || f.status==="future"; }).forEach(function(f){
|
|
657
|
+
var r = { id:f.id, present:false, verdict:covVerdict(f, false) };
|
|
658
|
+
covResults.push(r); paintCov(r);
|
|
659
|
+
});
|
|
660
|
+
var c = classifyCov(covResults);
|
|
661
|
+
paintCovTally(c);
|
|
662
|
+
window.__verify_coverage = { results:covResults, summary:c };
|
|
663
|
+
if (btn){ btn.disabled = false; btn.textContent = "\\u25b6 Run coverage"; }
|
|
664
|
+
return covResults;
|
|
665
|
+
}).catch(function(err){
|
|
666
|
+
if (btn){ btn.disabled = false; btn.textContent = "\\u25b6 Run coverage"; }
|
|
667
|
+
throw err;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
window.__verify = {
|
|
672
|
+
version: PROTO_VERSION,
|
|
673
|
+
manifest: buildManifest,
|
|
674
|
+
current: function(){ return lastSingle; },
|
|
675
|
+
runAll: runAll,
|
|
676
|
+
coverage: runCoverage,
|
|
677
|
+
features: function(){ return FEATURES.map(function(f){ return { id:f.id, name:f.name, surface:f.surface, status:f.status, tier:f.tier }; }); }
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
document.getElementById("manifest-json").textContent =
|
|
681
|
+
JSON.stringify(buildManifest(), null, 2) +
|
|
682
|
+
"\\n\\nawait window.__verify.runAll() -> one result per invariant\\u00d7fixture {verdict, checks, blockedReason, domSnapshot}\\n" +
|
|
683
|
+
"window.__verify_summary -> {pass, fail, blocked, ok, unexpectedFail, probeMissed}\\n" +
|
|
684
|
+
"ok===false (unexpected FAIL, or the probe failed to fail) -> scripts/verify.mjs exits 1.";
|
|
685
|
+
|
|
686
|
+
/* ===================== replay (visible, slow-motion) ===================== */
|
|
687
|
+
var replayOpen = false, playing = false, idx = 0, replayResults = [];
|
|
688
|
+
var DWELL = clampInt(qs("dwell"), 1500, 200, 30000);
|
|
689
|
+
var KEY_HOLD = clampInt(qs("hold"), 700, 100, 5000);
|
|
690
|
+
document.getElementById("dwell-label").textContent = "dwell "+DWELL+"ms";
|
|
691
|
+
|
|
692
|
+
function qs(k){ var m = new RegExp("[?&]"+k+"=([^&]+)").exec(location.search); return m ? decodeURIComponent(m[1]) : null; }
|
|
693
|
+
function clampInt(raw, fb, min, max){ var n = raw?parseInt(raw,10):NaN; if(isNaN(n))return fb; return Math.min(max,Math.max(min,n)); }
|
|
694
|
+
|
|
695
|
+
function openReplay(){ replayOpen = true; document.getElementById("replay").classList.remove("hidden"); buildStepList(); }
|
|
696
|
+
function buildStepList(){
|
|
697
|
+
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?"🔍 ":"")+fx.label+"</span></div>";
|
|
699
|
+
}).join("");
|
|
700
|
+
Array.prototype.slice.call(document.querySelectorAll(".steprow")).forEach(function(row){
|
|
701
|
+
row.addEventListener("click", function(){ jumpTo(Number(row.getAttribute("data-i"))); });
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
function markStep(i, verdict){
|
|
705
|
+
var row = document.querySelector('.steprow[data-i="'+i+'"]');
|
|
706
|
+
if (!row) return;
|
|
707
|
+
Array.prototype.slice.call(document.querySelectorAll(".steprow")).forEach(function(r){ r.classList.remove("active"); });
|
|
708
|
+
if (verdict==null){ row.classList.add("active"); row.querySelector(".st").textContent="\\u25b8"; return; }
|
|
709
|
+
row.querySelector(".st").textContent = verdict==="PASS"?"\\u2705":verdict==="FAIL"?"\\u274c":"\\u26d4";
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
var caption = document.getElementById("caption");
|
|
713
|
+
var ring = null;
|
|
714
|
+
function showCaption(t){ if(t){ caption.textContent=t; caption.classList.add("on"); } else { caption.classList.remove("on"); } }
|
|
715
|
+
function drawRing(el){
|
|
716
|
+
removeRing();
|
|
717
|
+
if (!el) return;
|
|
718
|
+
var ir = iframe.getBoundingClientRect();
|
|
719
|
+
var er = el.getBoundingClientRect();
|
|
720
|
+
ring = document.createElement("div");
|
|
721
|
+
ring.className = "ring";
|
|
722
|
+
ring.style.left = (ir.left + er.left - 3) + "px";
|
|
723
|
+
ring.style.top = (ir.top + er.top - 3) + "px";
|
|
724
|
+
ring.style.width = (er.width + 6) + "px";
|
|
725
|
+
ring.style.height = (Math.min(er.height, 120) + 6) + "px";
|
|
726
|
+
document.body.appendChild(ring);
|
|
727
|
+
}
|
|
728
|
+
function removeRing(){ if(ring){ ring.remove(); ring=null; } }
|
|
729
|
+
|
|
730
|
+
function setStageVerdict(v){
|
|
731
|
+
var w = document.getElementById("stage-wrap");
|
|
732
|
+
w.className = "stage-wrap" + (v ? " v-"+v.toLowerCase() : "");
|
|
733
|
+
}
|
|
734
|
+
function setFrameStatus(results, sid){
|
|
735
|
+
var fs = document.getElementById("framestatus");
|
|
736
|
+
var fails = results.filter(function(r){ return r.verdict==="FAIL"; });
|
|
737
|
+
var blocked = results.filter(function(r){ return r.verdict==="BLOCKED"; });
|
|
738
|
+
var pass = results.filter(function(r){ return r.verdict==="PASS"; });
|
|
739
|
+
var verdict = fails.length ? "FAIL" : (pass.length ? "PASS" : "BLOCKED");
|
|
740
|
+
var html = "<span class=\\"badge "+verdict.toLowerCase()+"\\">"+verdict+"</span>";
|
|
741
|
+
html += "<span class=\\"sub\\" style=\\"color:var(--fg-50)\\">\\u2705 "+pass.length+" \\u00b7 \\u274c "+fails.length+" \\u00b7 \\u26d4 "+blocked.length+"</span>";
|
|
742
|
+
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
|
+
else if (!pass.length && blocked.length) html += "<span class=\\"issues blk\\">"+blocked[0].blockedReason+"</span>";
|
|
744
|
+
fs.innerHTML = html;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Hide the app chrome inside the iframe so the stage shows ONLY the session
|
|
748
|
+
// detail being verified — an isolated mount, not the whole app in a tab.
|
|
749
|
+
var staged = false;
|
|
750
|
+
function stageClean(){
|
|
751
|
+
var d = frameDoc(); if (!d || staged) return;
|
|
752
|
+
var st = d.createElement("style");
|
|
753
|
+
st.textContent =
|
|
754
|
+
".header,.custom-view-banner,.failover-banner,.indexing-banner,.budget-banner,.squad-row,.bar,#task-detail-view .input-area{display:none!important}" +
|
|
755
|
+
"body{overflow:auto!important;height:auto!important}" +
|
|
756
|
+
".content{padding:0!important}#task-detail-view{padding:18px 22px!important}";
|
|
757
|
+
d.head.appendChild(st);
|
|
758
|
+
staged = true;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// The per-case checklist that fills in live (the granular part the human watches).
|
|
762
|
+
function clReset(){
|
|
763
|
+
var box = document.getElementById("checklist");
|
|
764
|
+
box.classList.add("on");
|
|
765
|
+
box.innerHTML = "<div class=\\"cl-h\\">honesty checks</div>" + INVARIANTS.map(function(inv){
|
|
766
|
+
return "<div class=\\"cl-row\\" id=\\"cl-"+inv.id+"\\"><span class=\\"cl-id\\">"+inv.id+"</span><span class=\\"cl-st\\">\\u25cb</span><span class=\\"cl-tx\\">"+inv.title+"</span></div>";
|
|
767
|
+
}).join("");
|
|
768
|
+
}
|
|
769
|
+
function clSet(invId, verdict, text){
|
|
770
|
+
var row = document.getElementById("cl-"+invId);
|
|
771
|
+
if (!row) return;
|
|
772
|
+
row.className = "cl-row run " + verdict.toLowerCase();
|
|
773
|
+
row.querySelector(".cl-st").textContent = verdict==="PASS"?"\\u2705":verdict==="FAIL"?"\\u274c":"\\u26d4";
|
|
774
|
+
if (text) row.querySelector(".cl-tx").textContent = text;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// The element each invariant reads from — so the ring lands on what's observed.
|
|
778
|
+
function targetFor(invId, d){
|
|
779
|
+
if (invId==="H1") return d.querySelector('[data-verify-unit="verifier"]');
|
|
780
|
+
if (invId==="H2") return d.querySelector('.tl-say');
|
|
781
|
+
if (invId==="H3") return d.querySelector('[data-verify-unit="outcome"]');
|
|
782
|
+
if (invId==="H5") return d.querySelector('[data-verify-unit="filetree"]') || d.querySelector('[data-verify-unit="outcome"]');
|
|
783
|
+
if (invId==="H6") return d.querySelector('.recovery-card,.crash-recovery,.hook-block-card') || d.querySelector('[data-verify-unit="detail"]');
|
|
784
|
+
return d.querySelector('[data-verify-unit="detail"]');
|
|
785
|
+
}
|
|
786
|
+
// A granular, human-readable line for what THIS check observed.
|
|
787
|
+
function narrate(inv, s, r){
|
|
788
|
+
if (r.verdict==="BLOCKED") return inv.id+" \\u00b7 n/a here (no "+inv.needs+")";
|
|
789
|
+
if (inv.id==="H1") return "H1 \\u00b7 verifier verdict='"+(s.verifiers[0]||{}).verdict+"' git="+(s.verifiers[0]||{}).gittracked;
|
|
790
|
+
if (inv.id==="H2") return "H2 \\u00b7 "+s.says.length+" reasoning bubble(s), all have words";
|
|
791
|
+
if (inv.id==="H3") return "H3 \\u00b7 outcome renders "+s.outcomeItems+" item(s)";
|
|
792
|
+
if (inv.id==="H5") return "H5 \\u00b7 claims "+s.filesClaimed+" files, renders "+s.filetreeRendered+" row(s)";
|
|
793
|
+
if (inv.id==="H6") return "H6 \\u00b7 status='"+s.status+"', reason shown="+s.hasExplanation;
|
|
794
|
+
return inv.desc;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Drive ONE invariant visibly: scroll its target into view, ring it, show a
|
|
798
|
+
// caption with the observed numbers, land the verdict on the checklist + matrix.
|
|
799
|
+
function playCheck(inv, fx, s){
|
|
800
|
+
var r = runOne(inv, fx, s);
|
|
801
|
+
resultMap[key(inv.id, fx.id)] = r;
|
|
802
|
+
paintCell(r);
|
|
803
|
+
var d = frameDoc();
|
|
804
|
+
if (r.verdict==="BLOCKED"){
|
|
805
|
+
// brief, dim — honest "nothing to observe", no theatrics
|
|
806
|
+
clSet(inv.id, "BLOCKED", inv.title+" \\u2014 n/a");
|
|
807
|
+
showCaption("\\u26d4 " + narrate(inv, s, r));
|
|
808
|
+
return sleep(Math.round(KEY_HOLD*0.45)).then(function(){ showCaption(null); return r; });
|
|
809
|
+
}
|
|
810
|
+
var el = targetFor(inv.id, d);
|
|
811
|
+
if (el && el.scrollIntoView) try { el.scrollIntoView({block:"center"}); } catch(e){}
|
|
812
|
+
return sleep(120).then(function(){
|
|
813
|
+
drawRing(el);
|
|
814
|
+
showCaption("\\ud83d\\udd0d " + narrate(inv, s, r));
|
|
815
|
+
return sleep(KEY_HOLD);
|
|
816
|
+
}).then(function(){
|
|
817
|
+
removeRing();
|
|
818
|
+
var verdictWord = r.verdict==="FAIL" ? " \\u2192 \\u274c FAIL" : " \\u2192 \\u2705 PASS";
|
|
819
|
+
showCaption((r.verdict==="FAIL"?"\\u26a0 ":"") + narrate(inv, s, r) + verdictWord);
|
|
820
|
+
clSet(inv.id, r.verdict, inv.title);
|
|
821
|
+
return sleep(Math.round(KEY_HOLD*0.7));
|
|
822
|
+
}).then(function(){ showCaption(null); return r; });
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Drive one fixture: open it on a clean stage, then walk each honesty check
|
|
826
|
+
// one at a time so the human SEES each case tested granularly.
|
|
827
|
+
function playFixture(i){
|
|
828
|
+
var fx = FIXTURES[i];
|
|
829
|
+
idx = i;
|
|
830
|
+
markStep(i, null);
|
|
831
|
+
clReset();
|
|
832
|
+
document.getElementById("stage-title").innerHTML = "case " + (i+1) + "/" + FIXTURES.length + " · <strong>" + (fx.probe?"🔍 ":"") + fx.label + "</strong> <span class=\\"dim\\">/ "+fx.id+"</span>";
|
|
833
|
+
document.getElementById("framestatus").innerHTML = "<span class=\\"sub\\" style=\\"color:var(--fg-40)\\">opening case\\u2026</span>";
|
|
834
|
+
setStageVerdict(null);
|
|
835
|
+
document.getElementById("prog").style.width = ((i)/FIXTURES.length*100)+"%";
|
|
836
|
+
return surfaceReady().then(function(){
|
|
837
|
+
var w = frameWin();
|
|
838
|
+
try { if (typeof w.hideD==="function") w.hideD(); } catch(e){}
|
|
839
|
+
return sleep(120);
|
|
840
|
+
}).then(function(){
|
|
841
|
+
stageClean();
|
|
842
|
+
showCaption("\\ud83d\\udc46 open case \\u2192 showD('"+fx.label+"')");
|
|
843
|
+
try { frameWin().showD(fx.id); } catch(e){}
|
|
844
|
+
return sleep(Math.round(KEY_HOLD*0.7));
|
|
845
|
+
}).then(function(){
|
|
846
|
+
showCaption(null);
|
|
847
|
+
var s = readSurface(frameDoc());
|
|
848
|
+
// walk the invariants sequentially, visibly
|
|
849
|
+
var results = [];
|
|
850
|
+
return INVARIANTS.reduce(function(p, inv){
|
|
851
|
+
return p.then(function(){ return playCheck(inv, fx, s).then(function(r){ results.push(r); }); });
|
|
852
|
+
}, Promise.resolve()).then(function(){ return results; });
|
|
853
|
+
}).then(function(results){
|
|
854
|
+
replayResults = replayResults.concat(results);
|
|
855
|
+
var verdict = results.some(function(r){return r.verdict==="FAIL";}) ? "FAIL" : (results.some(function(r){return r.verdict==="PASS";}) ? "PASS" : "BLOCKED");
|
|
856
|
+
setStageVerdict(verdict);
|
|
857
|
+
setFrameStatus(results, fx.id);
|
|
858
|
+
markStep(i, verdict);
|
|
859
|
+
lastSingle = results[results.length-1];
|
|
860
|
+
document.getElementById("prog").style.width = ((i+1)/FIXTURES.length*100)+"%";
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function jumpTo(i){ playing=false; openReplay(); playFixture(i); }
|
|
865
|
+
function advance(){
|
|
866
|
+
if (idx+1 >= FIXTURES.length){ playing=false; document.getElementById("play").innerHTML="↻ Replay again"; finishReplay(); return; }
|
|
867
|
+
playFixture(idx+1).then(function(){ if(playing) setTimeout(advance, DWELL); });
|
|
868
|
+
}
|
|
869
|
+
function startReplay(){
|
|
870
|
+
replayResults = []; idx = 0; playing = true;
|
|
871
|
+
document.getElementById("play").innerHTML = "❚❚ Pause";
|
|
872
|
+
playFixture(0).then(function(){ if(playing) setTimeout(advance, DWELL); });
|
|
873
|
+
}
|
|
874
|
+
function finishReplay(){
|
|
875
|
+
var c = classify(replayResults);
|
|
876
|
+
window.__verify_replay = { done:true, results:replayResults, summary:c };
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
document.getElementById("toggle-replay").addEventListener("click", function(){ openReplay(); document.getElementById("replay").scrollIntoView({behavior:"smooth"}); });
|
|
880
|
+
document.getElementById("play").addEventListener("click", function(){
|
|
881
|
+
if (playing){ playing=false; this.innerHTML="▶ Play"; }
|
|
882
|
+
else { startReplay(); }
|
|
883
|
+
});
|
|
884
|
+
document.getElementById("skip").addEventListener("click", function(){ advance(); });
|
|
885
|
+
|
|
886
|
+
/* in-browser clip recorder (getDisplayMedia, one webm per fixture) */
|
|
887
|
+
var recStream=null, recorder=null, recChunks=[], recClips=[];
|
|
888
|
+
document.getElementById("record").addEventListener("click", function(){
|
|
889
|
+
var btn = this;
|
|
890
|
+
if (recStream){ stopRecording(); btn.innerHTML="⏺ Record clips"; return; }
|
|
891
|
+
navigator.mediaDevices.getDisplayMedia({ video:{frameRate:30}, audio:false, preferCurrentTab:true })
|
|
892
|
+
.then(function(stream){
|
|
893
|
+
recStream = stream; btn.innerHTML="⏺ Recording \\u2014 stop";
|
|
894
|
+
stream.getVideoTracks()[0].addEventListener("ended", function(){ stopRecording(); btn.innerHTML="⏺ Record clips"; });
|
|
895
|
+
// record one continuous clip across the whole replay (simple + reliable);
|
|
896
|
+
// download offered on stop.
|
|
897
|
+
recChunks = [];
|
|
898
|
+
recorder = new MediaRecorder(stream, pickMime());
|
|
899
|
+
recorder.ondataavailable = function(e){ if(e.data.size>0) recChunks.push(e.data); };
|
|
900
|
+
recorder.onstop = function(){
|
|
901
|
+
var blob = new Blob(recChunks, {type:"video/webm"});
|
|
902
|
+
var a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "codehere-verify-replay.webm";
|
|
903
|
+
document.body.appendChild(a); a.click(); a.remove();
|
|
904
|
+
};
|
|
905
|
+
recorder.start(200);
|
|
906
|
+
startReplay();
|
|
907
|
+
})
|
|
908
|
+
.catch(function(e){ btn.innerHTML="⏺ Record clips"; alert("recording cancelled: "+e); });
|
|
909
|
+
});
|
|
910
|
+
function stopRecording(){
|
|
911
|
+
try { if(recorder && recorder.state!=="inactive") recorder.stop(); } catch(e){}
|
|
912
|
+
if (recStream){ recStream.getTracks().forEach(function(t){t.stop();}); recStream=null; }
|
|
913
|
+
}
|
|
914
|
+
function pickMime(){
|
|
915
|
+
var cs = ["video/webm;codecs=vp9","video/webm;codecs=vp8","video/webm"];
|
|
916
|
+
for (var i=0;i<cs.length;i++){ if (window.MediaRecorder && MediaRecorder.isTypeSupported(cs[i])) return {mimeType:cs[i]}; }
|
|
917
|
+
return undefined;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/* -------- boot -------- */
|
|
921
|
+
buildMatrix();
|
|
922
|
+
buildCovMatrix();
|
|
923
|
+
// Run all = honesty matrix THEN feature coverage (one click, both truths).
|
|
924
|
+
document.getElementById("run-all").addEventListener("click", function(){ runAll().then(runCoverage); });
|
|
925
|
+
document.getElementById("run-cov").addEventListener("click", function(){ runCoverage(); });
|
|
926
|
+
|
|
927
|
+
// Auto modes for headless drivers (scripts/verify.mjs, scripts/record-replay.mjs).
|
|
928
|
+
// Replay mode is selected by the ROUTE (/verify/replay) or ?mode=replay.
|
|
929
|
+
var bootDone = false;
|
|
930
|
+
iframe.addEventListener("load", function(){
|
|
931
|
+
if (bootDone) return; bootDone = true; // the iframe reloads as we drive it; boot once
|
|
932
|
+
var isReplay = /\\/verify\\/replay/.test(location.pathname) || qs("mode")==="replay";
|
|
933
|
+
var auto = qs("auto");
|
|
934
|
+
if (isReplay){ openReplay(); if (auto!=="0") surfaceReady().then(function(){ setTimeout(startReplay, 400); }); }
|
|
935
|
+
else if (auto==="1"){ surfaceReady().then(function(){ setTimeout(function(){ runAll().then(runCoverage); }, 300); }); }
|
|
936
|
+
});
|
|
937
|
+
})();
|
|
938
|
+
</script>
|
|
939
|
+
</body>
|
|
940
|
+
</html>`;
|
|
941
|
+
//# sourceMappingURL=verify-page.js.map
|