Skip to content

fix(text): repair extraction glyph artifacts app-wide and route all document-derived text through formatters - #121

Merged
BigSimmo merged 6 commits into
mainfrom
claude/clever-heisenberg-9a53a9
Jul 2, 2026
Merged

fix(text): repair extraction glyph artifacts app-wide and route all document-derived text through formatters#121
BigSimmo merged 6 commits into
mainfrom
claude/clever-heisenberg-9a53a9

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fixes poorly formatted document-derived text across the app: PDF-extraction glyph artifacts (typographic ligatures fi fl, soft hyphens, zero-width/control characters) no longer reach any rendered surface.
  • Adds one shared, lossless primitive — normalizeExtractedGlyphs in source-text-sanitizer.ts — wired into the base compactWhitespace/readableWhitespace cleaners so all ~20 downstream formatters inherit the repair at once. Deliberately does not rejoin line-break hyphenation: a soft-wrap hyphen is indistinguishable from a real compound hyphen (low-dose, twice-daily), so fusing would corrupt clinical compounds (caught by adversarial review, with regression tests).
  • Routes previously-raw render surfaces through formatters: exact quote body and quote clipboard copy (sourceTextForVerbatimQuote — verbatim wording, artifacts repaired), citation labels (formatCitationLabel/formatCompactCitationLabel now glyph-clean titles centrally, fixing mobile labels + ward-output/smart-rag/clinical-safety), document titles (cleanDisplayTitle), visual-evidence labels, table snippets, image captions/alt, evidence-map details, and AccessibleTable cells/captions.
  • Provenance rows drop unknown-filler segments ("Publisher unknown · Jurisdiction unknown · review Unknown") while always keeping clinical governance warnings; the clipboard provenance line stays fully explicit (audit artifact — reconciled with the main-branch test asserting this).
  • Ingestion (buildChunks) normalizes page text once, keeping stored chunk content and source-span offsets consistent so citation anchoring is unaffected.
  • New scripts/backfill-text-normalization.ts (npm run backfill:text-normalization): in-place stored-text cleanup, no re-index, no re-embed (vectors frozen → retrieval unchanged by construction), dry-run by default, requires --write --confirm, writes a revertible JSON backup, refuses to run against the wrong Supabase project. Not yet run against production.
  • Centralizes static UI copy (empty states, error messages, starter prompts) in src/lib/ui-copy.ts; adds tests/rendered-text-formatting.test.ts as a static guard against reintroducing raw interpolations; conventions documented in docs/process-hardening.md.
  • Merged latest main (mode homes, Clinical White restyle, relevance-first selection) with conflicts resolved so design changes win on structure/styling and formatter wiring is preserved (AnswerEmptyState now feeds ModeHomeTemplate from ui-copy.ts).

Verification

  • npm run verify:cheap (runtime check, eslint, typecheck, 787 unit tests) — pass on the merged branch
  • npm run verify:ui — not run locally (no env in this worktree); relying on the CI Chromium UI gate
  • npm run verify:release — not claimed
  • npm run format:check — all files touched by this PR are clean; 9 remaining warnings are pre-existing drift from main (rag.ts, rag-provider.ts, docs, unrelated tests), intentionally not reformatted here
  • npm run check:production-readiness — not run locally (no env in this worktree); no environment, Supabase config, or deployment behavior changes are included
  • npm run check:deployment-readiness — n/a, no deployment behavior changes

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use — no changes to grounding/verification logic; display formatters only repair extraction artifacts and never alter clinical meaning (guard tests prove dose strings, ranges, and comparison symbols survive untouched)
  • No patient-identifiable document workflow was introduced or expanded
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy) — the backfill script hard-fails on any other project
  • Service-role keys and private document access remain server-only — backfill runs server-side via the admin client; no client-side changes to access
  • Demo/synthetic content remains clearly separated — Synthetic title prefix is stripped only for display labels, not from stored data
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative — governance warnings ("Review status unknown", "Not locally validated") are always shown even when filler segments are dropped; the copied provenance line remains fully explicit for audit
  • Deployment classification/TGA SaMD impact — no clinical decision-support behavior changed; this is display formatting, copy centralization, and a lossless stored-text repair

