Skip to content

PR: Production readiness improvements and E2E fixes - #105

Merged
BigSimmo merged 7 commits into
mainfrom
feature/production-readiness-review
Jun 29, 2026
Merged

PR: Production readiness improvements and E2E fixes#105
BigSimmo merged 7 commits into
mainfrom
feature/production-readiness-review

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Resolves type/lint warnings, missing BadgeCluster/ClinicalBadge components in the medication workspace, and timing issues in E2E tests. Kept open for review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 227dd0ad0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/rag.ts Outdated
Comment thread src/lib/document-organization.ts Outdated
Comment thread src/lib/rag.ts Outdated
Comment thread src/lib/rag.ts Outdated
Comment thread src/lib/rag.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a3c82c4100

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/rag.ts Outdated
Comment thread src/lib/document-organization.ts Outdated
Comment thread src/lib/rag.ts
Comment thread src/lib/rag.ts Outdated
Comment thread src/lib/document-organization.ts Outdated
Comment thread src/lib/rag.ts Outdated
Comment thread src/lib/rag.ts
@BigSimmo
BigSimmo enabled auto-merge (squash) June 29, 2026 05:38
@BigSimmo
BigSimmo disabled auto-merge June 29, 2026 05:38
@BigSimmo
BigSimmo enabled auto-merge (squash) June 29, 2026 05:45
@BigSimmo
BigSimmo disabled auto-merge June 29, 2026 05:46
BigSimmo and others added 2 commits June 29, 2026 15:00
Tier 1 (reliability):
- logger.ts: redacting structured logger (secrets/PII) wired into http.ts,
  the answer-stream error sink, and upload cleanup
- /api/health readiness probe (config presence + optional deep Supabase check)
- upload magic-number content verification + orphaned-cleanup logging
- audit_logs writes on document upload/rename/delete (src/lib/audit.ts)
- supabase migrations: rag_queries retention purge via pg_cron + audit_logs table

Tier 2/3:
- CI runs test:coverage; vitest coverage thresholds as a regression floor
- production-readiness preflight fails on demo mode in a prod-like env
- tsconfig target ES2017 -> ES2022

Tests: logger, file-signature, health-route, audit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

The verify job was failing because the settings dialog (mobilePlacement="top")
applied animate-sheet-up on mobile, starting the panel at translateY(100%)
off-screen. Playwright captured the bounding box mid-animation at y≈118,
failing the assertion y<=24.

Fix: for defaultSheetIsTopAligned, use animate-pop-in on mobile instead.
The pop-in animation scales from 0.97 at centre, keeping the top edge at
y≈12+height*0.015 (≤23.94 for max-h 796px), which satisfies y<=24 at every
frame.
@BigSimmo
BigSimmo merged commit 3cedd60 into main Jun 29, 2026
6 checks passed
BigSimmo added a commit that referenced this pull request Jul 29, 2026
#1376)

* perf: cut fixed per-request round trips and add preamble Server-Timing

Five latency fixes from the 2026-07-28 audit (docs/audit/latency-audit-2026-07-28.md).
Rebased fresh on main; the index migration that sank PR #1312 is split out.

