Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
eab2183
ci: add dispatch-only live Web-Vitals workflow to discharge #017
BigSimmo Jul 29, 2026
0f4395b
Merge origin/main into claude/live-web-vitals-workflow
claude Jul 29, 2026
6972f36
fix(web-vitals): count a missing mobile run as a breach
claude Jul 29, 2026
0d88075
Merge origin/main into claude/live-web-vitals-workflow
claude Jul 29, 2026
e2e05ea
fix(web-vitals): measure /documents/search, not the non-existent /doc…
claude Jul 29, 2026
a626ef0
Merge origin/main into claude/live-web-vitals-workflow
claude Jul 29, 2026
d860e63
fix(web-vitals): fail closed on desktop gaps and null metrics too
claude Jul 29, 2026
46c85c4
fix(web-vitals): compute completeness over the whole matrix
claude Jul 29, 2026
07d0fdb
fix(web-vitals): refuse an #017 verdict for a non-production origin
claude Jul 29, 2026
8b3904d
fix(web-vitals): never close #017 on lab metrics; pin Lighthouse exactly
claude Jul 29, 2026
44a4221
fix(web-vitals): reject runs that redirected or errored
claude Jul 29, 2026
93a6955
fix(web-vitals): compare query strings when validating measured routes
claude Jul 29, 2026
a286c40
Merge origin/main into claude/live-web-vitals-workflow
claude Jul 29, 2026
75a2ed8
Merge origin/main into claude/live-web-vitals-workflow
claude Jul 29, 2026
2ba5abc
Merge remote-tracking branch 'origin/main' into claude/live-web-vital…
claude Jul 29, 2026
7ed9785
docs(issues): record the #017 dispatch caveat with the instrument
claude Jul 29, 2026
b345746
fix(web-vitals): gate all verdict prose on the run being able to give…
claude Jul 29, 2026
e2a636c
docs(ledger): record the PR #1391 merge
claude Jul 29, 2026
5516e63
Merge remote-tracking branch 'origin/main' into claude/live-web-vital…
claude Jul 29, 2026
30b1cda
Merge remote-tracking branch 'origin/main' into claude/live-web-vital…
cursoragent Jul 30, 2026
662c332
fix(web-vitals): close open review gaps before merge
cursoragent Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions .github/workflows/live-web-vitals.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: Live Web Vitals baseline

# Ledger #017 gates every client payload decision (#012/#013/#016) behind
# reproducible mobile/desktop Web-Vitals evidence, and the 2026-07-28 latency
# audit added seven more findings behind the same gate. Nothing in the repo
# could produce that evidence: in-sandbox runtime vitals are blocked because the
# production server hard-requires Supabase secrets, and local Playwright timings
# rank routes against each other without discharging #017, which asks for the
# real origin.
#
# This runs Lighthouse against the deployed domain and keeps the JSON, so the
# baseline is reproducible after payload work rather than a one-off screenshot.
#
# Deliberately dispatch-only — no schedule. It is heavier than the plain GETs in
# live-domain-monitor.yml, and the decision it feeds is made a handful of times a
# year, not every six hours. Like that monitor it uses no secrets and no
# providers beyond what any anonymous visitor triggers.
#
# The decision rule is written down BEFORE the numbers are read, so the gate
# cannot be rationalised after the fact:
# mobile LCP < 2.5s AND CLS < 0.1 AND INP < 200ms on every route
# -> close #017 "metrics acceptable"; only the explicitly measured payload
# findings gated by #017 become WONTFIX (e.g. #013 route-chunk weight).
# #016's motion/CSS/waterfall/caching/dynamic-import items stay open
# unless they have separate evidence — LCP/CLS do not validate them.
# any breach
# -> only the breaching route's findings become actionable, ranked by
# measured contribution
# Record the verdict in docs/outstanding-issues.md against #017 either way.

on:
workflow_dispatch:
inputs:
routes:
description: "Comma-separated routes to measure"
required: false
# Every entry must be a real page route. `/documents` is not one — the
# documents segment holds only `search`, `source` and `[id]` with no
# `page.tsx` (see docs/site-map.md), so it would have measured the 404
# document. `/documents/search` is the canonical documents-mode route.
default: "/,/therapy-compass,/documents/search,/dsm,/forms"

permissions:
contents: read