Notes

  • The stored-text backfill is included but not executed; run order when ready: npm run check:supabase-projectnpm run backfill:text-normalization (dry-run prints changed-chunk count + samples) → --write --confirm.
  • Follow-ups tracked in docs/process-hardening.md: stale index_quality_issues/extraction_quality metadata recompute; source-metadata governance debt expires 2026-07-31.

🤖 Generated with Claude Code

BigSimmo and others added 3 commits July 2, 2026 11:29
…ocument-derived text through formatters

- Add normalizeExtractedGlyphs (ligatures, soft hyphens, zero-width/control
  chars; deliberately no line-break de-hyphenation — fusing would corrupt
  clinical compounds like low-dose) wired into the base compactWhitespace/
  readableWhitespace cleaners so every formatter inherits it
- Fix bypass surfaces: verbatim quote body + clipboard, citation labels
  (formatCitationLabel/formatCompactCitationLabel now glyph-clean titles),
  source titles, visual-evidence labels, table snippets, image captions/alt,
  evidence-map details, AccessibleTable cells/captions
- Drop unknown-filler segments from provenance joins while always keeping
  clinical governance warnings (source-metadata.ts, ui-primitives.tsx)
- Normalize glyphs at ingestion (buildChunks) with source-span offsets kept
  consistent via a single normalized page text
- Add scripts/backfill-text-normalization.ts: in-place, no-re-embed, dry-run
  by default, revertible JSON backup, project-guarded
- Centralize static UI copy in src/lib/ui-copy.ts (empty states, errors,
  starter prompts) for the dashboard and document manager
- Add tests/rendered-text-formatting.test.ts guard against new raw
  interpolations; document conventions in docs/process-hardening.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erg-9a53a9

# Conflicts:
#	src/components/ClinicalDashboard.tsx
#	src/components/clinical-dashboard/answer-status.tsx
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 2, 2026 04:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR standardizes handling of document-derived text across the application by introducing a shared glyph-normalization primitive and routing previously raw render surfaces through existing formatter helpers, while also centralizing static UI chrome copy and adding regression tests + an optional stored-text backfill script.

Changes:

  • Adds normalizeExtractedGlyphs and wires it into base whitespace cleaners so downstream formatters inherit PDF-extraction glyph repair.
  • Updates multiple UI surfaces (quotes, titles, citations, tables, image captions, provenance) to avoid raw interpolation of extracted text.
  • Introduces a dry-run-by-default backfill script plus new tests and documentation to prevent regressions.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/source-text-sanitizer.test.ts Adds unit coverage for glyph normalization and the new verbatim-quote cleaner.
tests/source-metadata.test.ts Updates provenance-summary expectations (drop unknown filler segments; keep governance warnings).
tests/rendered-text-formatting.test.ts Adds static “no raw interpolation” guards for known document-derived render surfaces.
tests/citations.test.ts Adds coverage for glyph repair + Synthetic prefix stripping in citation labels.
src/lib/ui-copy.ts Introduces a central module for static UI chrome copy (empty states, errors, labels).
src/lib/source-text-sanitizer.ts Adds normalizeExtractedGlyphs, integrates into cleaners, and introduces sourceTextForVerbatimQuote.
src/lib/source-metadata.ts Drops “unknown” filler segments from visible provenance summaries while keeping governance warnings.
src/lib/citations.ts Cleans citation titles centrally (glyph repair + Synthetic prefix removal) before building labels.
src/lib/chunking.ts Normalizes extracted glyphs during chunk building so stored chunk text and offsets stay consistent.
src/components/ui-primitives.tsx Adjusts provenance UI to drop unknown filler segments while retaining governance signals.
src/components/DocumentViewer.tsx Routes image/table captions/headings through compact display formatting and improves alt text.
src/components/DocumentManagementActions.tsx Documents intentional use of raw stored document title for destructive confirmation UX.
src/components/ClinicalDashboard.tsx Routes quotes/titles/snippets through formatters; centralizes empty/error copy usage.
src/components/clinical-dashboard/master-search-header.tsx Uses cleanDisplayTitle for scoped-document display strings and tooltips.
src/components/clinical-dashboard/DocumentManagerPanel.tsx Centralizes empty/error strings and cleans displayed document titles.
src/components/clinical-dashboard/display-text.ts Adds cleanDisplayTitle using glyph normalization; reuses it for source titles.
src/components/clinical-dashboard/answer-status.tsx Moves empty-state and button copy to ui-copy.ts.
src/components/AccessibleTable.tsx Normalizes extracted glyphs in table cell text cleaning.
scripts/backfill-text-normalization.ts Adds an opt-in, safety-guarded stored-text backfill script (no re-embed).
package.json Adds backfill:text-normalization script entry.
docs/process-hardening.md Documents formatter/copy conventions and the new backfill + regression-test guard.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/source-text-sanitizer.ts Outdated
Comment thread scripts/backfill-text-normalization.ts Outdated
@BigSimmo
BigSimmo enabled auto-merge (squash) July 2, 2026 04:16

