Skip to content

Wire Services/Forms to a Supabase registry (hybrid); label Differentials demo content - #209

Merged
BigSimmo merged 12 commits into
mainfrom
claude/bold-pare-3e568c
Jul 2, 2026
Merged

Wire Services/Forms to a Supabase registry (hybrid); label Differentials demo content#209
BigSimmo merged 12 commits into
mainfrom
claude/bold-pare-3e568c

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

Replaces the static mock data behind the Services and Forms modes with an owner-scoped Supabase registry (hybrid strategy: curated records + real corpus search), and honestly labels the Differentials synthetic content. Four phases, one commit each:

  1. Schema + seedclinical_registry_records (kind service|form, full record shape, conservative governance columns) + clinical_registry_record_sources join table for source-document linkage; service-role-only RLS; schema.sql mirror + test assertions; npm run registry:seed upserts the reviewed fixtures per owner (dry-run by default, never emits approved).
  2. APIGET /api/registry/records + /[slug] following the documents-route pipeline (demo-mode mock short-circuit, authenticated owner scoping on every query, new registry rate bucket, governance + linked-document passthrough). 8 new contract tests.
  3. Client wiring — detail routes drop SSG/mock lookups for client loaders (skeleton/sign-in/not-found/error states; saved-slug localStorage unchanged); home verification footers count fetched records; dashboard + forms search rank registry-fetched records client-side (live-typing unchanged); new labelTypesAny scope filter lets Services/Forms searches default the corpus scope to service-labelled / form-type documents (user filters win).
  4. Differentials labelling — the synthetic ranking is now explicitly badged as demonstration content (banner + relabelled strips); the fabricated "312 sources" and "Local results only / Reviewed content prioritised" claims are gone; real library-match counts stay. Playwright asserts the notice.

Verification

  • npm run verify:cheap per phase — final: 915 unit tests pass, lint/typecheck clean
  • npm run verify:ui — 69/69 Chromium tests on the final tree (includes new demo-notice assertion; 13YARN specs pass through the registry API demo path)
  • npm run format:check (prettier applied per phase)
  • Browser QA (demo mode): 13YARN detail, unknown-slug not-found state, services search renders the registry-backed verified record card, forms search best-matches, home footers
  • npm run check:production-readiness — env-key FAILs are the known worktree condition (no .env.local); unchanged from main
  • npm run verify:release — not a release handoff

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use (registry rows carry source_status/validation_status; seeding derives conservatively and never emits approved; differentials synthetic content now explicitly labelled)
  • No patient-identifiable document workflow introduced or expanded
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only (registry tables are service-role-only RLS; API enforces owner scoping on every query)
  • Demo/synthetic content remains clearly separated (demo mode short-circuits to fixtures with demoMode: true; authenticated users never silently see mock data; differentials demo banner)
  • Source metadata, review status, and unknown-source behavior remain conservative (missing → unknown/unverified)
  • TGA SaMD impact checked: no new automated clinical output; differentials demo labelling reduces the risk of synthetic content being read as reviewed guidance

Post-merge user steps (NOT performed here)

  1. Apply migration 20260703020000_clinical_registry_records.sql to the live project (explicit approval step).
  2. Seed: npm run registry:seed -- --owner-id <uuid> --write --confirm.

Until then, authenticated users see an honest empty-registry state; demo/env-less deployments are unaffected.

🤖 Generated with Claude Code