concurrency:
group: live-web-vitals
cancel-in-progress: false

jobs:
measure:
name: Lighthouse against the live domain
runs-on: ubuntu-24.04
Comment thread
BigSimmo marked this conversation as resolved.
timeout-minutes: 30
env:
# Repository variable overrides the default (e.g. a staging cutover),
# matching live-domain-monitor.yml.
LIVE_DOMAIN_URL: ${{ vars.LIVE_DOMAIN_URL || 'https://psychiatry.tools' }}
Comment thread
BigSimmo marked this conversation as resolved.
ROUTES: ${{ inputs.routes }}
# Pinned exactly, not `lighthouse@12`. That range is >=12.0.0 <13.0.0-0,
# so a patch published between a baseline run and its follow-up would
# change metric collection independently of the application and silently
# decalibrate the comparison this workflow exists to make. The resolved
# version is recorded in summary.json so a baseline states its own tooling.
LIGHTHOUSE_VERSION: "12.8.2"
steps:
- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Comment thread
BigSimmo marked this conversation as resolved.
with:
# The job only reads files; nothing pushes. Keeping the token in the
# local git config for the rest of the job buys nothing (zizmor
# artipacked).
persist-credentials: false

- name: Use the repository Node runtime
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"

- name: Normalize the configured origin
run: |
set -euo pipefail
# LIVE_DOMAIN_URL may be configured with a trailing slash (common for
# "site root" variables). Appending "/forms" would then yield
# `https://…//forms`, which the summariser rejects as the wrong page
# after the server canonicalises the double slash.
normalized="${LIVE_DOMAIN_URL%/}"
echo "LIVE_DOMAIN_URL=$normalized" >> "$GITHUB_ENV"
echo "origin -> $normalized"

- name: Confirm the target is reachable before spending a Lighthouse run
run: |
set -euo pipefail
code="$(curl -sSL -o /dev/null -w '%{http_code}' --max-time 30 --retry 2 --retry-delay 10 --retry-all-errors "$LIVE_DOMAIN_URL/")"
echo "root -> $code"
if [ "$code" != "200" ]; then
echo "::error::$LIVE_DOMAIN_URL/ returned $code — not measuring"
exit 1
fi

- name: Measure each route on mobile and desktop
run: |
set -euo pipefail
mkdir -p web-vitals
IFS=',' read -ra route_list <<< "$ROUTES"
for strategy in mobile desktop; do
for route in "${route_list[@]}"; do
route="$(echo "$route" | xargs)"
[ -n "$route" ] || continue
# Filename-safe slug: "/" -> root, "/a/b" -> a-b
slug="$(echo "$route" | sed 's|^/||; s|/|-|g')"
[ -n "$slug" ] || slug="root"
out="web-vitals/${strategy}-${slug}"
echo "::group::$strategy $route"
# One flaky route must not discard the whole run, so a failure is
# a warning here; the summary step fails if NOTHING was produced.
npx --yes "lighthouse@$LIGHTHOUSE_VERSION" "$LIVE_DOMAIN_URL$route" \
Comment thread
BigSimmo marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
--output=json --output-path="${out}.json" \
--preset="$([ "$strategy" = desktop ] && echo desktop || echo perf)" \
--only-categories=performance \
--chrome-flags="--headless=new --no-sandbox --disable-dev-shm-usage" \
--max-wait-for-load=60000 \
--quiet || echo "::warning::lighthouse failed for $strategy $route"
Comment thread
BigSimmo marked this conversation as resolved.
echo "::endgroup::"
done
done

# ROUTES is passed so a route whose Lighthouse run failed above is counted
# as a breach. Grading only the reports that happen to exist would let a
# partially failed run read as "every mobile route passed".
- name: Summarise LCP / CLS / TBT against the decision rule
run: node scripts/summarise-web-vitals.mjs web-vitals "$ROUTES"

- name: Upload the reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: live-web-vitals
path: web-vitals/
retention-days: 30
2 changes: 2 additions & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-07-29 | PR #1377 / claude/latency-findings-impl-s8g01v | 80df35f6cebe5f8a29dbbe98c960c114c64de8c7 | PR #1377 CI/review babysit | Re-synced main after #1378 (DIRTY=staleness). Codex P1+P2 threads resolved. Hosted CI green on d7aa4c6c prior tip; re-running after sync. | prior tip CI: PR required success; Production UI/Unit/Build/Static/Migration success; CircleCI success; merge-tree clean |
| 2026-07-29 | PR #1377 / claude/latency-findings-impl-s8g01v | 8b8d4b8952fc96401116af9e34604c2a3e6e53b4 | PR #1377 CI/review babysit | Merged #1376 from main with real conflicts: kept admission-before-scope + L1-2 REFUTED docs; took #1376 invalidation epochs / empty-scope Server-Timing / stream signal. Codex threads resolved earlier. MERGEABLE; CI re-running. | vitest preamble+rag-cache-invalidation 7/7; check:rag:fixtures 36/21; prior tip PR-required green before #1376 land |
| 2026-07-29 | claude/clinical-design-system-update-e34ca9 | 0cdae091ad92f40e0ad7335b3e2d396c44188a4f | PR #1375 conflict fix + Bugbot | FIXED second CONFLICTING after #1378: took main removal of SelectedDocumentEvidencePanel; retained tracking-eyebrow on surviving document-search-results. Prior DocumentViewerRail + form-detail settlement retained. MERGEABLE; CI re-running. | local: document-search-record-fault + design-token tests; merge-tree CLEAN; prior Production UI PASS on 6903f51f; form-detail e2e 2/2. |
| 2026-07-29 | claude/latency-fixes-2026-07-29 | 96a4c76da12b4478539b44fdc01a59ccfe791890 | prlanded | PR #1376 merged via squash. Content diff against the squash commit is empty and six probes confirmed on main (preambleServerTimingEntries, LoadingPanel fallbacks, Supabase preconnect, Refutation 6, audit corrections, tableFactListProjection). No orphaned commits despite pushing after auto-merge was armed; disarm-push-rearm was used. | verify:pr-local exit 0 on fresh npm ci: 422 files / 4272 tests passed, 3 skipped. prettier clean; docs:check-links 1333 refs; docs:check-index OK. verify:ui NOT run (heavy lock contended). No provider-backed gates. |
| 2026-07-29 | cursor/recent-pr-bugfixes-f30d | 2f48884e1a65e8747164cc4fc8107b4b521f0941 | pr-1374-ci-fix | FIXED: account-switch signed-URL cache clear before setSession; merged main; resolved Codex P1 + CodeRabbit privacy/ui-smoke threads. CI was green pre-push. | vitest auth-signed-url+privacy 22/8; merge-tree clean; eslint touched files |
| 2026-07-29 | cursor/recent-pr-bugfixes-f30d | 96eaf8768ffc369c4fb4ec406f9ea2b00443b734 | pr-1374-merge-main-staleness | merged origin/main; merge-tree clean; GitHub DIRTY was staleness | merge-tree-clean; push |
| 2026-07-29 | PR #1378 / codex/remove-source-overlays (squash) | 6f2f1aa259ad7b554b3bac4e6c24adf2f8d28436 | PR #1378 babysit | MERGED via squash auto-merge. Supersedes prior closeout row that recorded pre-squash tip c3feb4cea34dd1a0d0d675df3e063c95174f2504 (unreachable after squash). Hosted required checks green; unresolved threads 0; Bugbot no open findings. | Hosted PR required/Production UI/Static/Unit/Build/Safety/PR policy SUCCESS; verify:cheap 4273 pass; squash SHA 6f2f1aa2 resolvable |
Expand Down Expand Up @@ -1301,5 +1302,6 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-07-29 | cursor/page-anchored-search-composer-30ee | 7ff134ca7f614db527b8d142676640305533669d | branch-cleanup-deletion-pending | DELETION PENDING — content proven fully on main. Merge-base with main is 79d1c879 and tree(merge-base) equals tree(tip): git diff --name-only 79d1c879 7ff134ca reports 0 files, so the tip introduces nothing beyond a state already in main. Backs no open PR. Deletion blocked by HTTP 403; HEAD recorded here so the later branch-cleanup row can be keyed to it after the ref is gone. Supersedes the aggregate main-keyed row at 855aa291, which recorded no candidate HEADs. | local git only for the content proof: git merge-base, git diff --name-only <merge-base> <tip> (0 files), git diff --name-only origin/main...<tip> (0 files), full history after git fetch --unshallow. PROVIDER-BACKED evidence obtained (GitHub reads, authorised repo-scope): open-PR head cross-check via GitHub API against PRs #1374/#1377/#1384/#1385/#1386/#1387, and GitHub MCP capability inspection. PROVIDER-BACKED MUTATION ATTEMPTED AND REJECTED: git push origin --delete returned HTTP 403; no branch was deleted. No OpenAI/Supabase/hosted-CI calls. |
| 2026-07-29 | cursor/pr-1379-babysit-ledger-9365 | be2de03f855cb7fdfccea4bb74d05eb4c9bf6c61 | branch-cleanup-deletion-pending | DELETION PENDING — content proven fully on main. Merge-base with main is b2740480 and tree(merge-base) equals tree(tip): git diff --name-only b2740480 be2de03f reports 0 files, so the tip introduces nothing beyond a state already in main. Backs no open PR. Deletion blocked by HTTP 403; HEAD recorded here so the later branch-cleanup row can be keyed to it after the ref is gone. Supersedes the aggregate main-keyed row at 855aa291, which recorded no candidate HEADs. | local git only for the content proof: git merge-base, git diff --name-only <merge-base> <tip> (0 files), git diff --name-only origin/main...<tip> (0 files), full history after git fetch --unshallow. PROVIDER-BACKED evidence obtained (GitHub reads, authorised repo-scope): open-PR head cross-check via GitHub API against PRs #1374/#1377/#1384/#1385/#1386/#1387, and GitHub MCP capability inspection. PROVIDER-BACKED MUTATION ATTEMPTED AND REJECTED: git push origin --delete returned HTTP 403; no branch was deleted. No OpenAI/Supabase/hosted-CI calls. |
| 2026-07-29 | claude/latency-findings-impl-s8g01v | 9e2ee65ca0bcce45a3cb6a0539e265ec8d961582 | PR #1377 latency findings — #098 stale offline-harness references | Codex P2 confirmed and fixed: the #098 row in docs/outstanding-issues.md still named test-cache-path.mjs and check-rag-fixtures.mjs as the offline fixtures for the round-trip counting harness. Neither exercises a RAG request (cache paths; fixture-manifest validation), so a harness built on them would count nothing. The audit doc carried the retraction at :358 but this row did not - the same local-retraction pattern flagged in two prior rounds. Now names eval-rag-offline.mjs, test-rag-offline.mjs, rag-offline-contract.mjs and the contract fixture, all verified present, with the correction recorded inline. Docs only. | prettier --check clean; docs:check-links 1363; docs:check-scripts 390; grep confirms no stale refs remain |
| 2026-07-29 | 1391 | ff4ddfabb8d8594da0c09e04dc37e9286745140b | PR #1391 merge | merged as 39f2bcea. Three review P2s fixed: both ui-smoke retries made idempotent (toPass re-runs after a late-landing click; the mode-menu one clicked a toggle and oscillated) plus a second guard so the <768px branch can recover at all, and #111 archived instead of left open-and-done. One Production UI red on ui-phone-scroll.spec.ts:574 was proven a flake before re-running: the delta from the passing head baecef05 was two ui-smoke guards and one docs row, neither reachable from that spec, and the PR touches no document-viewer phone-scroll surface. Re-run green | verify:cheap exit 0 (429 files / 4404 tests); design-token orphan guard proven red on a reintroduced class; PR required success with UI_RESULT success on re-run |
| 2026-07-29 | 1391 | baecef05cac86c4d52af895d483a33ba3c40cd61 | PR #1391 review | reviewed clean — text-4xs retirement confirmed against globals.css (--text-3xs 0.625rem present, --text-4xs absent); orphan guard proven to fail on a reintroduced class; six Playwright retries all retry action-plus-effect so a genuine regression still fails. Resolved the outstanding-issues #108/#109 double-allocation (renumbered to #110/#111, marker to 112) and recorded #111 done | verify:cheap exit 0 (429 files / 4404 tests); design-token-contract 28 passed; check:branch-review-ledger passed |
| 2026-07-29 | 1374 | c14edb9c6f0bdbbfb147752503e016f2543fd803 | PR #1374 review + merge | merged as 3704007c — DocumentViewer identity-bound state clear (P1) implemented and verified red without it; all 12 review threads resolved | verify:cheap exit 0 (429 files / 4403 tests); PR required success; Production UI success |
Loading
Loading