@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: 143ec9f220

ℹ️ 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 scripts/backfill-text-normalization.ts Outdated
Comment thread scripts/backfill-text-normalization.ts Outdated
Comment thread src/lib/source-text-sanitizer.ts Outdated
Comment thread src/lib/chunking.ts
Comment thread scripts/backfill-text-normalization.ts Outdated
Comment thread src/lib/source-text-sanitizer.ts
BigSimmo and others added 3 commits July 2, 2026 12:18
…arify verbatim-quote whitespace contract

Addresses Copilot review on PR #121: '--limit foo' / '--limit 0' previously
fell through the falsy truthiness check and silently meant unlimited.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…omitted-image markers, boilerplate keys, backfill hardening

- Convert VT/FF/NEL controls to newlines instead of deleting (deleting fused
  words like dose\fmonitoring)
- Strip [[IMAGE_DATA_OMITTED]] markers from verbatim quotes and all display paths
- Build repeated-boilerplate keys from the same normalized page text that
  removePageNoise compares against
- Backfill: make the revertible backup mandatory (no --no-backup escape hatch)
  and include retrieval_synopsis in scan/patch/backup

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 2, 2026 04:28
@BigSimmo
BigSimmo merged commit ad5c9b6 into main Jul 2, 2026
3 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/components/DocumentManagementActions.tsx:203

  • The delete-confirmation gate compares against document.title byte-for-byte, but titles can contain invisible extraction artifacts (soft hyphens / zero-width / control chars) that users cannot reliably see or type. That can make deletion impossible even when the user types what appears to be the exact title, and it also reintroduces glyph artifacts into a rendered surface. Consider displaying a cleaned title and comparing confirmation using normalizeExtractedGlyphs() on both sides (or allowing either exact-match or normalized-match).
            {/* Deliberately the RAW stored title: the confirmation input is compared
                against document.title verbatim, so the user must see the exact string. */}
            <p className={cn("break-words text-xs font-semibold", textMuted)}>{document.title}</p>
            <div className="flex flex-wrap justify-end gap-2">
              <button type="button" className={floatingControl} onClick={closeDialog} disabled={pending}>
                Cancel
              </button>
              <button
                type="submit"
                className={cn(primaryControl, "bg-[color:var(--danger)] hover:bg-[color:var(--danger)]")}
                disabled={pending || deleteConfirmation !== document.title}
              >
                {pending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}

Comment thread src/lib/source-text-sanitizer.ts
Comment thread scripts/backfill-text-normalization.ts
BigSimmo added a commit that referenced this pull request Jul 2, 2026
…dry-run memory flat

Post-merge Copilot follow-ups on PR #121:
- sourceTextForIndexedPage now starts from normalizeExtractedGlyphs (spaces/
  tabs preserved, so fixed-width table parsing is unaffected) instead of only
  normalizing CRs — the indexed source text panel no longer leaks ligatures
- Backfill dry-run tracks a counter + capped samples only; the full changed-row
  list (needed for the mandatory backup) is materialized only when writing

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

@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

export function sourceTextForIndexedPage(text: string) {
return text
.replace(/\r/g, "\n")

P2 Badge Normalize page viewer text before rendering

When the selected page is rendered through sourceTextForIndexedPage, this path still starts from the raw stored page text and never calls normalizeExtractedGlyphs, unlike the other display helpers. Existing document_pages.text values containing ligatures, soft hyphens, or zero-width/control characters will therefore still show those extraction artifacts in the DocumentViewer page text even though this change cleans chunks/citations; normalize the text at the start of this helper while preserving its fixed-width spacing behavior.


{!hasStructuredTable && image.tableTextSnippet ? (
<p className={cn("text-sm leading-6", textMuted)}>{image.tableTextSnippet}</p>

P2 Badge Clean visual table snippets in DocumentViewer

When an image row has tableTextSnippet but no structured table (or the snippet is passed as markdown with clinicalOnly false), the DocumentViewer still renders the extracted table text raw here. Snippets containing , soft hyphens, zero-width controls, or internal image/table markers will leak on the document page despite the new cleaned caption/title path, so route tableTextSnippet through the same compact/display formatter before passing or rendering it.

ℹ️ 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 on lines +2606 to +2608
detail:
sourceTextForCompactDisplay(row.quote || row.source.snippet || row.source.reason || "") ||
cleanDisplayTitle(row.source.title),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Sanitize evidence-map passage samples

This only cleans the row detail, but EvidenceMapTable renders the separate bestLinkedPassage as the “Passage sample”; for render-model evidence rows that field is still assigned from the raw quote/snippet/reason just below. When a cited quote or snippet contains ligatures, soft hyphens, or internal image markers, switching to the evidence-map view still exposes those artifacts, so apply the same compact/verbatim formatter to the passage sample field too.

Useful? React with 👍 / 👎.

for (;;) {
let query = supabase
.from("document_chunks")
.select("id,document_id,content,section_heading,retrieval_synopsis")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize section paths in the backfill

When existing chunks have glyph artifacts in extracted headings, this backfill normalizes section_heading but leaves document_chunks.section_path untouched because it is not selected or patched. Search/ranking paths still combine section_path into section text/haystacks (for example src/lib/clinical-search.ts joins result.section_path), so a confirmed write run can leave the old ligatures/soft hyphens leaking into retrieval context and facets even after the heading itself was cleaned.

Useful? React with 👍 / 👎.

BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
…laim

Codex review is correct and this retracts the previous commit's central
claim. Reading `form.document-viewer-composer[data-scroll-hidden]` as a
proxy for the header's `scrollHidden` was wrong: they are separate state
machines. The header is driven by the shell's `chromeScrollHide`
(global-search-shell.tsx:332), fed only by `useDocumentScrollHideReporter`
(line 345) and passed in at line 876, while DocumentViewer runs its own two
`useHideOnScroll` instances (use-document-viewer-chrome-scroll.ts:20-30).
Composer-hidden therefore proves DocumentViewer's reporter fired and says
nothing about the header's, so it never separated a pin from a
reporter-never-fired — the exact distinction the helper claimed to make.

`data-scroll-signal` on the collapse wrapper now publishes the header's raw
`scrollHidden` before the pin is applied, and `expectChromeHidden` reports
it alongside DocumentViewer's so a divergence between the two feeds is
visible instead of collapsed into one verdict. Nothing styles the
attribute; no CSS or code reads it (verified by grep), so behaviour is
unchanged.

Ledger #127 is corrected rather than patched over: the "traces prove
sharedChromePinned is stuck" claim is explicitly withdrawn, what the traces
do establish is separated from what they do not, and the new leading
hypothesis is recorded as untested — the shell's feed is document-only, so
where `#main-content` owns scrolling `window.scrollY` never moves and the
shell reporter cannot see the gesture, which would explain the
standalone-PWA variant directly. It also now says not to infer the header's
scroll state from any page-owned composer.

Verified: verify:cheap exit 0 — Test Files 434 passed (434), Tests 4562
passed | 4 skipped (4566); typecheck and lint clean; prettier clean; the
full phone-scroll spec 56 passed (4.4m) against an isolated production
build. That build used the container's Chromium 1194, not the bundled 1234
CI installs (#121), so it proves the attribute broke nothing and nothing
more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XrPbbfU9yWuEjEVypCr4ZQ
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
…it asks for

A real conflict this time, not staleness: main's PR #1427 rewrote the same
helpers this branch touches, and it very likely found the actual cause of
#127. `addPhoneScrollRunway` slept 50ms and merely hoped the appended 1600px
runway had reached layout; `dragScrollBy` clamped silently at the end of the
range while reporting nothing. Under CI load the drag therefore delivered
less than it asked for and the chrome was right to stay visible. #1427 polls
for the runway, returns the distance actually travelled, and
`dragScrollUntilHidden` refuses to expect a hide until remaining runway and
delivered travel both clear 160px.

Main's helpers are taken whole. This branch keeps only what #1427's own
comment says is still missing: "Separating THOSE two still needs the pin
state exposed in the DOM; today only the composite `data-scroll-hidden`
(`scrollHidden && !sharedChromePinned`) is observable, so both look
identical." `data-scroll-signal` publishes the raw signal, and
`expectChromeHidden` is cut down to answer only that question, now layered
after `dragScrollUntilHidden` rather than duplicating its travel proof.

#127 is rewritten again and withdraws a second wrong diagnosis of my own: a
short/clamped drag was ruled out early using a maxOffset of 2753 read at a
different trace moment than the failing drag, when the pre-runway reading in
that same trace was 1153 — and a runway not fully landed puts the offset in
the near-bottom band where computeScrollHideUpdate legitimately refuses.
That is exactly what #1427 fixes. The row now points at #1427 as the likely
fix, keeps the observability gap as the only open part, and says to close it
if no recurrence appears on a post-#1427 head.

Main also claimed #135 for an unrelated issue, so the union-driver finding
renumbers to #140, marker 141.

Verified: typecheck 0 errors, lint 0 problems, whole-tree prettier clean,
check:outstanding-issues 138 rows / unique ids / next-id=141, and the merged
phone-scroll spec 56 passed (4.1m) against an isolated production build —
under Chromium 1194, not CI's bundled 1234 (#121).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XrPbbfU9yWuEjEVypCr4ZQ
BigSimmo added a commit that referenced this pull request Jul 30, 2026
…1430)

* test(phone-chrome): name which value holds the header open, not just that it did

`data-scroll-hidden` on the collapse wrapper is
`scrollHidden && !sharedChromePinned`, so a missing attribute has two very
different causes the assertion cannot separate: the scroll state machine
never fired, or it fired and a pin held the chrome open. The bare assertion
reads as the first even when it is the second — which is how a stuck pin was
misread as a flaky scroll gesture across two CI runs on 2026-07-30.

`expectChromeHidden` keeps the same pass condition and adds a failure
message. The discriminator is already in the DOM: DocumentViewer's
page-owned composer hides on `composerScrollHidden`, which consults
`scrollHidden` and not the pin, so composer-hidden plus header-visible
proves the pin. Every term of `sharedChromePinned` also has a DOM tell —
an `aria-expanded` trigger, a popover, or focus inside the portaled addon
host — so when all read false the pin is a stale latch rather than a live
surface, and the message says so.

Also updates ledger #127 with what the traces establish: `scrollHidden` is
TRUE and `sharedChromePinned` is stuck, reproducible on both completed
full-suite runs and both variants, always at the reduced-motion hide that
follows the section-sheet round-trip and never at the first hide. `main`
only looks green because `Production UI` is skipped on its docs-only
pushes; it has not run this test since 90b3e34, with zero `src/` changes
since.

Deliberately not the fix. Which term latched is proven; the mechanism is
inferred, and it does not reproduce locally — every local run used the
container's Chromium 1194 rather than the bundled 1234 CI installs (#121),
so no local green is evidence here. This makes the next CI failure name its
own cause instead of costing another trace download.

Verified: typecheck clean, lint clean, prettier clean,
check:outstanding-issues 125 rows / unique ids / next-id=128, and both
affected tests still pass locally (2 passed, 12.6s).

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

* fix(test): publish the header's own scroll signal; withdraw the pin claim

Codex review is correct and this retracts the previous commit's central
claim. Reading `form.document-viewer-composer[data-scroll-hidden]` as a
proxy for the header's `scrollHidden` was wrong: they are separate state
machines. The header is driven by the shell's `chromeScrollHide`
(global-search-shell.tsx:332), fed only by `useDocumentScrollHideReporter`
(line 345) and passed in at line 876, while DocumentViewer runs its own two
`useHideOnScroll` instances (use-document-viewer-chrome-scroll.ts:20-30).
Composer-hidden therefore proves DocumentViewer's reporter fired and says
nothing about the header's, so it never separated a pin from a
reporter-never-fired — the exact distinction the helper claimed to make.

`data-scroll-signal` on the collapse wrapper now publishes the header's raw
`scrollHidden` before the pin is applied, and `expectChromeHidden` reports
it alongside DocumentViewer's so a divergence between the two feeds is
visible instead of collapsed into one verdict. Nothing styles the
attribute; no CSS or code reads it (verified by grep), so behaviour is
unchanged.

Ledger #127 is corrected rather than patched over: the "traces prove
sharedChromePinned is stuck" claim is explicitly withdrawn, what the traces
do establish is separated from what they do not, and the new leading
hypothesis is recorded as untested — the shell's feed is document-only, so
where `#main-content` owns scrolling `window.scrollY` never moves and the
shell reporter cannot see the gesture, which would explain the
standalone-PWA variant directly. It also now says not to infer the header's
scroll state from any page-owned composer.

Verified: verify:cheap exit 0 — Test Files 434 passed (434), Tests 4562
passed | 4 skipped (4566); typecheck and lint clean; prettier clean; the
full phone-scroll spec 56 passed (4.4m) against an isolated production
build. That build used the container's Chromium 1194, not the bundled 1234
CI installs (#121), so it proves the attribute broke nothing and nothing
more.

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

* Merge origin/main; rebuild the ledger the union driver doubled

`PR mergeability` flagged this branch, but `git merge-tree` returned a clean
tree — behind-but-clean staleness, not a content conflict. The merge itself
then reported success while producing every open-items row twice
(`#59 appears 2 times (lines 101, 166)` and so on for the whole table):
`.gitattributes` sets `merge=union` on this file, which is git's built-in
concatenate-both-sides driver with no dedupe, and the table is not
append-only. Rebuilt from `origin/main` (now through #134) with only the one
row this branch actually changed re-applied. Recorded as #135, since the
driver turns a resolvable conflict into a guaranteed guard failure and makes
`merge-tree` look clean.

Also corrects #127's own framing. `Production UI` PASSED on run
30530393684, so the failure is intermittent at 2 of 3 completed runs, not
reproducible as the previous row claimed — that was premature on two
datapoints. The `data-scroll-signal` diagnostic therefore has not yet had a
failure to report; it is still the thing that will name the cause when one
comes.

Verified: check:outstanding-issues 133 rows / 67 open / unique ids /
next-id=136; check:branch-review-ledger 112 live + 1206 archived; whole-tree
prettier clean; no conflict markers under docs/, tests/ or src/. Not re-run
for this merge: verify:cheap and the phone-scroll spec — the code changes
are unchanged from 5495f28, where both passed (434 test files / 4562 tests,
and 56 passed), and this commit touches only the ledger plus main's own
already-verified tree.

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

* Merge origin/main (fcd9041); rebuild the ledger rather than trust union

Third staleness resolution on this branch in about forty minutes.
`PR mergeability` again reported `mergeable_state: dirty` and "a real merge
conflict", but `git merge-tree --write-tree` returned a clean tree, so this
is behind-but-clean staleness, not a content conflict. The single
overlapping path between this branch and main is
`docs/outstanding-issues.md` — nothing else on this branch is contested,
which is why the code files are byte-identical to 5495f28.

The file is rebuilt from `origin/main` with this branch's two rows
re-applied (#127 corrected, #135 added after #134, marker 136) instead of
keeping the union driver's output, which concatenates both sides of every
overlapping hunk without dedupe and doubled the whole table last time —
that behaviour is what #135 records. Main's #127 still carried the withdrawn
"sharedChromePinned is stuck" text and #135 was unclaimed, so neither graft
overwrote anyone else's edit.

Verified: check:outstanding-issues 133 rows / unique ids / next-id=136;
check:branch-review-ledger 113 live + 1206 archived; whole-tree prettier
clean. Not re-run: verify:cheap and the phone-scroll spec — `git diff
5495f28 -- src/ tests/` is empty, so the code carries that commit's evidence
(434 test files / 4562 tests, and 56 passed) unchanged.

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

* docs: record PR 1430 review

---------

Co-authored-by: Claude <noreply@anthropic.com>
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Codex review was right: #121 ("Container Playwright browser build lags the
pinned client") already tracks this exact condition — client 1234 versus
container 1194, every browser test failing at launch — so #145 created a second
canonical action for one problem. The row was allocated without first searching
the open table, which is the dedupe step the issues skill requires.

#145 is removed and its distinct content folded into #121: the reproduction on
main at c5c1a86, the fact that the condition has now been misread twice (the
handoff's 13 launch failures, and #120 filed as a gate defect under it), the
detection command to run before trusting a browser gate, and the stop rule
against filing a gate defect from a run whose tests never launched. #121's own
workaround, PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD note and open Next decision are
unchanged.

The id marker rolls back 146 -> 145 because #145 was never used by a live row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Sync main, then update two rows against evidence rather than adding new ones.

#98: PR #1450 landed the counting proxy and answer-path budgets while this
branch was open. Verified rather than assumed — the helper counts on execution
not construction, tests/rag-round-trip-budget.test.ts pins two answer-path
scenarios plus three counter self-tests, and it is registered in the offline
contract fixture so it runs there. Ran it: Test Files 1 passed (1), Tests 5
passed (5). The row stays open with its Next narrowed to the two real gaps:
/api/search has no budget, and eval-rag-offline/test-rag-offline were not wired.
Also records the helper's own blind spot — it sees only traffic through the
wrapped client.

#130: already owns the unfiled pre-paint/cold-load guard, so its design goes
there instead of a new row. Records what the guard must test (the pre-paint
reserve seed, sampled before and after hydration rather than once after), why a
zero-inset profile is required for it to be able to fail at all, and that it
must be proven against the broken shape first. Also records the environment
blocker: browser gates cannot launch here per #121, and the symlink bridge
writes under /opt, which the sandbox refuses.

No new ids allocated; both are updates to rows that already own the work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
BigSimmo added a commit that referenced this pull request Jul 30, 2026
…gn (#1455)

* issues: close #122, capture the container Playwright pin mismatch

Three related ledger items, each independently revertible.

Close #122 (`ci/circleci: verify` fails on every branch). Its outcome allowed
either "trustworthy signal again, or it stops reporting"; the second happened.
`.circleci/config.yml` was deleted by 9779828 (PR #1412), and PR #1452's head
reported 21 check runs with none named `ci/circleci: verify`, so the status no
longer reports on new PRs. No operator log read is needed and the quota
hypothesis is retired unproven.

Capture #145: the remote container ships Chromium 1194 while the repo's
Playwright pin wants 1234, so every browser test dies at launch and zero
assertions run while the output reads like product breakage. This has cost time
twice — the 2026-07-30 handoff records 13 launch failures read as a code defect,
and #120 was filed on a gate reading taken under the same condition. The row
gives the start-of-session check and keeps the existing "never run
npx playwright install" stop rule.

Fix a stale rule found while verifying #122: AGENTS.md cited
`ci/circleci: verify` as a check that fails on unformatted files. It cannot
report again, so the rule now names `Static PR checks` and records the CircleCI
failures as history.

The outstanding-issues diff is 4 insertions / 3 deletions ignoring whitespace;
the rest is Prettier re-padding the archive table, because #122's original
summary is wider than that column and was kept verbatim.

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

* issues: fold the Playwright pin evidence into #121, drop duplicate #145

Codex review was right: #121 ("Container Playwright browser build lags the
pinned client") already tracks this exact condition — client 1234 versus
container 1194, every browser test failing at launch — so #145 created a second
canonical action for one problem. The row was allocated without first searching
the open table, which is the dedupe step the issues skill requires.

#145 is removed and its distinct content folded into #121: the reproduction on
main at c5c1a86, the fact that the condition has now been misread twice (the
handoff's 13 launch failures, and #120 filed as a gate defect under it), the
detection command to run before trusting a browser gate, and the stop rule
against filing a gate defect from a run whose tests never launched. #121's own
workaround, PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD note and open Next decision are
unchanged.

The id marker rolls back 146 -> 145 because #145 was never used by a live row.

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

* issues: record #98 delivery and #130 pre-paint guard design

Sync main, then update two rows against evidence rather than adding new ones.

#98: PR #1450 landed the counting proxy and answer-path budgets while this
branch was open. Verified rather than assumed — the helper counts on execution
not construction, tests/rag-round-trip-budget.test.ts pins two answer-path
scenarios plus three counter self-tests, and it is registered in the offline
contract fixture so it runs there. Ran it: Test Files 1 passed (1), Tests 5
passed (5). The row stays open with its Next narrowed to the two real gaps:
/api/search has no budget, and eval-rag-offline/test-rag-offline were not wired.
Also records the helper's own blind spot — it sees only traffic through the
wrapped client.

#130: already owns the unfiled pre-paint/cold-load guard, so its design goes
there instead of a new row. Records what the guard must test (the pre-paint
reserve seed, sampled before and after hydration rather than once after), why a
zero-inset profile is required for it to be able to fail at all, and that it
must be proven against the broken shape first. Also records the environment
blocker: browser gates cannot launch here per #121, and the symlink bridge
writes under /opt, which the sandbox refuses.

No new ids allocated; both are updates to rows that already own the work.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Earlier today I recorded on #121 that a sandboxed remote session has only two
options for the Chromium 1194-vs-1234 mismatch: get the /opt write permission,
or decline to claim browser evidence. That was an over-generalisation from a
single blocked mkdir, and it is wrong.

PR #1432 landed a preflight whose own failure message names the route I had
missed: PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, read by playwright.config.ts:11 and
honoured by scripts/playwright-browser-preflight.mjs:101. It needs no filesystem
write. Verified by launching rather than by reading the flag — the container's
existing 1194 headless_shell drives fine under the repo's Playwright 1.62 client
(version 141.0.7390.37, page rendered, boundingBox measured).

#121 keeps the wrong sentence with the retraction beside it, because which claim
was wrong and why is the part worth carrying forward. #130's recorded blocker for
the pre-paint guard is marked LIFTED: that guard is buildable in a remote session
after all. Also records #1432 itself, which no row referenced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Both findings verified against the code before accepting, and both were right.

1. scripts/rag-offline-contract.mjs still described the suite as "the same guard
   for /api/search". That is the same overstatement Codex caught in the test and
   the ledger, and I had missed this third copy of it. The comment now says
   retrieval core and names what the suite does not observe: the route's auth,
   rate limiting, scope resolution, enrichment and telemetry write.

2. vi.doMock registrations survive vi.restoreAllMocks (which targets spies) and
   vi.resetModules (which clears the module cache, not the mock registry), so the
   Supabase/OpenAI fakes could outlive this file if isolation were relaxed.
   afterEach now calls vi.doUnmock for both. This is the established pattern here
   rather than a generic suggestion: five sibling suites already do it, and the
   comment cites tests/rag-variant-early-exit.test.ts:110.

Also resolves the fourth docs/outstanding-issues.md conflict from main advancing,
by rebuilding the #98/#121/#130 edits on main's table and diffing row-id sets
against main to prove nothing was lost.

Focused suites after the change, including the neighbouring mock-sensitive ones:
Test Files 3 passed (3), Tests 9 passed (9).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018K7sEKH35KZkWxvCnQcNN2
BigSimmo added a commit that referenced this pull request Jul 30, 2026
… retract a wrong #121 claim (#1464)

Budget the search retrieval core: tests/search-round-trip-budget.test.ts pins
Supabase round-trip counts for searchChunksWithTelemetry, registered in the
offline contract. Scoped explicitly as retrieval-core, not an /api/search
endpoint budget — the route's auth, rate limiting, scope resolution, enrichment
and telemetry write are outside what it observes. One retrieval measures 11
round trips, with match_document_chunks_text_v2 and
match_document_table_facts_text_v2 each firing three times; whether that is
intended is left open on #98 rather than guessed at.

Retract a wrong claim on #121: a sandboxed remote session can produce browser
evidence via PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, verified by launching. The
earlier "only two options" sentence is kept with the retraction beside it.
#130's blocker is marked LIFTED as a result, so the pre-paint guard is buildable.
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