BigSimmoand others added 5 commits July 3, 2026 01:03
…se 1)
Adds the curated registry backing the Services and Forms modes:
- clinical_registry_records (kind service|form, full ServiceRecord shape,
conservative governance columns) + clinical_registry_record_sources
join table for source-document linkage, service-role-only RLS
- schema.sql mirror + supabase-schema test assertions
- database.types.ts entries for both tables
- src/lib/registry-records.ts bidirectional mappers with conservative
governance derivation (never emits approved from seeding)
- scripts/seed-registry-records.ts (registry:seed) upserting the mock
fixtures per owner; dry-run by default
Migration is a repo file only - NOT applied to the live project.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(phase 2)
- GET /api/registry/records?kind=service|form&q=&limit= and
/api/registry/records/[slug]?kind= following the documents-route
pipeline: demo-mode mock short-circuit, admin client, authenticated
owner scoping on every query, new 'registry' rate-limit bucket
(120/min), governance metadata + linked-document passthrough,
Cache-Control private/no-store
- non-breaking ranker extraction: rankServiceRecords/rankFormRecords
operate on an injected records array; searchServiceRecords/
searchFormRecords delegate with the mock fixtures as defaults
- tests/registry-records-route.test.ts covers the same per-route
contract the aggregate suites check (demo short-circuit, 401, 400
invalid kind, owner scoping, 429, 404, demo detail) so no duplicate
blocks were added to api-route-coverage/api-validation-contract
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- detail routes drop generateStaticParams/mock lookups; thin server
pages render client loaders (RegistryRecordLoader) with skeleton,
sign-in, not-found, and error states; ServiceDetailPage/FormDetailPage
and saved-slug localStorage behaviour unchanged
- services/forms home verification footers now count fetched registry
records via useRegistryRecords (hidden until ready)
- ClinicalDashboard record matches rank registry-fetched records
client-side (one fetch per active mode; live-typing behaviour
unchanged); forms search results page same treatment
- searchScopeFiltersSchema gains labelTypesAny (match any label of the
requested types); services/forms searches default the corpus scope to
service-labelled / form-type documents with user filters winning
- browser QA (demo mode): 13YARN detail, unknown-slug not-found state,
services search shows the registry-backed verified record card, forms
search best-matches table, home footers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(phase 4)
- warning banner (differentials-demo-content-notice) above the results
grid: ranked diagnoses are synthetic demonstration content; source
counts reflect real library matches
- replace the misleading 'Local results only / Reviewed content
prioritised' strip with 'Demonstration ranking - synthetic content'
- SourceStatusCard: real library-match count kept, fabricated '312
sources' row replaced with an explicit Demonstration row
- honest wording for the home subtitle, no-evidence notice, and
evidence pills ('Library matches'); dynamic demo result count
- Playwright: differentials search flow now asserts the demo notice
renders with the results view
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migration 20260703020000 applied to the live Clinical KB Database
(sjrfecxgysukkwxsowpy) with explicit user approval. Both tables present,
RLS enabled, service-role-only, 0 rows; security advisors clean. Notes
the UTC version-recording nuance (live version 20260702183308) and the
remaining per-owner seed step.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BigSimmo
BigSimmo enabled auto-merge (squash) July 2, 2026 18:36

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:d8652807f0

ℹ️ 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 threadsrc/lib/use-registry-records.ts Outdated
Comment threadsrc/components/registry-record-loader.tsx Outdated
Comment threadsrc/components/forms/forms-search-results-page.tsx Outdated
Comment threadsrc/app/api/registry/records/route.ts
Comment threadsrc/components/ClinicalDashboard.tsx
Comment threadsrc/components/clinical-dashboard/differentials-home.tsx
@BigSimmo

Copy link
Copy Markdown
OwnerAuthor

@copilot resolve the merge conflicts in this pull request

auto-merge was automatically disabled July 2, 2026 19:14

Head branch was pushed to by a user without write access

CopilotAI commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved in c71766a.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:32fd31a006

ℹ️ 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 threadsrc/components/ClinicalDashboard.tsx Outdated
Comment threadsrc/components/services/services-home-page.tsx Outdated
Comment threadsrc/components/registry-record-loader.tsx
@BigSimmo
BigSimmo enabled auto-merge (squash) July 2, 2026 19:19
@BigSimmo
BigSimmo disabled auto-merge July 2, 2026 19:19
@BigSimmo
BigSimmo enabled auto-merge (squash) July 2, 2026 19:19
BigSimmoand others added 2 commits July 3, 2026 03:34
- registry hooks: gate fetches until the auth provider resolves the
session (skip while status==='loading'); only markSessionExpired on
401 when authenticated, so a still-loading header can't clobber a
valid session. List hook is keyed by kind so a Services<->Forms switch
never ranks the previous kind's records (no wrong-mode links).
- registry-record-loader: surface the retryable error state before the
not-found branch, so a failed request (registry not migrated, Supabase
down, network) no longer shows the misleading 'not seeded' copy.
- forms-search-results-page: RegistryStatusNotice renders loading/
sign-in/error states instead of a silent '0 forms'.
- /api/registry/records: return the full owner+kind set (capped at 500)
so client-side ranking/counts see every row; 'limit' now bounds only
the ranked matches. New test covers the >limit case.
- differentials-home: 'Library matches' badge counts documentMatches,
not the whole indexed library; dropped the now-dead documentCount prop
chain (SearchResultsView -> DifferentialsHome -> ClinicalDashboard).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:2d25f08c4a

ℹ️ 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 threadsrc/lib/use-registry-records.ts
Comment threadsrc/components/ClinicalDashboard.tsx
Comment threadsrc/components/ClinicalDashboard.tsx Outdated
Comment threadscripts/seed-registry-records.ts
- ClinicalDashboard: drop the auto-applied Services/Forms mode-default
label scope. It routed every mode search through resolveSearchScope's
label path, whose single document_labels.in(<all ids>) request builds
an over-long PostgREST URL that fails on large libraries (the live
corpus has 2065 docs). Corpus search now runs unscoped like Documents;
users opt into label filters explicitly.
- services/forms home footers: count verified records from the API's
authoritative governance (validation_status) via
countVerifiedRegistryRecords, not the copied fixture verification JSON,
so later review/downgrade is reflected. Hook now exposes a governance
map; the demo API path derives it from fixtures.
- registry-record-loader: the unauthorized detail state now offers a
'Go to sign in' action routing to the auth-capable dashboard, instead
of only a home link with no way to start sign-in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BigSimmo
BigSimmo merged commit 7d75920 into mainJul 2, 2026
4 checks passed
BigSimmo added a commit that referenced this pull request Jul 2, 2026
- registry detail hook + [slug] API now carry governance; the loader
reconciles the verified badge from the authoritative validation_status
so a reviewed/downgraded record no longer shows the stale fixture
verification state.
- DocumentSearchResultsPanel takes recordStatus and renders a loading/
sign-in/error notice for Services/Forms record matches, so the main
dashboard no longer silently shows zero cards when the registry fails.
- seed-registry-records preserves existing governance columns
(source_status/validation_status/last_reviewed_at/review_due_at) on
reseed, so a fixture-copy reseed can't downgrade reviewed rows.
- the 'apply default mode scope to shortcut searches' finding is moot:
the mode-default scope was removed entirely in the prior commit, so
executeSearch and shortcut searches are consistently unscoped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:8f094ade63

ℹ️ 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".

.from("clinical_registry_record_sources")
.select("record_id, document_id")
.eq("owner_id", user.id)
.in("record_id", recordIds);

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 Batch optional registry source lookups

When an owner has hundreds of registry rows, this .in("record_id", recordIds) serializes every row UUID into one PostgREST request (roughly 20KB at the 500-row cap) before the list response can be returned. The current list hook does not consume linkedDocumentIds, so this optional source-link lookup can hit URL/proxy limits or otherwise fail the whole Services/Forms registry load even though the records themselves were fetched successfully; omit it from the list endpoint or fetch the links in smaller pages/batches.

Useful? React with 👍 / 👎.

}

