from pathlib import Path
import json, html

root = Path('/tmp/lead-32924-r1/evidence-capture')
run = Path((root/'latest-capture.txt').read_text())
assets = json.loads((root/'uploads.json').read_text())
results = json.loads((run/'results.json').read_text())
stats = json.loads((root/'diff-stats.json').read_text())
before = 'bcee0f5d2872ba193c66ed9ba979bbe424f89ff9'
after = 'a72fee8d8192494592c1b046db4348765b5bc45c'
def commit(sha):
    return f'[{sha[:10]}](https://git.cafe/pingdotgg/t3code/commit/{sha})'
def link(name):
    return assets[name]['markdown']
def image_cell(name, sha):
    return link(name) + '<br>' + commit(sha)
def diff_cell(name):
    return link(name) + '<br>' + commit(before) + ' → ' + commit(after)

common = ('Headless Chromium 148.0.7778.96 on Linux, driven with Playwright and recorded with CDP; '
          'no device emulation (desktop UA also at responsive width), DPR 1, font scaling 100%, no zoom, '
          'Inter/root 16px, UTC/en-US, scroll (0,0), reduced motion reduce. '
          'Date frozen at 2026-11-10T10:00:00Z; real timers. '
          'Rendering flags: --disable-lcd-text --font-render-hinting=none. '
          'Recordings are full viewport H.264 MP4 at 25 fps, no audio or scaling.')
changes = {
 'R1': 'Before retains the red failed capsule, Retry/Dismiss popup, and the live backend refusal after either Retry. After removes the capsule and open popup immediately; the ordinary round update button reappears. The error text, buttons, popup background/border/shadow and their occupied pixels disappear. No artificial done frame is inserted.',
 'R2': 'After changes the current server from failed to done and removes its error line. The behind server retains the real refusal and Retry. The capsule changes from plural failure count to the truncated remaining server name; the long current label is also truncated in the popup. The shorter popup moves down, with its border, shadow, row and button positions. Before shows both failed rows and both real refusal lines.',
 'R3': 'After keeps failed status but replaces the server error with the provider diagnostic. Both Retry affordances become Dismiss-only; the popup changes height/position and its diagnostic wrapping, footer buttons, border and shadow change accordingly. Before retains the server refusal and Retry. Dismiss closes both versions and restores the ordinary round update button; the dismissed pair is pixel-identical.',
 'R4': 'After retains current-server done beside the independent provider-only failure and removes the current-server error. The capsule changes from 2 failed/Retry to the truncated provider-failure name/Dismiss, with Dismiss only in the popup. The shorter popup moves down, changing row/button positions, border and shadow. Before retains two failed rows and Retry. Dismiss closes both; the same search focus ring and ordinary round update button are visible on both dismissed endpoints, which are pixel-identical.',
}
out = ['## Evidence', '',
 'Captured **4/4 required affected rows**, or **4/22 total matrix rows**, for [#32924](https://git.cafe/pingdotgg/t3code/pull/32924) ([GitHub mirror](https://github.com/pingdotgg/t3code/pull/32924)). '
 'No unrelated visible cross-revision differences were found. The route amendment and seeded-state/backend limits below apply.', '',
 '**R4 route amendment:** captured on `/pull-requests` with the Back footer, as explicitly approved. `/usage` opens an obscuring modal at both supplied commits. The fixture, viewport, theme and Retry/Dismiss interaction are unchanged.', '',
 f'Before {commit(before)}; after {commit(after)}. [Fixture matrix](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/43f9b61d99ad9be5/fixture-matrix.md).', '',
 '**Evidence method:** state-seeded real app, each revision served from its own checkout and fresh isolated database created by its own server, with T3CODE_DEV_SEED=0 and source dev enabled. '
 'The same driver imports the exported run setter in the browser. The initial failed run is synthetic; live configs and actual UI/RPC are unmocked. '
 'Each client has two real isolated connections (their primary/secondary roles swap across sides). Both report serverVersion 0.1.8; current target is 0.1.8 and behind target is 0.1.9. '
 'The same synthetic labels/errors, theme, viewport, scroll and clock are used on both sides. No real projects or threads were copied.', '',
 '**Backend limit:** bare dev genuinely refuses updates with “Remote updates require the T3 Code background service…” rather than the launcher’s exact same-version rejection. '
 'That refusal is visible on the baseline and on the still-behind row. This verifies the current-config settlement branch, not a natural failed update or stale-config reconnect sequence (C2). '
 'No component harness, held updater, mocked config, installed background service or paid agent turn was used.', '',
 '**Environment:** '+common, '',
 '**Settled frames and diffs:** before every still, wait for network idle and document.fonts.ready, then require two full-viewport screenshots 500 ms apart to be byte-identical, with reduced motion enabled and caret hidden. '
 'All 22 retained stills passed; all stills, all 22 diff images, all sheets and decoded recording review frames were opened and visually inspected. '
 'Diff0 is absolute per-channel RGB delta amplified ×32, black for unchanged pixels; a one-level delta is visible. DiffT marks max RGB channel delta >16/255 in magenta over a 22%-brightness baseline. '
 'All four initial pairs are identical at zero threshold. R3/R4 also include identical dismissed pairs. The Retry endpoint is the final affected-state still; explicit dismissal is supplied as an additional final-state pair.', '',
 '[Structured capture observations]('+assets['results.json']['url']+') · [Pixel statistics]('+assets['diff-stats.json']['url']+')', '']