RAG impact: no retrieval behaviour change — the only src/lib/rag/** edit defers a
process-local cache write off the response path. No scoring, ordering, selection,
alias, or citation logic is touched, and the mid-request staleness guard that
discards the write when the corpus moves is preserved.

- Server-Timing: /api/answer/stream emitted none at all, and stream/route.ts:262
  confirms it is the route the UI actually uses. Adds auth/ratelimit there and to
  /api/search, plus scope on /api/answer. On a streaming route only pre-flush
  stages can reach a header; routing in-stream stages through the SSE contract
  would put instrumentation inside a governed clinical payload.
- Shared-cache hits no longer await the cache write. setCachedAnswer forces an
  uncached documents read, so the fastest path in the system paid a round trip
  before responding. Deferred, not dropped.
- /api/answer overlaps scope resolution with the rate-limit RPC and aborts it on
  deny, so a throttled caller still costs nothing. Threading the signal also
  fixes scope queries never receiving .abortSignal().
- document_table_facts: three select("*") narrowed to explicit projections,
  keeping the generated search_tsv and owner_id off the wire. The PATCH response
  is unchanged — the projection matches the TableFactRow DTO field for field.
- 10 of 11 ssr:false dashboard surfaces had no loading fallback and rendered
  nothing between HTML arrival and chunk execution; all now use the shared
  LoadingPanel. Adds preconnect/dns-prefetch for the Supabase origin, which
  AuthProvider contacts on mount with no connection warm-up.

Ledger #98-#105 record the 19 findings not addressed here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: address Codex P1s and restore docs link CI

Keep resolveSearchScope behind the answer rate limiter so denied callers
cannot enumerate documents/labels, and discard deferred shared-cache
promotions that resume after invalidateRagCachesForOwner via an
invalidation epoch. Drop the outstanding-issues reference to the
unauthored L2-3 migration path that broke docs:check-links.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* docs(ledger): record pr-1376 CI/bugbot repair review

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* docs(ledger): supersede pr-1376 repair record at tip HEAD

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* style: prettier-format files that failed format:check

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* fix: harden cache invalidation epochs and close review follow-ups

Scope invalidation epochs per owner, await shared-cache promotion with
post-write cleanup on raced invalidation, thread the stream abort signal
into resolveSearchScope, emit Server-Timing on empty-scope answers, and
tighten the rate-limit gating test synchronization.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* docs(ledger): record pr-1376 follow-up repair at tip

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* docs: correct three audit claims and record the token-streaming refutation

Corrections found while planning the remaining work — each was verified at the
cited line, and each REDUCES the work the audit implied:

- L3-3 was wrong twice: the Therapy Compass index and full payloads are mutually
  exclusive (mode-home first paint is 707 KB, not 3.16 MB), and the index split
  the finding implied was missing already ships via build-therapies-index.mjs
  with its own gate. The real residual is only the missing /public Cache-Control
  and the proxy matcher not excluding .json.
- L3-6(b) overstated a 3-hop first-paint waterfall. The 4-way fan-out is gated on
  drawer visibility, and readLocalProjectIdentity is a guaranteed no-op off
  localhost. Real production win is ~1 hop.
- L3-6(c) proposed batching as new work, but /api/images/signed-urls already
  exists with zero call sites. Records the hard constraint that signed URLs must
  never reach SearchResult.images[].signed_url, because rag-cache persists
  results into a shared cross-owner cache table.
- The measurement plan named test-cache-path.mjs as an offline fixture; it is the
  vitest/tsc cache-directory helper. Names the real offline surface and the
  vi.doMock seam instead.

Also adds Refutation 6 to docs/rag-behaviour/refuted-approaches.md: token
streaming as a latency fix. Unlike Refutations 1-5 this was not re-attempted —
it is recorded because the defect it would "fix" is real and visible, so the
wrong fix is the one a future task reaches for first. answer-stream-contract.ts
removed token/revising as a clinical-safety control; raw tokens bypass the
numeric-faithfulness gate. Only admissible shape is progressive disclosure of
already-verified sections over the existing progress event.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: keep shared-cache promote off the cold-path response

Awaiting replaceSharedCacheRow inside setCachedAnswer blocked every
generation path on a Sydney DB round trip. Restore fire-and-forget
shared promote while keeping owner-scoped epoch cleanup after commit.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
BigSimmo pushed a commit that referenced this pull request Jul 29, 2026
Confirms the squash merge landed by content: the diff between squash
commit f4a3f50 and branch tip bebc6c0 is empty, so nothing was orphaned
by the auto-merge race despite four concurrent pushes during the merge
window. Also records that the coverage follow-ups were renumbered
#98/#99 -> #106/#107 (main claimed #98-#105 concurrently) with both
rows verified intact, and that the three red `PR required` results were
concurrency cancellations rather than defects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012YRCXgX4AWZ579bKN6sk6b
BigSimmo pushed a commit that referenced this pull request Jul 29, 2026
…iew batch

Eight review findings (1 Codex P2, 7 CodeRabbit), each verified against the
code before acting.

MOST CONSEQUENTIAL — the L2-3 "recall is byte-identical" claim was wrong, and
it was load-bearing. fetchDocumentTitleAliasRows (rag-candidate-sources.ts:482)
applies .limit(12) with no ORDER BY, so which twelve documents return is
plan-dependent; a new index can change the title-alias set feeding candidate
assembly. "No query text changes" is true, but recall does not follow from it.
That claim was the argument for keeping L2-3 out of canary territory, so the
gating is revised: the documents-list and (status,id) uses stay ordering-safe,
the RAG-path index is canary-gated unless the unordered .limit(12) is made
deterministic first — the cheaper fix, since an unordered LIMIT is latent
nondeterminism regardless of this work.

Operator SQL alone never reaches staging, DR, or local replay: migrations/ is
the source of truth and schema.sql only a mirror, so hand-run statements hit
the live database and nothing else, and a required_indexes registration would
fail on every replayed environment. Authoring the migration is now a required
part of #102, following the 20260717170000 idempotent pattern. This PR still
ships no migration (the #1312 objection), but the runbook no longer implies
the operator sequence is sufficient.

Test guard hardened: the ordering case anchored on a fixed 5 ms sleep, which
can expire before the handler reaches the limiter. It now waits for
consumeSubjectApiRateLimit to be entered, then asserts scope is untouched — the
same guarantee without the timing fragility.

Ledger: the 78e2beb record still described the reverted scope-vs-ratelimit
overlap. The ledger is append-only, so this appends a superseding record via
ledger:append --supersede rather than editing the row, per the contract.

Status wording: #102 is "runbook prepared", not done, while the operator steps
are pending; #105 separates shipped implementation from pending browser
verification; #98/#99/#102/#103/#105 restored to the execution queue with
their remaining actions. Fixed the MD038 malformed RAG-impact code span.

Verification: verify:cheap exit 0 (427 files, 4386 passed / 4 skipped);
check:branch-review-ledger pass (1232 records, no duplicates).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
BigSimmo pushed a commit that referenced this pull request Jul 29, 2026
Resolved docs/outstanding-issues.md by keeping this branch's updated #98,
#102, #104 and #105 entries (the ones this PR rewrites) and adding main's new
#108 and #109 rows; the rest of the conflict was prettier re-padding column
widths after a summary-cell length change. next-id stays at 110.

Removed four exact-duplicate branch-review-ledger rows produced by the union
merge driver keeping both copies of rows present on this branch and on main -
the duplication risk tracked as #88.
BigSimmo pushed a commit that referenced this pull request Jul 29, 2026
Resolves the `docs/outstanding-issues.md` conflict. Two independent causes:

1. Rows #98#105 diverged. `main` carries the newer text (PR #1377 landed
   "Done 2026-07-29" outcomes and rewrote several summaries); this branch
   carried the pre-#1377 wording. Took `main`'s rows wholesale — the branch
   made no edits to that range, it simply forked before them.

2. `#108`/`#109` were allocated twice. `main` used them for the verified-landed
   branch deletions and the shallow-clone finding; this branch had used the same
   two ids for the design-system token manifest and the `ui-overlap` flake. The
   `issues:next-id` marker has no concurrency protection, which is exactly the
   failure the branch's own PR notes predicted. Renumbered this branch's two rows
   to #110/#111 and bumped the marker to 112. No content was dropped from either
   side; all 111 rows are unique.

Also records #111 as done, since this branch is what fixes it: the ui-overlap
inset measurement now retries inside `toPass` with the 2px symmetry tolerance
and the assertions unchanged. Leaving it open with a "Next: apply the retry
shape" action would have re-queued work this PR already did.
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Main's #1461 reflowed the open-items table while this branch appended
#17's live Web-Vitals verdict, so the two sides conflicted for real
(`git merge-tree` dirty, not staleness). Resolved row by row rather than
by taking either side: kept this branch's #17 BREACH verdict and #105
preconnect evidence, took main's #86 X3 progress and its new #145 row.

#105 closes. Its remaining half — the `LoadingPanel` fallbacks — is now
verified, and by a different method than the row prescribed. It claimed
the fallback renders solely while the client chunk is in flight, so only
a throttled-network run could observe it. That is wrong: the installed
Next 16 loader wraps an `ssr:false` import in Suspense whenever a
`loading` element is supplied, and `BailoutToCSR` throws on the server,
so the fallback is emitted in the server response HTML. Confirmed
against the running dev server — `role="status" aria-label="Loading"`
appears 1x on `/`, 2x on `/dsm`, 2x on `/forms`. No throttled run and no
`verify:ui` were needed. Finding raised by Codex on PR #1459.

The two recommended-queue entries now match their detail rows: #105 is
dropped from the queue, and #17 no longer directs a reader to capture
evidence that has already been captured and graded — its next action is
ranking the mobile findings by measured contribution and cross-checking
the raw Lighthouse JSON artifact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Second real conflict on docs/outstanding-issues.md, this time in the
archive table: main's #1455 archived #122 while this branch archived
#105. Kept both rows and took main's separator width.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
…d the wrong component

The claim that `LoadingPanel` was verified present in the SSR response HTML
does not hold, so #105 is reopened rather than archived.

The evidence was `role="status" aria-label="Loading"` appearing in the
initial HTML of `/`, `/dsm` and `/forms`. Those hits are real, but that
exact string has exactly one renderer in `src/` — `ModeHomePageSkeleton`
(`src/components/mode-home-page-skeleton.tsx:8`). `LoadingPanel` never
emits a bare "Loading" label; it renders `aria-label={label}`, always a
specific string such as "Loading differentials". Re-checked per route
against a running server, `/`, `/dsm`, `/forms`, `/differentials` and
`/favourites` return zero `aria-label="Loading <something>"` matches.

Codex's mechanism finding on this PR stands and is not what failed: the
installed Next 16 loader does set `hasSuspenseBoundary` when a `loading`
element is supplied, and `BailoutToCSR` throws on the server, so a mounted
`ssr:false` surface emits its fallback into the response HTML. What was
missing is that this only fires for a surface mounted during the initial
render, and none of the eleven in `clinical-dashboard-lazy.tsx` are —
every one sits behind interaction state (`ClinicalDashboard.tsx:3666-4070`).
So neither a cold-load grep nor the throttled check the row originally
prescribed can observe them; that needs a driven browser session.

The reopened row and its queue entry both record the withdrawn claim and
carry an explicit stop rule against re-closing on a cold-load grep. The
preconnect half remains verified and is marked so, to avoid redoing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Fourth genuine conflict on docs/outstanding-issues.md, confirmed with
git merge-tree before acting. Main's #1470 rewrote #13 as
measurement-gated while this branch changed #17, #105 and added #147.
Verified by hash that main touched neither #17 nor #105 — both are
byte-identical at the merge base and at origin/main — so those rows take
this branch's version and #13 takes main's, rather than either side
winning wholesale.

Also de-duplicated against main's new #118, which already owns adopting
the Lighthouse baseline and flipping enforce. #147's gate paragraph now
cross-references it instead of restating it, and contributes the
constraint #118 was missing: pinning a baseline today would bake CLS
0.363 on /dsm in as the accepted value, so the CLS fixes must land first
or the gate ratifies the breach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Fifth consecutive genuine conflict on docs/outstanding-issues.md,
confirmed with git merge-tree before acting.

Main's d8b034c (PR #1441) deleted three rows from the Open items table
and archived none of them: #85, which that PR genuinely resolved by
adding check-upload-limit-parity.mjs; #119, a duplicate of #122; and
#105, which is open and unresolved — its LoadingPanel half is still
unverified. This resolution mirrors main on #85 and #119, which were
dispositionable, and keeps #105, which was not.

#105 survived only because this resolution was done row by row and then
diffed with column padding normalised away. Taking either side wholesale
would have lost it silently, which is what AGENTS.md forbids for this
file and what appears to have happened upstream.

Filed as #148: check:outstanding-issues validates duplicate ids, ids in
both tables, stale markers and malformed rows, but never compares
against the previous revision, so a row that simply disappears is
invisible to it in verify:cheap and static-pr alike. The fix is a
deletion check against the merge base, not a union merge driver — #133
removed that driver deliberately and reinstating it would bring back the
duplicate-row damage it was removed to stop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
BigSimmo added a commit that referenced this pull request Jul 30, 2026
…ation, restore #105 after an upstream deletion (#1459)

* docs(issues): record #105's preconnect verification in a browser

The preconnect half of #105 is verified; the LoadingPanel half is not, and the
row now says which is which rather than carrying one PENDING for both.

Worth recording how it was verified, because the obvious check silently proves
nothing: locally NEXT_PUBLIC_SUPABASE_URL is unset, so supabaseOrigin() returns
null and the layout renders no link at all. Loading the app and finding no
preconnect would have looked like a failure and been an absent fixture. The
check needs a placeholder origin (no real network call — the point is React's
hoisting, not Supabase connectivity).

With that set, both the SSR HTML and the live browser DOM show preconnect and
dns-prefetch in <head> with zero stray preconnect in <body>, so React 19 does
hoist them out of the body where they are authored. crossOrigin="anonymous"
survives the hoist, which matters: without it supabase-js's CORS fetches open a
second connection and the hint buys nothing.

The remaining half needs a different technique. A LoadingPanel fallback only
renders while a lazy chunk is in flight, so it needs a throttled-network browser
check rather than verify:ui, which would race past it.

Gates: outstanding-issues guard 142 rows / unique ids; prettier clean; the edit
is a 1-line diff because it stays inside the Detail column's existing width
(1133 chars against a 3268 budget) — see #133 for why that matters in this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF

* docs(issues): record #17's live Web-Vitals verdict — mobile breaches

Dispatched live-web-vitals.yml against psychiatry.tools (run 30548662649,
5 routes x 3 samples). #17 has been the gate on seven latency findings for
weeks with no evidence behind it. It now has evidence, and it is a breach.

Mobile medians against LCP<2500ms / CLS<0.1:

  root              4308ms  0.023
  therapy-compass   4716ms  0.142
  documents-search  3676ms  0.220
  dsm               3639ms  0.363
  forms             3786ms  0.212

Desktop passes everywhere (LCP 625-694ms, CLS 0.016-0.097), which is worth
noting because desktop-only checking would have concluded the opposite.

The verdict is gradeable rather than noisy: every LCP sample range sits wholly
above 2500ms, so the straddle rule #114 added does not fire. That distinction is
the whole point of #1407's sampling work — a single run per cell could not have
told a real breach from variance.

Applying the rule as written before the numbers were read: any breach makes only
the breaching routes' findings actionable, and NOTHING becomes WONTFIX. That is
the opposite of what a favourable sample would have produced, which is exactly
why the rule was written first.

CLS 0.363 on /dsm is the worst single number and is visible layout shift, not a
byte-count problem — so it is not addressed by the payload findings #17 gates.

Recorded honestly as one step short of complete: this reads the summariser's
emitted table, not the raw Lighthouse JSON (artifact 8762211043, 30-day
retention). The row itself asks for that cross-check before recording a verdict,
most of all a PASS; this is a breach, so the risk of the table flattering the
result is lower, but the check is still outstanding. INP is absent because
Lighthouse cannot measure it in lab conditions.

Diff stays 1 line: #17's Detail cell is 2184 chars against the column's 3268
budget, so nothing repads (see #133).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF

* docs(issues): withdraw the #105 LoadingPanel verification — it matched the wrong component

The claim that `LoadingPanel` was verified present in the SSR response HTML
does not hold, so #105 is reopened rather than archived.

The evidence was `role="status" aria-label="Loading"` appearing in the
initial HTML of `/`, `/dsm` and `/forms`. Those hits are real, but that
exact string has exactly one renderer in `src/` — `ModeHomePageSkeleton`
(`src/components/mode-home-page-skeleton.tsx:8`). `LoadingPanel` never
emits a bare "Loading" label; it renders `aria-label={label}`, always a
specific string such as "Loading differentials". Re-checked per route
against a running server, `/`, `/dsm`, `/forms`, `/differentials` and
`/favourites` return zero `aria-label="Loading <something>"` matches.

Codex's mechanism finding on this PR stands and is not what failed: the
installed Next 16 loader does set `hasSuspenseBoundary` when a `loading`
element is supplied, and `BailoutToCSR` throws on the server, so a mounted
`ssr:false` surface emits its fallback into the response HTML. What was
missing is that this only fires for a surface mounted during the initial
render, and none of the eleven in `clinical-dashboard-lazy.tsx` are —
every one sits behind interaction state (`ClinicalDashboard.tsx:3666-4070`).
So neither a cold-load grep nor the throttled check the row originally
prescribed can observe them; that needs a driven browser session.

The reopened row and its queue entry both record the withdrawn claim and
carry an explicit stop rule against re-closing on a cold-load grep. The
preconnect half remains verified and is marked so, to avoid redoing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF

* docs(issues): rank #17's mobile findings — CLS reproduces exactly offline

#17 asked for the mobile breach to be ranked by measured contribution.
Done, and the measurement turned up something that changes what the work
costs.

Ran the local offline production Lighthouse harness over the same five
routes the live dispatch used. The mobile CLS numbers do not merely
correlate with production — they match run 30548662649 to three decimals
on all four locally measurable routes (0.023 / 0.142 / 0.220 / 0.363),
and the desktop range matches at both endpoints (0.016-0.097). /forms
did not measure locally (Lighthouse NO_NAVSTART, its own "run again"
transient); live it was 0.212.

So CLS on these routes is deterministic layout structure, not network or
production data. The CLS half of #17 is debuggable and gate-able locally
at zero provider cost, and #17's recorded premise that production vitals
need Supabase secrets is stale — run-lighthouse-budget.mjs builds and
serves an offline production app precisely to sidestep that. Its earlier
"dev-mode CLS is excellent at 0.00-0.04" note was measuring dev, which
does not reproduce production.

LCP does NOT reproduce and must not be read from local runs: 2014-2051 ms
local against 3639-4716 ms live, because the loopback server has no
network latency. The LCP ranking does survive — /therapy-compass is the
outlier both locally and live — which corroborates #117.

Filed as #147 with the ranking (/dsm 0.363 worst, then /documents/search
0.220, /forms 0.212, /therapy-compass 0.142, / 0.023 passing) and the
next step: Lighthouse's layout-shift-elements audit returned zero items
on every route, so element attribution needs a PerformanceObserver on
layout-shift against the same offline build.

Two reproduction traps recorded with it: the harness reads CHROME_PATH
but nothing in this container sets it, so chrome-launcher fails on every
route; and a truncated `progress` package in the npx cache broke
Lighthouse startup entirely. No budget baseline was committed — the
grader correctly refused this run as incomplete evidence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF

* docs(review): record PR 1459 final reconciliation

---------

Co-authored-by: Claude <noreply@anthropic.com>
BigSimmo pushed a commit that referenced this pull request Jul 31, 2026
Resolves the fifth conflict on docs/outstanding-issues.md by taking
main's side on both hunks, after verifying that was correct rather than
convenient: main renumbered the queue to fix a duplicate rank 18 this
branch introduced, and it archived #138 and #146 rather than dropping
them (confirmed present in the archive table). The resulting id set is
byte-identical to main's.

#150 escalated. That row records CodeRabbit reviewing none of a full
day's PRs and rests explicitly on the Codex connector being the
surviving reviewer that "found three real defects that had survived
local gates". On PR #1505 Codex posted its own usage-limit notice
alongside CodeRabbit's spending-cap notice, so that PR received zero
automated review and so will anything opened while both caps hold. This
is the same issue with its fallback removed, not a new one — which
matters because the single Codex finding on #1459 was correct and
caught a verification that had matched the wrong component, closing
#105 on bad evidence. Local gates did not catch it.

The attribution harness lands as scripts/measure-cls-attribution.mjs.
#147's next step needs a before/after CLS pair, and #118 will want
element attribution again; without this, both mean re-deriving it. Build
and serve mirror run-lighthouse-budget.mjs so the numbers sit beside the
Lighthouse reports. Two traps that cost real time are encoded rather
than left to be rediscovered: CHROME_PATH must be set where the browser
is outside a standard location, and an init script attaching a
MutationObserver to document.documentElement before <html> exists throws
and silently takes the CLS observer with it — so the script now fails
loudly on an all-zero result instead of reporting a false clean bill.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants