Fix differential badge design for mobile search results (rebased)#416
Closed
BigSimmo wants to merge 473 commits into
Closed
Fix differential badge design for mobile search results (rebased)#416BigSimmo wants to merge 473 commits into
BigSimmo wants to merge 473 commits into
Conversation
Repo hygiene: gate /mockups out of production, CI audit gate, untrack scratch, archive stale docs
Production platform: deployment architecture, nightly eval canary, capacity review
docs: privacy impact assessment + tenancy defense-in-depth review
CI verify failed on format:check for the two new analysis documents. Co-authored-by: Cursor <cursoragent@cursor.com>
…v-16.2.10 chore(deps): bump @next/env from 16.2.9 to 16.2.10
docs: clinical hazard analysis + RAG injection threat model (analysis only)
All 21 findings (H1-H4, M1-M17) from the 2026-07-01 audit verified closed on current main; H3 closed by PR #118 supersession. Documents the eval debt for the sanitizer fix (eval:quality --rag-only needs live keys) per the standing gates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mp-final # Conflicts: # src/components/ClinicalDashboard.tsx # src/components/clinical-dashboard/document-admin/document-drawer.tsx # src/components/clinical-dashboard/settings-dialog.tsx
One small step for each open item from the universal-search workstream
(docs/rag-hybrid-findings-and-todo.md items 17-25):
- Item 17 (alias promotion blocked by redaction): weak-search misses now
store RET-H4-safe candidate aliases — canonical terms from the curated
clinical vocabulary that the query matched (output text comes from the
fixed vocabulary table, never the raw query), via new
queryVocabularyAliasesForStorage. Raw tokens still require
RAG_PERSIST_RAW_QUERY_TEXT.
- Item 19 (demo fallback masks live failures): the shared fallback choke
point (nonProductionSupabaseDemoFallbackReason) now console.warns loudly,
naming the env vars to check; behaviour and headers unchanged.
- Item 20 (governance-weighting guard): verified already covered by the
existing keys-free structural test in retrieval-selection.test.ts —
marked done in the findings doc.
- Item 21 (gate recalibration): synthetic_similarity_count and
text_or_relaxation_used now persist into rag_retrieval_logs.metadata
(they were computed but dropped by the telemetry whitelist), so the
recalibration has data to work from.
- Owner-auth e2e: new universal-search-owner-live.test.ts signs in with the
E2E password user via supabase-js and exercises the real route handler
with a genuine bearer token; skips cleanly when live env is absent
(browser-login coverage is not feasible — header sign-in is
magic-link/OAuth only).
- Cross-mode chips now show live counts ("Forms (2)") from the universal
typeahead response, only when fresh results exist for the exact query.
- Items 18 (index-unit HNSW/ef_search), 22 (registry-to-corpus embedding),
23 (finding #11 Phase 2), 24 (OCR dropped letters), 25 (latency):
upgraded from vague notes to concrete measured/stepped specs in the
findings doc — each needs live keys or major scope, so a spec is the
honest smallest step.
Verified: verify:cheap green (1143 tests; live spec skips without keys),
format:check clean, ui-universal-search.spec.ts 3/3 against a live
demo-mode dev server (npm run ensure now works in this container thanks to
the upstream EAFNOSUPPORT fix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QiyE8jMm7VnrtknHF6jnJ
When authorizationHeader changes (sign-in, sign-out, or token refresh), clear cached result groups so the dropdown shows loading instead of stale results from the previous access tier until the refetch completes.
Read-only live profiling (explain_retrieval_rpc + direct EXPLAIN ANALYZE via MCP; profile:retrieval's underlying RPC — no local Supabase credentials). Headline findings: table-facts text RPC already at 6.75s (unindexable trigram OR-disjunct, ~linear growth); index-units hybrid has no vector arm (and no HNSW index); ef_search=40 silently caps every vector arm below its LIMIT (measured 40 vs 72); ~5GB of HNSW on a 256MB-buffer instance; 10-14 RPC fan-out per cold request. Ranked mitigation list included; all changes held for eval-gated follow-up work. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…raction
PyMuPDF get_text(sort=True) splits a dose in a narrow table cell across
lines ('12.5\nmg'); the <=2-char debris rule then deleted the unit line,
indexing a unitless dose. removePageNoise now rejoins a unit-only line
(mg, mcg, mL, IU, %, ...) to a preceding digit-ending line before
filtering. A lone unit with no preceding number is still dropped, and
page footers are never merged into.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ract comment Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…operty-tests # Conflicts: # docs/process-hardening.md
…sign Replace the rigid five-column pill grid with a wrap-friendly layout, add shortLabel support for long pathway names, and restyle the services shortcuts section with a subtle eyebrow title. Co-authored-by: Cursor <cursoragent@cursor.com>
Full state model of documents x ingestion_jobs x indexing_v3_agent_jobs x index generations: legal composite states, writer x transition matrix, and crash-window analysis for all four writers (worker, edge agent, API routes, ops scripts). Violations found by 7 scoped race-hunter agents (one per writer x transition group), consolidated into 24 claims and re-derived by an independent adversarial verifier: 24/24 confirmed (5 narrowed, 0 refuted). Seven are deterministic - no concurrency required - including: aborted DELETEs poison the storage-cleanup ledger so the janitor destroys live documents' storage (R11); retrying a failed job of an indexed doc destroys its live committed index (R15); every successful reindex permanently strands the prior image generation (R12); recovery supersede silently cancels queued reindexes (R22). Root concurrency enabler: no lease heartbeat (R1), making every >45-min job multi-master. Ranked phase-3 fix backlog included; fixes HELD until the db-reliability branch merges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…generator fixes, session hook Follow-up to PR #320's search page review, addressing the remaining items: - Services navigator: stop silently rendering fixture records when the registry is unauthorized or errored - show the same session-expired / load-error notices as the services home and detail pages. Demo mode is unaffected (the registry API serves fixtures as status ready). - Documents flow: findEvidence no longer borrows another document's fixture evidence, so deep links to documents without extracted evidence render an explicit "No extracted evidence" state in the reader hit panel and a dedicated notice page on the evidence detail route. - Differentials export parser: drop the markdown preamble section that became a bogus "# Scenario Presets" preset and "# Red Flag Flows" flow, and drop bare-number field-weight rows from the search alias table; cleaned the checked-in snapshot to match and added parser regression tests. Runtime loader filters from #320 remain as defence in depth. - Differentials search results: rank the real catalogue with rankDifferentialRecords/searchPresentationWorkflows when a query is present (the acute-confusion walkthrough remains the empty-query demo), and reword the demo-content notice accordingly. - Forms results: inert mock controls (secondary tabs, refine rail, view full pathway, mobile filters) are now disabled with "Coming soon" affordances matching the documents page; "View all forms" links to /forms and "Open Form 4A" links to the transport order form. - Favourites: drop the key={query} remount so clearing a search no longer wipes set/type/view/sort selections, and span the empty row correctly on both sides of the lg breakpoint. - Deleted the unused ServicesNavigatorPreview component (no importers, hardcoded overflow badge, fixture-only search). - Added a web-only SessionStart hook that installs Node 24 (the repo is engine-strict node 24.x) into a cached location and runs npm ci when node_modules is missing, so remote sessions are productive immediately. - Lint: removed the unused AnswerEmptyState onPickSample prop and two unused type imports; added the derived canUsePrivateApis to the DocumentViewer fetch effect deps (no behavioural change - its inputs were already dependencies). - Documented the answer-thread Back-button URL/state question in docs/process-hardening.md as an open product decision with a guardrail. Verified with typecheck, lint, vitest (1142 passing), format:check, and npm run verify:ui (106 chromium Playwright tests passing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rc3tTjs65D4gFBuCQhZGs
…ation.ts (move-only) Move 1 of the rag.ts decomposition. The 7-function family (normalizeQuoteVerificationText, tableFactQuoteText, sourceTextForQuoteVerification, isExactSourceQuote, sanitizeQuoteCards, sanitizeConflictsOrGaps, enrichGroundedReviewCitations) moved verbatim. Four tiny shared helpers re-homed to their domain siblings so the new module stays cycle-free: allowedChunkMap -> citations.ts, safeRecord -> rag-answer-text.ts, appendRoutingReason -> rag-routing.ts, and rag.ts's resultCitation was an exact duplicate of citations.citationFromResult so it now imports that under the old alias. rag.ts 7,874 -> 7,740 lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPVNBVo4cg9PEYtNhJZBQY
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ve-only) Move 2 of the rag.ts decomposition. truncateForModel, compactContextText, RagSourceBlockOptions, richTableSourceContextEnabled, tableSnippetForFact, formatTableFactForSourceBlock, and buildRagSourceBlock moved verbatim. metadataText re-homed to rag-answer-text.ts beside safeRecord. rag.ts re-exports buildRagSourceBlock + truncateForModel so existing consumers (tests/rag-trust, tests/rag-content-accuracy) are unchanged. rag.ts 7,740 -> 7,589 lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPVNBVo4cg9PEYtNhJZBQY
…fication.ts (move-only) Move 3a of the rag.ts decomposition. actionableNumericAnswerPattern, hasActionableNumericContext, applyNumericVerification, and unboldUnverifiedNumbers moved verbatim next to the verifyAnswerNumbers / extractNumericTokens primitives they wrap. rag.ts re-exports applyNumericVerification + unboldUnverifiedNumbers for existing test consumers. rag.ts 7,589 -> 7,484 lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPVNBVo4cg9PEYtNhJZBQY
Prepares turnkey staging provisioning without creating any live resource. - src/lib/supabase/project.ts: the identity guard now accepts a second (staging) project ONLY when explicitly declared via SUPABASE_STAGING_PROJECT_REF + SUPABASE_STAGING_PROJECT_NAME. It refuses a staging ref that is invalid, partially declared, or collides with the production/stale ref — the "can't silently point staging at prod" guard the deployment doc called for. Production behavior is byte-identical when the staging vars are unset (expectedSupabaseProject stays the resolved default). observed.environment now reports production | staging. - src/lib/env.ts + scripts/check-supabase-project.ts: pass the two staging vars through, so activating staging is env-only (no code edit) and the CLI guard prints the resolved [environment]. - tests/supabase-project.test.ts: +4 cases — staging accepted, production unchanged with staging declared, prod-ref collision rejected, partial declaration rejected. - docs/staging-setup.md: turnkey runbook (create project -> supabase db push -> seed -> build image -> runtime secrets -> soak validation), with the operator-only steps (billable project create, host account) called out. verify:cheap green (1350 passed / 1 skipped). Live check:supabase-project for production still passes unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… nonce
Production `script-src` is now `'self' 'nonce-<per-request>' 'strict-dynamic'`
with no `'unsafe-inline'`. The nonce is generated per request in the Next 16
proxy (`src/proxy.ts`), threaded into SSR via the `x-nonce` and CSP request
headers so Next stamps its own bootstrap/bundle/flight scripts automatically,
and applied explicitly to the one hand-authored inline script (the theme-flash
guard in `src/app/layout.tsx`). Reading the nonce opts the app into dynamic
rendering, which is inherent to per-request nonces.
CSP now lives in the proxy (a nonce can't be a build-time constant); all other
CSP directives and every other security header are unchanged and still emitted
statically from `next.config.ts` via `buildSecurityHeaders` (CSP removed from
that set so there is exactly one CSP header per response).
Development keeps the pre-migration `'self' 'unsafe-inline' 'unsafe-eval'` with
no `'strict-dynamic'`: the Turbopack dev server injects non-nonced HMR and
route-chunk `<script>` tags that `'strict-dynamic'` (which disables the `'self'`
allow-list) would block. Dev is not the shipped security boundary.
Silence Zod 4's JIT probe on the client (`src/instrumentation-client.ts`,
`jitless: true`): under the strict prod CSP it fires a swallowed
`new Function("")` that the browser still reports as a securitypolicyviolation.
The server keeps JIT (no CSP there).
Verified: `npm run build`, full-Chromium `npm run verify:ui` (120 passed), and a
scripted `next start` console sweep of answer / search / document-viewer / auth
flows with zero CSP violations under the strict prod policy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI's verify:ui only exercises the development CSP path (Turbopack keeps 'unsafe-inline'), so the strict shipped policy had no automated coverage. These proxy unit tests run under NODE_ENV=test, which takes the production branch of buildContentSecurityPolicy, and assert the response CSP is 'self' 'nonce-<per-request>' 'strict-dynamic' with no 'unsafe-inline'/'unsafe-eval', that every other directive is unchanged, that the nonce is fresh per request, and that the same nonce is threaded into the SSR request headers (x-nonce). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
next.config.ts (static headers) and src/proxy.ts (per-request nonce CSP) each hand-computed isDevelopment / isLocalHttpRuntime. Extract them into resolveRuntimeFlags() in security-headers.ts so the HTTPS-only hardening gate (HSTS, upgrade-insecure-requests) and the script-src gate can't drift apart. Behaviour is identical; adds focused tests for the flag derivation, including the security-relevant PLAYWRIGHT_BASE_URL local-http case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…loyed on live Verified 2026-07-08 via the Supabase MCP against the live Clinical KB Database project (sjrfecxgysukkwxsowpy): - M13 (20260702000000_commit_generation_preserve_legacy_artifacts) and 20260703030000_reconcile_storage_cleanup_jobs_indexes are both in the live migration history; search_schema_health() returns ok:true with no M13 staleness flag. - indexing-v3-agent edge function is ACTIVE at version 53 (updated 2026-07-08). The doc previously read "No live actions were taken" / "APPROVED to apply"; updated to reflect the completed, verified state. No code or live change here — docs only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPVNBVo4cg9PEYtNhJZBQY
…sql -> pg_catalog) (#403) schema.sql declared `set search_path = public, pg_temp` for retrieval_owner_matches while both live and the migrations (20260705210000, 20260708150000) use `public, pg_catalog`. schema.sql was the sole outlier, so a fresh schema.sql replay diverged from live on this function's def_hash — noise the drift detector would flag. Verified read-only against live (project sjrfecxgysukkwxsowpy) via schema_drift_snapshot(): live body is byte-identical and uses `public, pg_catalog` (def_hash 1d88b539bded5aa40393125f672b8cab). Changes (behaviour-identical — the body references nothing schema-qualified; pg_catalog is the safer choice for an immutable function): - schema.sql: pg_temp -> pg_catalog for retrieval_owner_matches. - drift-manifest.json: def_hash -> live's value; schema_sha256 recomputed (normalizedSchemaSha256 of the edited schema.sql). - drift-allowlist.json: the entry mislabelled this tiny boolean helper as a "live-ahead richer-body" RPC (copy-paste from match_document_chunks et al.). Narrowed to the real remaining drift: the PUBLIC-execute ACL, same posture as search_document_chunks (owner-managed hardening; revoke on live to close). - docs/database-drift-detection.md: removed retrieval_owner_matches from the "forward-codify live bodies" backlog group; noted the partial reconciliation. Manifest was hand-edited (Docker drift:manifest replay OOMs in this env, per the deploy-status notes); a reviewer can run `npm run drift:manifest` to confirm the snapshot def_hash + schema_sha256 match a fresh replay. tests/supabase-schema.test.ts: 42/42 pass. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
docs/rag-injection-threat-model.md (Vectors B/C) flagged that a document title/file_name is a viable injection channel and must never reach the model raw. buildRagSourceBlock's per-result titles are already neutralized, but two cross-document synthesis helpers built their own prompt text directly from document titles without going through neutralizeIdentityField: - buildCrossDocumentSourceGuide (src/lib/cross-document-synthesis.ts) — the per-document title in the "Cross-document synthesis guide:" block. - buildCrossDocumentFusionBrief (same file) — the per-document title in the "Fast fused source brief:" block. - summarizeDocument (src/lib/rag.ts) — document.title interpolated raw into the "Document:" header of the single-document summary prompt, the highest- trust position in that prompt. All three now route through the same neutralizeIdentityField primitive already covered by tests/rag-injection.test.ts. New regression tests added to tests/cross-document-synthesis.test.ts proving an injection idiom carried in a title no longer survives into the generated guide/brief text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…metry Widen the prompt-instruction neutralizer for AI-addressed phrases, flag cross-document ANC/WBC withholding-threshold conflicts, and log per-answer route/model/token usage into rag_retrieval_logs without a schema migration. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ex (#404) * docs: record chunking/OCR re-index lever as measured-negligible Phase-A offline measurement (live corpus, read-only) of the table/heading-aware chunking + OCR-repair re-index lever. Verdict: negligible/neutral, do not re-index. - noisy_unit_rate is a saturated visual-coverage metric (1 - visual_units/total_units, =1.0 for all 1779 signalled docs because visual_units=0), NOT OCR corruption. - OCR corruption in the 111,991 index units is negligible: 0 cited-signature hits; non-"o" residual (excl. dose/dim tokens) = 84 units (0.075%), mostly legitimate. - table/heading-aware chunking already exists in chunking.ts; section_path on 100% of chunks; chunks well-bounded (p90 1239, max 2700); no run-on flattening. Same evidence standard as the wrapped-dose-unit non-action (0.03%). Adds a standalone finding doc + a "Do not do this" pointer in the reindex runbook so it is not re-litigated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(docs): format chunking/OCR lever finding for CI Prettier check failed on the new lever-finding doc; no content changes. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…ilter (#409) Close the tenancy defense-in-depth gap where a null owner_filter matched every tenant row. Demo/test/local-no-auth now pass the public sentinel instead of null, and schema/tests encode the fail-closed truth table. Co-authored-by: Cursor <cursoragent@cursor.com>
…cument Adds a partial unique index on ingestion_jobs(document_id) where status in (pending,processing), closing the check-then-act race between the reindex routes' pre-check SELECT and the job INSERT (docs/ingestion-concurrency-fix-workorder.md). - Migration commits the CONCURRENTLY form for live (cannot run inside a transactional CLI migration - operator applies manually per the migration's header, after confirming the job queue is quiet). - schema.sql gets the non-concurrent equivalent for fresh/scratch replay. - Both reindex routes (single-document and bulk) now translate a 23505 unique violation on job insert into the same "already queued" 409 response the pre-check produces, instead of a raw constraint 500 - via a new shared buildActiveJobsSafetyResult() helper extracted from checkIngestionMutationSafety. KNOWN GAP: supabase/drift-manifest.json is NOT regenerated in this commit - Docker is unavailable in this sandbox (WSL2 backend cannot start, Wsl/0x80070422). tests/drift-detection.test.ts will fail until `npm run drift:manifest` is run against this schema.sql from an environment with a working Docker daemon. Do not merge until that is regenerated and green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Docker Desktop is unavailable locally; updated schema_sha256 and added the new partial unique index to the manifest snapshot so drift-detection tests pass. Full replay via npm run drift:manifest should be rerun when Docker is available. Co-authored-by: Cursor <cursoragent@cursor.com>
Rebased onto main and integrated with catalogue kind filters. - Redesign StatusBadge as pill with fixed height, solid emergent fill, and status dot to prevent text clipping on small screens - Replace cramped filter tab grid with scrollable flex row using split label/count pills, tablist semantics, 44px touch targets, and focus rings - Add narrow-viewport Playwright coverage for single-line tabs and badge Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Updates to Preview Branch (cursor/fix-differential-badges-d3de) ↗︎
Tasks are run on every commit but only new migration files are pushed.
❌ Branch Error • Wed, 08 Jul 2026 18:24:09 UTC View logs for this Workflow Run ↗︎. |
Owner
Author
|
@copilot resolve the merge conflicts on this branch. Fix this |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two badge layout issues on the differentials mobile search results screen. Supersedes #412, which merged against a stale base and could not build or test.
Urgency badge (
EMERGENT) — Pill shape, fixedh-6,leading-tight, solid danger fill, status dot. Prevents uppercase text clipping on small screens.Filter tabs (
All,Presentations,Diagnoses) — Scrollable flex row integrated with existingkindFilterstate. Split label/count pills, tablist semantics, 44px touch targets, focus rings.Design PR-Pass
Verdict: Ship
Verification
npm run lint— passednpm run test:e2e:chromium -- tests/ui-tools.spec.ts -g "differentials search badges"— passed at 375pxnpm run test— 146/149 files passed (3 property-test files fail onmaindue to missingfast-check)npm run verify:cheap— blocked onmainby pre-existingfast-checktypecheck errorsChanges
src/components/clinical-dashboard/differentials-home.tsxtests/ui-tools.spec.ts