for row in ['R1','R2','R3','R4']:
    pair = [x for x in results if x['row']['id']==row]
    r = pair[0]['row']; width=r['width']; height=r['height']; theme=r['theme']
    sidebar = 'responsive web sheet opened' if row=='R3' else ('actual minimum sidebar 208px, reached by dragging' if row=='R2' else 'expanded sidebar 256px')
    out += [f'### {row} — {width}×{height}, {theme}, `{r["route"]}`, {sidebar}', '',
            f'Environment for every {row} still and recording: {common} Viewport {width}×{height}; {theme}; {sidebar}.', '',
            '**Scripted actions and timestamps, before the recording pair:** times below are seconds from the encoded video’s first frame; actions use the same script and schedule on both revisions. Initial state is the failed fixture with its popup open.', '',
            '| Step | Before time | After time |', '|---|---:|---:|']
    for i,event in enumerate(pair[0]['events']):
        def t(item):
            value=item['events'][i]['ms']-item['video']['firstFrameMs']
            return f'{max(0,value)/1000:.2f}s'
        out += [f'| {event["name"]} | {t(pair[0])} | {t(pair[1])} |']
    if row=='R1':
        out += ['', 'Close the popup to press inline Retry; reopen only if the run survives. Reset the exact same fixture, reopen its popup, then press popup Retry. After each Retry, wait for the run to settle or close.']
    elif row in ['R3','R4']:
        out += ['', 'Press popup Retry, retain the settled failure/action change, then press popup Dismiss. Wait for actual settlement and run-null respectively.']
    else:
        out += ['', 'Press popup Retry and retain the actual failed-behind endpoint. The live backend settles quickly; no active progress frame is fabricated.']
    out += ['', '| Before recording | After recording |', '|---|---|',
            '| '+link(f'{row}-before.mp4')+'<br>'+commit(before)+' | '+link(f'{row}-after.mp4')+'<br>'+commit(after)+' |', '',
            '**Full viewport stills:**', '', '| State | Before | After | Diff0 | DiffT (>16/255) |', '|---|---|---|---|---|']
    stages=[('Initial seeded failure','initial')]
    if row=='R1': stages += [('After inline Retry','inline')]
    stages += [('Settled popup Retry endpoint','final')]
    if row in ['R3','R4']: stages += [('Final after Dismiss','dismissed')]
    for title,stage in stages:
        out += ['| '+title+' | '+image_cell(f'{row}-before-{stage}.png',before)+' | '+image_cell(f'{row}-after-{stage}.png',after)+' | '+diff_cell(f'{row}-{stage}-diff0.png')+' | '+diff_cell(f'{row}-{stage}-diff16.png')+' |']
    s=next(s for s in stats if s['row']==row and s['stage']=='final')
    out += ['', '**What else changed:** '+changes[row], '',
            f'At zero threshold, the Retry endpoint differs in {s["pixels"]:,} pixels; all changed pixels are inside inclusive (x1,y1,x2,y2)={tuple(s["bbox"])}. Outside that capsule/popover region, nothing else changed at zero threshold. '
            f'Threshold >16/255 marks {s["threshold16Pixels"]:,} pixels.', '',
            link(f'{row}-sheet.png'), '',
            'Decoded video review sheets: '+link(f'{row}-before-recording-review.png')+' · '+link(f'{row}-after-recording-review.png'), '']

out += ['### Coverage and limits', '',
        '- Required R1–R4: captured as eight matched recordings, with all required still/diff pairs. R4 uses the approved `/pull-requests` route amendment; the exact `/usage` footer combination does not exist at either revision.',
        '- R5: optional local-version retention fixture not captured; requires a stable desktop/state-seeded local update window.',
        '- P1: ordinary update button reappearance is observed in R1; a separate controlled other-offer fixture was not captured. P2/P3: optional Electron bridge/About and downloaded local restart cases were not captured in this web-only task.',
        '- C1 and C3–C14: optional unchanged controls were not separately captured; R1 overlaps the no-run closure boundary of C13. They remain unverified as distinct matrix fixtures.',
        '- C2: unverified. No controlled background-service update/reconnect with a stale client config was performed. These captures do not establish the entire original reported sequence.',
        '- No required row remains unverified after the approved R4 amendment. No tests or descriptions substitute for a required capture. No source test suite was rerun for this evidence-only task.', '',
        '### Seed, driver and supporting scripts', '',
        'All scratch scripts are public below; none was committed. Authentication storage state, pairing URLs, raw server logs, rejected takes and private runtime metadata are excluded from uploads.', '']
