Skip to content

perf: SSR-safe lazy loading, loading skeletons, and SourceImage memo - #147

Merged
BigSimmo merged 1 commit into
mainfrom
perf-improvements-v2
Jul 2, 2026
Merged

perf: SSR-safe lazy loading, loading skeletons, and SourceImage memo#147
BigSimmo merged 1 commit into
mainfrom
perf-improvements-v2

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

Re-implements the performance improvements from PR #133 with the critical SSR bug fixed.

Root cause of original revert (PR #145)

Using
ext/dynamic({ ssr: false })\ directly in Next.js App Router Server Component pages throws:

'ssr: false is not allowed with next/dynamic in Server Components'\

This caused 500 errors on /\ and /documents/[id].

Fix approach

Create 'use client'\ wrapper components that call \dynamic(..., { ssr: false })\ internally. Server page files import these wrappers — the wrapper is a Client Component so \ssr: false\ is legal.

Changes

New files

  • \src/components/clinical-dashboard-lazy.tsx\ — Client wrapper for ClinicalDashboard with \ssr: false\
  • \src/components/document-viewer-lazy.tsx\ — Client wrapper for DocumentViewer with \ssr: false\
  • \src/app/loading.tsx\ — Route-level loading skeleton for /\
  • \src/app/documents/[id]/loading.tsx\ — Route-level loading skeleton for document pages

Modified files


  • ext.config.ts\ — Added \optimizePackageImports: ['lucide-react']\ under \�xperimental\
  • \src/app/page.tsx\ — Imports \ClinicalDashboardLazy\ wrapper instead of direct import
  • \src/app/documents/[id]/page.tsx\ — Imports \DocumentViewerLazy\ wrapper instead of direct import
  • \src/components/ClinicalDashboard.tsx\ — Wraps \SourceImage\ in \React.memo\

Validation


  • px tsc --noEmit\ — passes (pre-existing mockups/ errors unrelated to this PR)

  • pm run lint\ — passes (0 errors, 1 pre-existing warning in unrelated file)
  • Pattern validated against commit \d1f2de4\ which used the same approach

- Add 'use client' wrapper components for ClinicalDashboard and DocumentViewer
that use next/dynamic with ssr:false — avoids the Server Component restriction
- Update page.tsx routes to import the lazy wrappers instead of direct imports
- Add route-level loading.tsx skeletons for / and /documents/[id]
- Wrap SourceImage in React.memo to avoid unnecessary re-renders
- Add optimizePackageImports for lucide-react in next.config.ts
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@BigSimmo
BigSimmo merged commit e2ac30c into mainJul 2, 2026
3 checks passed
BigSimmo added a commit that referenced this pull request Jul 2, 2026
* fix: close type-honesty gaps from PR #131 typed-client migration
- planDocumentName: select the metadata column so the stored
smart_title_group_key actually participates in duplicate-title
grouping (the typed select silently dropped it, leaving the
metadata branch dead on the DB-backed path)
- rag.ts: derive RagQueryInsert from the generated rag_queries Insert
type instead of a hand-written interface, removing the whole-row
cast and the source_chunk_ids null lie (column is NOT NULL)
- rag.ts: validate document_images.bbox jsonb as a [x0,y0,x1,y1]
tuple at the read boundary instead of blind-casting Json
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: make npm run format:check pass again
PR #131's generated database.types.ts and PR #147's
document-viewer-lazy.tsx both fail the prettier gate on main.
Ignore the generated Supabase types file (regeneration would keep
fighting prettier) and format the lazy viewer wrapper.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: prettier-format files brought in unformatted by main churn
Whitespace/wrapping only; keeps npm run format:check green after
merging origin/main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: empty commit to trigger required checks (verify/Gitleaks/ui-smoke)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: address Codex review on PR #174
- document-naming: also match a renamed document by its current title, not
only the stored smart_title_group_key (renames update title but preserve
metadata, so the stored key can be stale and miss a duplicate)
- image-filtering: normalizeImageBbox rejects non-numeric coordinates
(null/""/false) instead of Number()-coercing them to a bogus 0
- tests: cover stale-group-key rename matching and non-numeric bbox entries
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
BigSimmo added a commit that referenced this pull request Jul 2, 2026
…ndering) (#178)
* test: stabilize release-browser-matrix under client-only rendering
The dashboard/viewer became client-only via dynamic(ssr:false) (#144/#147),
so "page loaded" no longer implies "app mounted". Firefox/WebKit paint the
client chunk later than Chromium, so three release-browser-matrix specs raced
and failed on main while Chromium stayed green. All three are test-timing
gaps, not product regressions.
- ui-overlap: wait for the mounted header#search instead of networkidle;
every failure was "header#search not found" (count 0), never a real overlap.
- ui-tools (forms detail search): the shell re-syncs query from the URL on
mount via requestAnimationFrame, which on FF/WebKit lands just after a
programmatic fill and wipes it / drops the submit. Drive fill->submit->route
as one toPass unit until the search navigates.
- ui-stress (desktop evidence panel): assert the <summary> is focused before
pressing Enter so the <details> reliably toggles open in WebKit.
Validated locally in Firefox + WebKit (ui-overlap all widths, ui-stress both
viewports, ui-tools:264 repeated x2 per browser). No product code changed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: make forms-search retry idempotent after late navigation
Address Codex P2: if the click navigates just after the inner 2s URL wait
times out, the toPass retry would re-run on the results page where the
detail-page input no longer exists and fail a genuinely-successful nav.
Short-circuit the retry to success once the URL has already routed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@BigSimmo
BigSimmo deleted the perf-improvements-v2 branch July 2, 2026 16:31
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
The merge of origin into this branch hit exactly the damage #133/#140
describe: merge=union concatenated both sides of the ledger rather than
merging it.
Two collisions, both repaired without dropping either side's rows:
- Another agent had already allocated #141-#144 on main for different
items while this branch used #141-#143. The incoming rows renumber, per
the ledger rule, so the capture becomes #145 (adopt a consolidated
answer-home notice block), #146 (answer mode ships no verify-before-use
caveat) and #147 (verify:pr-local exits 0 when its build step refuses to
run). Their cross-references were updated to match, and the two
duplicated next-id markers collapse to one at 148.
- #140 appeared in both tables: it was closed on main as a duplicate of
#133 (PR #1444) while this branch still carried it open. The resolution
is honoured — the stale open row goes, the archive row stays.
check:outstanding-issues: 145 rows (72 open, 73 archived), unique ids,
next-id=148 above the highest, no merge driver.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPyFcMfn1jMmphr6AqiWBg
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Resolves the one conflicted file, docs/outstanding-issues.md. With
merge=union removed by #1444 the ledger now conflicts honestly instead of
silently doubling, so this is a real resolution rather than a repair.
main had allocated #145 for "a branch sync during in-flight CI cancels a
near-complete run" while this branch used #145-#147 for the answer-home
notice capture. Per the ledger's own rule the incoming rows renumber: main's
#141-#145 are kept verbatim and the capture becomes #146 (adopt a
consolidated answer-home notice block), #147 (answer mode ships no
verify-before-use caveat) and #148 (verify:pr-local exits 0 when its build
step refuses to run), with their cross-references updated to match and the
marker lifted to 149. No row was dropped from either side — main's #141-#145
each appear exactly once, verified against origin/main.
check:outstanding-issues: 146 rows (73 open, 73 archived), unique ids,
next-id=149 above the highest, no merge driver.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPyFcMfn1jMmphr6AqiWBg
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Third ledger collision in a row, same shape as the last two: main allocated
#146 for the `ui-phone-scroll` Services anchor issue while this branch used
#146-#148 for the answer-home notice capture. Main's row is kept and the
incoming rows renumber to #147 (adopt a consolidated answer-home notice
block), #148 (answer mode ships no verify-before-use caveat) and #149
(verify:pr-local exits 0 when its build step refuses to run), with their
cross-references and the marker (150) following.
Verified against origin/main that #141-#146 each still appear exactly once;
nothing was taken wholesale from either side.
check:outstanding-issues: 147 rows (74 open, 73 archived), unique ids,
next-id=150 above the highest, no merge driver.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPyFcMfn1jMmphr6AqiWBg
BigSimmo added a commit that referenced this pull request Jul 30, 2026
The id this branch allocated was taken by main three times running (#135 ->
#141 -> #145 -> #147), and the third merge conflicted as one hunk covering the
entire open-items table, so main's table was taken wholesale and the branch's
deltas re-applied by script.
Two further findings: the GitHub Update-branch button auto-merged this file into
duplicate #141 rows with the marker left below main's highest id, a head that
would have failed check:outstanding-issues; and removing merge=union did not
reduce collision frequency, it converted silent duplication into loud conflicts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
…line
#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
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 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.
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 added a commit that referenced this pull request Jul 30, 2026
…#1481)
* issues: capture the production live-region defect; record the landed #1457 review
Adds #147 — `search-results-header-band.tsx` sets aria-live="off" when faulted,
so a clinical search that fails while focus is elsewhere is announced to nobody
and the user is never told Retry appeared. Affects all twelve call sites that
render the band. Raised by Codex against the copied line on PR #1457, fixed
there, and confirmed to exist unchanged in production. The merged mockup carries
the proven pattern and a non-vacuous assertion to port.
Appends two further data points to #146 from PR #1457: the Services viewport
anchor failed once more on head c739340 (expected -138, received -7) and then
passed on two later heads with the diff byte-identical — six data points, two
failures, shard 1 only. Both failures landed on a PR touching only mockups,
which strengthens the unchanged-code reading. Notes that the 131px delta is
roughly 2x the 64px shrink rather than sub-pixel drift.
Records the prlanded verification for PR #1457 in the branch review ledger:
squash e79e499, content diff against the branch tip empty, and the late
aria-live/role="alert" commit confirmed present on main rather than orphaned by
the squash.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JdPa3mHCX5ZQZZvU5GHU3r
* issues: withdraw #149 — the live-region defect was false, and the fix was worse
Codex caught this on PR #1481 and it is correct. I filed #149 claiming a failed
clinical search is announced to nobody because the count span sets
aria-live="off" when faulted. I never checked whether another node makes the
announcement. It does.
search-results-header-band.tsx mounts a fault panel with role="alert" carrying
the failure title, body and Retry (lines 407-414), and the mute is deliberate,
documented in place: "While faulted the live region is silenced
(aria-live='off') and the freshly-mounted fault role='alert' below makes the
single announcement, rather than both speaking."
tests/search-results-header-band.dom.test.tsx already pins exactly that with
singular role queries that throw on duplicates.
Escalating the count span in production, as #149 recommended, would have added a
second alert beside the fault panel — a duplicate announcement and a broken
test. The row is withdrawn to the archive rather than deleted, with the reasoning
recorded so nobody re-files it.
The mockup is unaffected: search-refine-adaptive-mockups.tsx has no fault panel,
so there the count span is the only announcement channel and its escalation is
correct. It simply does not port to production.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JdPa3mHCX5ZQZZvU5GHU3r
* docs: record PR 1481 review
* docs: preserve issue 149 allocation
* docs: format withdrawn issue record
* issues: restore the #146 data points lost while preserving the #149 record
The two Services viewport-anchor data points from PR #1457 were dropped when the
withdrawn #149 row and its id allocation were restored. They are unrelated to
that decision, so this puts them back and changes nothing else.
#149 stays exactly as set: archived as a withdrawn record, with issues:next-id
preserved at 150 so the id is retired rather than reused.
Restored to #146: the test failed once more on head c739340 (anchorTop expected
-138, received -7) then passed on 9da02d9 and a6f2281 across all three shards
with the spec byte-identical — six data points, two failures, shard 1 only, both
failures on a mockups-only PR. Notes that the 131px delta is roughly 2x the 64px
viewport shrink rather than sub-pixel drift, which constrains the element
attribution that row already asks for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JdPa3mHCX5ZQZZvU5GHU3r
---------
Co-authored-by: Claude <noreply@anthropic.com>
BigSimmo added a commit that referenced this pull request Jul 31, 2026
* issues: capture the unreadable-CI token, at-risk worktree work, and the unpushed hook fix
Three findings from the 2026-07-30 organisation session that were recorded
nowhere durable:
- #149 the session GitHub PAT lacks Checks: Read, so no agent can confirm a PR
is green. The endpoint that does work returns an empty result rather than an
error, so it reads like an absence of checks rather than an absence of
permission.
- #150 four worktrees on already-merged branches hold uncommitted work that
exists in no branch and no PR, the largest being +395/-200 across 19 files
including CI config.
- #151 the pre-commit fail-open for #143 lives only on a never-pushed local
branch, which is also 17 behind main and conflicts on the file whose count
sentence main's new docs:update generator now owns.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(ledger): record the session-followup capture review for PR #1490
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(ledger): record #143/#151/#149 reconciliation for PR #1490
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* docs(ledger): supersede PR #1490 reconciliation after remote sync
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* issues: record the worktree snapshots and redirect #151 to PR #1494#150 — the four at-risk worktrees were snapshotted onto their own already-merged
branches (748ef018f, 5dbd9f965, b7eae51a4, d949859c3), so the work survives a
worktree reclaim. All four are clean now. None is pushed or reviewed; the next
action is per-snapshot promote-or-reset.
#151 — the never-pushed branch is superseded rather than salvageable: its script
and hook reached main by other routes, so the fail-open guard was applied to
main's committed hook in PR #1494 instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: remove credential metadata and correct audit dates
* docs: consolidate session follow-up findings
* docs: record consolidated follow-up review
* issues: record that #101 hydration shipped
PR #1463 merged as dba7356, so #86's "Next X3 unit — rag-hydration.ts" is
now stale. The row records the extraction as shipped and keeps the corrected
boundary: hydration re-homed only two of prepareCoverageGateResults's five
rag.ts-only dependencies, so it did not unblock that function — exactly as the
Codex review on PR #1461 predicted.
This row was deliberately dropped from #1463 itself (commit 6290d02) after
docs/outstanding-issues.md conflicted on five consecutive main syncs. Recording
it separately here is the same pattern used for #1454 via #1461.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GGEBHp4Seoh1jK1vGTNtYS
* docs(ledger): record the landed X3 hydration review
Appended with npm run ledger:append (never hand-written), keyed to the squash
commit dba7356 so ledger:lookup can resolve it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GGEBHp4Seoh1jK1vGTNtYS
* docs: fix the #101 mislabel and key the ledger row to a resolvable ref
Both defects were raised by Codex on PR #1495 and both are real; verified
against the files before accepting.
1. #101 is NOT this extraction. docs/outstanding-issues.md:138 shows #101 is
"Canary-gated retrieval parallelisation candidates" (P3, rec) — a separate,
still-open recommendation gated on a live canary pair. Calling the hydration
extraction "#101" marked that unrelated work as shipped and could have caused
the live-evaluation work to be skipped. The label came from the original task
brief and was propagated without checking it against the ledger. Both the
#86 row and the X3 work-order entry now identify the change as the X3
hydration unit (PR #1463) instead. #101's own row is untouched and still open.
2. The ledger row did not resolve. `npm run ledger:lookup --
dba7356` returned NOT REVIEWED, because the
ref cell held only the slash-form branch token and that branch no longer
resolves locally, so the throttling record could not prevent a repeat review.
Appended a superseding record keyed to the landed SHA; the same lookup now
returns ALREADY REVIEWED. The original row is retained, per the ledger's
append-only rule.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GGEBHp4Seoh1jK1vGTNtYS
* docs: record consolidated PR reviews
* docs: record ingestion recovery review
* docs(visual): document the platform-scoped baseline layout and how to seed it
`playwright.visual.config.ts` records snapshots under
`__screenshots__/{platform}/`, so a baseline taken on Windows lands in `win32/`
and is never consulted by the `ubuntu-24.04` CI job, which reads `linux/`.
Nothing said so, and committing `win32/` images looks like protection while
providing none.
Records the constraint, names the CI artifact as the supported recorder for
`linux/` baselines, and notes that comparison stays advisory until the jobs come
off `continue-on-error`. Also creates the tracked directory `.gitignore` already
claims exists, which sets `ui_changed=true` (`scripts/ci-change-scope.mjs`) so
the visual job can run and produce that first artifact.
No baselines are added here — they cannot be produced on this platform.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct visual baseline adoption steps
* docs: record visual baseline guidance review
* fix(ui): repair mockup accent token references
* docs: record token-reference repair review
* docs: archive advisory UI scoping task
* docs: record advisory UI closure review
* issues: archive #151 after #1494 and mark #143 fully resolved
PR #1494 landed the fail-open guard on main, so close the open salvage
row and update the #143 archive from PARTIAL to resolved across #1442
and #1494. Also carries the merge of origin/main that cleared the
GitHub DIRTY mergeability state.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* docs(ledger): record PR #1490 main-sync and #151 closeout
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* docs(ledger): record #1496 id-collision renumber for PR #1490
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* issues: record the withdrawn live-region finding as #151 so it is not re-filed
Archive-only row. There is no defect and no work to do — the row exists purely
as a guard rail against repeating a misreading that already happened once.
search-results-header-band.tsx sets aria-live={faulted ? "off" : "polite"} on
its count/status span, which reads like a silenced failure announcement. It is
not: the band mounts a separate fault panel with role="alert" carrying the
failure title, body and Retry, and the mute is deliberate so the two do not both
speak. The reasoning is in a comment directly above the attribute, and
tests/search-results-header-band.dom.test.tsx pins it with singular role queries
that throw on duplicates.
During session 2026-07-30 (PR #1481) this was filed as a real P2 defect on the
strength of the attribute alone, and the proposed fix — escalating the count span
to role="alert"/aria-live="assertive" — would have produced a duplicate
announcement and a red test, making it worse than no change. Codex caught it.
An earlier withdrawal row was then lost to the squash that merged #1481, which
is the row-deletion shape #148 now guards against.
Also records that the mockup's escalation is correct in the mockup and must not
be ported: search-refine-adaptive-mockups.tsx has no fault panel, so there the
count span is the only announcement channel.
#148 needed no work — the merge-base deletion check landed on main
independently, and its output now reports the base it compared against.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JdPa3mHCX5ZQZZvU5GHU3r
* docs(rag): record refuted lexical probe collapse (#98)
* issues: capture the residual id-allocation hazard as #151#133 is resolved: #1444 removed merge=union and #1479 excluded the ledger from
Prettier, which together fixed conflict frequency. Neither changes id
allocation, which is still read-modify-write against the next-id marker, so
concurrent branches still claim the same number.
Measured on PR #1451: one row was renumbered #135 -> #141 -> #145 -> #147 ->
#149 across four sync cycles. The sharper finding is that GitHub's Update-branch
button resolved one such collision into duplicate #141 rows with the marker left
below main's highest id — git reported success and only
check:outstanding-issues caught it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(issues): attribute the mobile CLS breach — a 128px reserve round trip
#147 asked which elements shift. Driving Chromium against the same
offline production build with a PerformanceObserver on layout-shift
(Lighthouse mobile emulation, reading entry.sources[].node) gives one
dominant cause on all four breaching routes: the entire main content
region moves down 128px and straight back up 128px within 15-60ms. Both
moves score, so it is pure cost with zero net movement — 100% of
/documents/search's 0.220 and about 75% of /dsm's.
The shifting element is the max-sm:pt-[var(--phone-overlay-chrome-h)]
wrapper around <main>. A MutationObserver timeline on the root style
attribute pins the mechanism rather than inferring it: the property goes
CSS seed -> 200px -> 72px, and the 200px is written when the header
stack ALREADY measures 72px (t=1552ms reserve=200px stack=72, corrected
at t=1612ms). usePhoneOverlayChromeReserve reads stack.offsetHeight
while the stack is transiently tall, publishes a value that is stale by
the time it lands, and its ResizeObserver then corrects it.
The CSS seed at globals.css:375 is correct for the settled stack, which
corrects the mechanism recorded on the now-archived #130 — that framed
the defect as the seed under-reserving by 0-8px. Measured, the driver is
a 128px transient over-reserve written by the hook, not the seed. / is
the control: it never writes the property and is the one clean route.
Variance is stated rather than smoothed: /dsm measured 0.363 and 0.219
across two runs, and this harness has no network throttling so /forms
and /therapy-compass run high locally. Only /dsm, /documents/search and
/ reproduced the live dispatch exactly.
Also recorded: attaching a MutationObserver to document.documentElement
inside a Playwright addInitScript throws before the document element
exists, silently killing the CLS observer and reporting a uniform
CLS=0.000 — a false clean bill that voided one run of this harness.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01361jh3eYVjJCzXWjAhdZiF
* docs(ledger): record the #151 capture review for PR #1506
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(review): clarify snapshot branch state
* docs(ledger): record PR #1490 main sync after snapshot wording
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* docs: archive rendered style contract task
* docs: record style contract closure review
* docs: record synced style contract review
* docs: record post-121 style closure review
* docs: normalize style review ledger after sync
* docs: record post-1490 style closure review
* docs: record consolidated PR 1490 review
* docs: record replacement consolidation review
* docs: record reconciled consolidation review
* docs: record post-1511 consolidation review
* docs: normalize PR 1510 ledger after main sync
* docs: record PR 1510 post-sync review
* docs: correct false #98 canary evidence and NOTES triage
Remove the incorrect probe-collapse canary attribution from #98 and
point the unread --med-accent-soft note at #157 without breaking the
seven-token TOKENS_MISSING accounting.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* docs(ledger): record PR #1510 evidence-correction review
Supersede the prior approve-with-no-findings row after correcting the
false #98 canary attribution and NOTES triage drift.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* docs: keep concurrency note inside issue table
* docs: record post-1513 consolidation review
* docs: address CodeRabbit notes on PR #1510
Fix the computed-value-time wording in design-sync notes, give #33 a
unique recommended-queue order, and drop the duplicated #98 Done block.
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
* docs(ledger): record PR #1510 CodeRabbit fix review
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 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
BigSimmo pushed a commit that referenced this pull request Aug 12, 2026
…re-scope the wave plan
Archived with source evidence:
- #166 the verify-before-use caveat ships (verification-notice.tsx:52, rendered
unconditionally by AnswerCard at answer-card.tsx:108). I reported this row as
open twice before reading the code; the archive says so.
- #208 buildAnswerClipboardText composes renderCopyText rather than replacing
formatAnswerRenderCopyText (answer-copy-payload.ts:121-138).
- #216 AnswerCard is adopted on the answer surface and the manifest agrees.
Re-scoped #250: its wave plan names A1 rows that are now closed and hygiene
rows that are in flight, so as written it misdirects. Recorded what actually
remains (#147, #117, #118, #98, #189).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017paT42ZVMf8jaLtkjFxdy5
BigSimmo pushed a commit that referenced this pull request Aug 12, 2026
Measured, not assumed. Two independent runs of the offline harness this row
specifies, on Chromium 141 — the same browser that produced the row's original
figures, so before/after is like-for-like. Mobile CLS (run1/run2 vs recorded):
/dsm 0.035 / 0.035 was 0.363
/documents/search 0.000 / 0.000 was 0.220
/ 0.013 / 0.013 was 0.023
/forms 0.081 / 0.081 was 0.310 local
/therapy-compass 0.000 / 0.000 was 0.240 local
The 128px reserve round trip is gone. The cause was fixed by PR #1616, not by
this session: use-phone-overlay-chrome-reserve.ts keeps the CSS seed until an
observer candidate holds across an 80ms quiet window and publishes only via
ResizeObserver — both remedies the row proposed — and its comment at :61 cites
the row by number.
Two controls make this a pass rather than the false clean the row warns about:
values are not uniformly 0.000, so the observer is live; and the /dsm variance
the row recorded (0.363 vs 0.219) has itself vanished, which is what you expect
once the transient write causing it stopped.
Also recorded: #118 updated — the Lighthouse browser drift is 141-vs-151, wider
than the 150-vs-151 the row states, so the budget cannot grade from this
container at all. New row #308 for desktop /documents/search CLS 0.119, stable
against a 0.119 baseline, above threshold, and outside #147's mobile scope —
which also makes the row's "desktop passes everywhere 0.016-0.097" stale.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017paT42ZVMf8jaLtkjFxdy5
BigSimmo pushed a commit that referenced this pull request Aug 12, 2026
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BigSimmo added a commit that referenced this pull request Aug 17, 2026
* docs(issues): queue inbox request for mobile CLS regression on /
PR #2050's Lighthouse budget check surfaced a real, reproducible mobile-root
CLS regression (0.236 vs the committed 0.013 baseline) unrelated to that
PR's diff. Cross-references the previously-resolved#147/#118 CLS work so
it isn't lost; reconciliation into the canonical ledger happens separately.
* docs(ledger): record review for PR #2059 (CLS regression issue log)
---------
Co-authored-by: Claude <noreply@anthropic.com>
BigSimmo pushed a commit that referenced this pull request Aug 21, 2026
The prior commit on this branch (7455b14) misdiagnosed the CLS source:
it assumed the entrance transform on a freshly-mounted notice card was
the cause, but Chromium's Layout Instability API does not score a
brand-new element's entrance regardless of which CSS property animates
it — confirmed empirically and by review. That fix left the actual
mechanism untouched, and Lighthouse budget still failed on this
branch's head (3dbf548) after it shipped.
Reading the real failing Lighthouse mobile-root report (from CI on
PRs #2199/#2204) shows the true source: `.pwa-notice-stack`
(`layout-shifts` audit, score 0.223) captured mid-shift displaying the
install-prompt card, at the exact box the offline card previously
occupied. `.pwa-notice-stack` is `position: fixed`, so when the set of
visible cards changes while the stack is already on screen — e.g. the
offline card clears the same React commit that reveals the
connection-restored/install cards, both driven by one `online` event —
the stack's height changes while already painted, which is a real,
attributable shift.
`useSettledNoticeSignature` forces every transition between two
different non-empty card combinations through one fully-unmounted
frame, so the stack only ever grows from nothing or shrinks to
nothing, never resizes with a sibling card still on screen. Reverts
the ineffective opacity-only keyframe change and its source-contract
test from the prior commit.
Verified with scripts/measure-cls-attribution.mjs (repo tooling from
ledger #147) driving real headless Chromium: a synthetic offline→online
blip while an install prompt is pending reproduced `.pwa-notice-stack`
as a 0.163 shift source pre-fix, matching the CI magnitude; with this
fix applied under the identical trigger, `.pwa-notice-stack` no longer
appears as a shift source at all.
RAG impact: no retrieval behaviour change — this only touches PWA
notice-stack rendering and its entrance animation, no rag/, retrieval,
or ranking surface.
Sign up for freeto 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.

1 participant

@BigSimmo