export function FormsHomePage() {
const registry = useRegistryRecords("form");

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 Handle empty registry states on mode homes

When an authenticated owner is newly migrated but not seeded yet, or this registry request returns unauthorized/error, the hook state is only used to hide/change the footer while the Forms home still renders the static task cards, including the fixture default detail link; clicking it lands on a registry-backed 404 with no empty/sign-in/error state. Gate the actions on registry.status/registry.total or show the same registry status/empty notice here (the Services home uses the same pattern).

Useful? React with 👍 / 👎.

status === "loading"
? { icon: Loader2, spin: true, tone: "info", text: "Loading your forms registry..." }
: status === "unauthorized"
? { icon: Shield, spin: false, tone: "warning", text: "Sign in to search your forms registry." }

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 Provide a sign-in action on forms search

On an authenticated deployment, a signed-out user who lands directly on /forms?q=...&run=1 now gets only this text notice because the route returns FormsSearchResultsPage instead of the auth-capable dashboard shell. There is no AuthPanel or sign-in link on this standalone search page, so the user cannot act on the message; add a sign-in control here or route unauthorized users to a surface that can start sign-in.

Useful? React with 👍 / 👎.

@BigSimmo

Copy link
Copy Markdown
OwnerAuthor

PR #209 was merged before the final late review comments could land on that branch. I cherry-picked the post-merge review fixes and addressed the remaining registry-state comments in follow-up PR #217: #217

BigSimmo added a commit that referenced this pull request Jul 3, 2026
BigSimmo added a commit that referenced this pull request Aug 2, 2026
…+8) (#1576)
* DS V2 PR-E: answer safety, form foundation, announcements (slices 6+7+8)
Build the five specified safety components so a degraded answer, an absent
clinical value, an invalid field and a route change all become states the type
system and assistive technology can see.
Slice 6 - answer safety. VerificationNotice owns eight approved strings (four
states x clinician/plain); AnswerCard requires both `state: AnswerState` and
`verification`, and a degraded state cannot be constructed without the
`onOpenSource` route that turns the caution into an affordance. DoseLine takes a
structured dose model, composes Quantity, and marks overdue in three channels
(amber rule + "Source review overdue" + StatusMark shape). MissingValue replaces
the bare dash with four phrases. DateDisplay takes ISO only and renders <time>.
AnswerFooter now names an absent field instead of dropping it - on a provenance
strip the absence is the governance signal. answerClipboardText carries the
degraded caveat out of the app and routes provenance through the one existing
clipboardProvenanceLine() implementation.
Slice 7 - FormField/FieldHint/FieldError/ErrorSummary. Hint and error are both
in the DOM and both in describedBy when invalid; caller ids merge ahead of them
rather than being overwritten; required/optional is label text; ErrorSummary
takes focus rather than announcing.
Slice 8 - LiveAnnouncer/RouteAnnouncer/announce. One singleton owns every
announcement, with a dedupe window and a queue gap; route change moves focus to
the new <h1> unless focus sits inside a dialog or a preserve-focus workflow.
Step 0 contract pre-check passed with no change to src/lib/rag/** or
source-review: ready, stale_evidence and source_only all project from fields the
retrieval layer already decides, so no component infers staleness from a date.
partial_retrieval has no producer and is recorded as such in SPEC, COMPONENTS,
/issues #206 and tests/answer-state-contract.test.ts.
All components are built, not registered - registration and product adoption
stay in PR 13.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ds-v2): tighten answer-safety states after clinical governance review
Eight findings from the PR 6 clinical governance review, all in code this
branch introduced, all safety-positive. No product surface imports any of
these modules yet, so nothing rendered changes.
Counting and identity
- answerStateFromRetrieval() counts and keys by document, not chunk.
RagAnswer.sources is chunk-level, so the previous projection produced
duplicate React keys, repeated a document as several overdue rows, and
reported fractions that were wrong in both directions — including the
direction that under-warns ("1 of 6" when half the documents are stale).
- Where chunks of one document disagree on governance status, the more
severe reading wins.
Governance status carried, not collapsed
- OverdueSource gains `status`, and DoseRow replaces the optional
`overdue` boolean with a required `status: DocumentStatus`. An overdue
status additionally requires `source`, so a warned row with no route
back to the document is unrepresentable. `outdated` (superseded) now
renders the slashed mark and the word "superseded" rather than the
half-ring "review overdue" vocabulary of a document still in force.
Cautions that cannot argue against themselves
- stale_evidence with an empty overdue list now throws in development
(mirroring the partial_retrieval guard) and, in production, states the
caution without a count instead of "0 of 3 sources are past their
review date". Same rule in answerClipboardText().
- VerificationNotice falls back to the most cautionary wording for an
unrecognised state, and logs once, rather than failing open to `ready`.
- The stale clinician wording says "some of which are past their review
date" rather than asserting every cited source is overdue.
- SourceOnlyBody no longer claims "nothing has been paraphrased": the
tier is inferred from the routing mode and the extractive builder
composes sections, so the surface cannot stand behind verbatim fidelity.
Provenance precision
- DateDisplay renders a date-only ISO as a date, even for kind="generated".
It previously printed "14/03/2026, 08:00" for "2026-03-14" — a precision
that was never recorded, on a provenance strip.
Clipboard (exceeds the original SPEC slice-8 scope, deliberately)
- answerClipboardText() now carries attribution and "Verify against the
linked source documents before clinical use." on every state including
`ready`, enumerates the cited documents, and suppresses the
single-document provenance line where it would contradict a
multi-source stale caveat. A copied answer loses the banner, the notice
and the links; unattributed clinical prose in a record reads as
clinician-endorsed. It remains narrower than formatAnswerRenderCopyText
and is not a replacement for it.
Recorded rather than fixed, so PR 13 cannot inherit them as clearance:
AnswerState has no channel for an ungrounded answer (#208), the clipboard
constraint above (#209), and the Gate 1 contrast pair for --warning at
text tier plus the per-process logged-once Sets (#210). All three are
also written into SPEC.md and COMPONENTS.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(ledger): record PR-E answer-safety review at 8ad91e3
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ds-v2): resolve PR-E review findings for answer safety
Filter staleness to cited supporting sources, reject rolled calendar dates,
make duplicate production announcers a no-op, re-focus ErrorSummary on
repeated submits, clear-then-set identical live announcements, and realign
AnswerCard/DoseLine dtsPropsFor declarations.
* fix(ds-v2): suppress duplicate LiveAnnouncer without lint cascade
Keep production duplicate ownership in an effect with a targeted
set-state-in-effect exemption, matching the repo pattern, so the
duplicate never registers a listener and unmounts its live regions.
* fix(ds-v2): address Copilot findings on banner and form tests
Import ComponentProps from react, require exact overdue/sourceCount
equality for the totality headline, and derive the partial-retrieval
numerator from the rendered missing list.
* fix(ds-v2): address CodeRabbit findings on answer-safety surfaces
Merge the broken OverdueSource doc comment, omit clipboard provenance
when metadata is absent, keep unidentified overdue sources as stale,
give ErrorSummary an accessible heading name, fail over LiveAnnouncer
ownership when the active instance unmounts, harden MissingValue phrase
lookup against prototype keys, and treat sourceCount underflow as totality.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
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.

2 participants

@BigSimmo