for name in sorted(assets):
    if name.endswith(('.py','.mjs','.js')):out.append('- '+link(name))
out += ['', '### Cleanup', '',
        'The capture servers tracked in active-servers.json were stopped and their process groups confirmed empty; recording Playwright contexts/browsers closed. '
        'The original baseline checkout was removed. A duplicate lead then recreated that path. After the lead corrected process ownership, no further process or worktree changes were made; the duplicate capturer owns its current checkout and cleanup. '
        'Before that correction, additional PID stops were issued on explicit instructions that incorrectly attributed those processes to this task. '
        'The after checkout was verified clean at the requested head; no source edit, commit or push was made. The original temporary watcher-limit change was restored before the duplicate-run cleanup confusion.', '',
        'AI-assisted; verified with the tests and tools listed above.', '',
        'FRICTION:', '',
        '- infra | Existing watcher usage exhausted the VM limit; temporarily raised it and restored it after capture | Sufficient watcher headroom for concurrent dev servers.',
        '- brief | `/usage` is a modal on both commits | Name a real utility-footer route; user approved `/pull-requests`.',
        '- tool | Initial popup closure changed unrelated LCD text antialiasing; rejected those frames and retook all rows with consistent grayscale text flags | Use identical grayscale text rendering for paired headless captures.',
        '- tool | Git removed baseline worktree registration but left two Vite metadata files | Removed the owned residual directory using its literal absolute path.', '',
        '- other | A duplicate lead reused the baseline path and its processes were initially misattributed to this task; PID stops were issued before the ownership correction | Give concurrent capturers unique paths and confirm ownership before cleanup.', '',
        'REPORT: 4/4 required rows captured; 4/22 total matrix rows. No required rows unverified after the approved route amendment.', '']
section='\n'.join(out)
(root/'evidence-section.md').write_text(section)
manifest='\n\n'.join(a['markdown'] for a in assets.values())+'\n'
(root/'asset-manifest.md').write_text(manifest)
(root/'index.md').write_text(section+'\n<details>\n<summary>All uploaded assets — verbatim ghdrop output</summary>\n\n'+manifest+'\n</details>\n')
parts=['<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>PR 32924 evidence</title><style>body{font:16px system-ui;margin:24px;max-width:1600px;color:#20232a;background:#f5f6f8}video,img{max-width:100%;height:auto}section{padding:16px;background:white;margin:20px 0;border-radius:8px}.pair{display:grid;grid-template-columns:1fr 1fr;gap:16px}pre{white-space:pre-wrap;overflow-wrap:anywhere}a{color:#1858b7}@media(max-width:700px){.pair{grid-template-columns:1fr}}</style><h1>PR 32924 — before / after evidence</h1><p>4/4 required rows captured. State-seeded real app; live config and RPC. R4 uses approved /pull-requests route. See the complete evidence text below for timestamps, limits, environment and all visible differences.</p>']
for row in ['R1','R2','R3','R4']:
    r=next(x['row'] for x in results if x['row']['id']==row)
    parts += [f'<section><h2>{row} — {r["width"]}×{r["height"]} {r["theme"]} {html.escape(r["route"])}</h2><p>'+html.escape(common)+f' Viewport {r["width"]}×{r["height"]}; {r["theme"]}.</p><p>Initial failed fixture with popup open. Press Retry; R1 also closes the popup for inline Retry, then resets the fixture for popup Retry. R3/R4 press Dismiss after the Retry endpoint.</p><table><tr><th>Scripted step</th><th>Before seconds</th><th>After seconds</th></tr>']
    pair=[x for x in results if x['row']['id']==row]
    for i,event in enumerate(pair[0]['events']):
        times=[max(0,p['events'][i]['ms']-p['video']['firstFrameMs'])/1000 for p in pair]
        parts += [f'<tr><td>{html.escape(event["name"])}</td><td>{times[0]:.2f}</td><td>{times[1]:.2f}</td></tr>']
    parts += ['</table><div class="pair">']
    for side,sha in [('before',before),('after',after)]:
        parts += [f'<div><h3>{side.title()} — <a href="https://git.cafe/pingdotgg/t3code/commit/{sha}">{sha[:10]}</a></h3><video controls preload="metadata" src="{assets[row+"-"+side+".mp4"]["url"]}"></video></div>']
    parts += ['</div><p>'+html.escape(changes[row])+'</p><img alt="'+row+' Before, After, Diff0 and DiffT full viewport sheet" src="'+assets[row+'-sheet.png']['url']+'"></section>']
parts += ['<section><h2>Complete evidence and links</h2><pre>'+html.escape(section)+'</pre></section><section><h2>Public asset links</h2><ul>']
for name,a in assets.items():parts += ['<li><a href="'+a['url']+'">'+html.escape(name)+'</a></li>']
parts += ['</ul></section>']
(root/'index.html').write_text('\n'.join(parts))
print('Wrote evidence-section.md, index.md, index.html, asset-manifest.md')
