Skip to content

feat(filters): services adopts the filter contract (PR C) - #1866

Closed
BigSimmo wants to merge 11 commits into
mainfrom
claude/plan-implementation-3t2vl5
Closed

feat(filters): services adopts the filter contract (PR C)#1866
BigSimmo wants to merge 11 commits into
mainfrom
claude/plan-implementation-3t2vl5

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

  • PR C of the filter-contract rollout (docs/filter-contract.md, tracked in #170): services adopts the shared filter contract — five real facets, one lens, a URL round-trip, the scope segment, and the six query-replacing quick filters evicted to suggested searches.
  • Fixes a real data-plumbing gap the rollout plan flagged as a spike to verify: catalogToServiceRecord never populated ServiceRecord.catalogPayload, so it was undefined in demo mode, anonymous/public access, and for any owner who had never run ensureRegistrySeeded — i.e. most real traffic. Now populated directly in the mapper.
  • Adds the > 3 facet groups density tier (find-a-filter + collapse-by-default) directly to the shared ResultFilterSheet, since services is the first mode to need it (5 facet groups). This is the gap the Codex reviewer flagged on PR feat(filters): make formulation a real facet surface #1858 (tracked there as #309, not yet on this branch since that PR is unmerged) — formulation's 9-option facet group bypassed docs/filter-contract.md section 5's density rule. Below the threshold every existing sheet renders byte-for-byte as before (formulation has one facet group, never crosses it).
  • Fixes two real invariant violations surfaced while wiring this up:
    • onClearAll previously called clearServiceQuery, wiping the search box on "Clear filters" — a direct section-6 violation (clearing filters and clearing a search are different intentions). The new onClearAll only touches facet/lens/scope URL params.
    • The zero-results empty state still said "remove the current quick filter" — stale once quick filters stopped filtering — and had no way to back out of a facet combination that zeroed the list. It now names whichever cause applies and offers a working "Clear filters" action alongside "Show all services".

What changed

  • src/lib/service-catalog-mapper.tscatalogToServiceRecord now sets catalogPayload to the full CatalogService, matching the doc comment's stated intent (service-ranker.ts's "Full source-specific payload retained in the registry JSONB column"). No schema/migration — purely a client-side mapper fix.
  • src/lib/service-facets.ts (new) — the typed facet index: which dimension is a lens vs a facet (measured against loadServicesSnapshot()substance_flags is an exact partition, all 219 services carry exactly one of general/aod; housing_flags is a near-partition, 4 services carry two, so it stays a facet), OR-within-dimension/AND-across-dimension matching, non-additive union counts (serviceFacetOptionCount), derive-from-data option lists, per-dimension value labels, and the URL round-trip helpers.
  • src/components/clinical-dashboard/result-filter-control.tsxResultFilterSheet gains the > 3 facet groups density chrome (find-a-filter field, per-group collapse-by-default, needle-filtered options that always keep a selected option reachable) and a scopeControl slot for the section-4 scope segment. No behavior change below the threshold.
  • src/components/services/services-navigator-page.tsx — facet/lens/scope selection state lives in the URL alongside q/group; the scope segment (These results N | All items N) renders when the catalogue (219) is meaningfully larger than the query/group-scoped result set; the six quick filters (Best fit, Crisis, Culturally safe, Phone referral, Free, WA) move to AnswerSuggestionChips below the band as suggested searches instead of living inside the filter sheet; the zero-results empty state fix above.
  • docs/filter-contract.md — documents the density chrome and scope slot, updates the Rollout section, and corrects section 5's table to match what actually shipped (a > 20-band "full-width row list" renderer does not exist yet — that's still document-search-results.tsx's own implementation, ported up in PR F).
  • docs/outstanding-issues.md#170 — records services' completion.
  • docs/design-system/{COMPONENTS.md,adoption-manifest.json} — regenerated (npm run design-system:adoption:update) for the new SegmentedControl consumer (services' scope segment).
  • Tests: tests/service-facets.test.ts (new, 13 tests — partition/near-partition measurements, OR/AND matching, non-additive union counts, dead-end-never-on-selected, derive-not-declare, URL round-trip, malformed-payload tolerance) and 7 new DOM tests in tests/search-results-header-band.dom.test.tsx for the density chrome (below/at/above threshold, collapse-by-default, explicit collapse, needle filtering + selected-survives-needle, empty-group-disappears, no-match message, scope slot rendering).

Verification

  • npm run test:focused -- --files <touched paths> — 286 passed
  • npm run test — 6125 passed, 4 skipped, 0 failed
  • npm run lint / npm run typecheck — clean
  • npm run verify:pr-local — all 15 steps green (check:runtime, check:installed-lock-parity, format:changed, sitemap:check, docs:check-index, docs:check-inventory, docs:check-scripts, docs:check-links, check:branch-review-ledger, check:outstanding-issues, lint, typecheck, test, build, check:rag:fixtures), run against the final diff after formatting
  • npm run check:bundle-budget on a freshly rebuilt .next — production 1302.4 KiB gzip vs 1279.1 KiB baseline (within 10% tolerance), mockups 285.1 KiB vs 267.5 KiB baseline (within 25% tolerance)
  • npm run verify:uinot run: Playwright 1.62.1 wants chromium/chrome-headless-shell revision 1234; this environment has 1194 preinstalled (/opt/pw-browsers). Preflight fails closed with Playwright browser preflight failed before the production build. Missing executable(s): ... Do not treat a later "N failed" summary as a product regression when the browser binary is absent. Delegated to CI's Production UI job, matching PR feat(filters): adopt the lens contract across the four one-of-N modes #1857/feat(filters): make formulation a real facet surface #1858's own precedent for the same environment limitation.
  • Manual browser proof via a Playwright script against the running dev server (demo mode, so this also directly exercises the catalogPayload fix): 0px horizontal overflow at 1440/800/390/320px, sheet open and closed; facet toggle → URL updates (?catchments=...) and footer count updates; substance lens toggle → URL (?substance=aod) and footer (71 showing, matching the measured crosstab); scope segment hidden while browsing all services, shown and correctly counted (These results 45 | All items 219) once narrowed by a query, switching to "All items" updates the URL (?scope=all); suggestion chips render the six evicted quick filters and apply as a new search; dense chrome (find-a-filter field, collapsed-by-default groups) present at every breakpoint; tap targets measured at 48px (the min-h-tap floor); a genuinely contradictory facet combination reaches the fixed zero-results state, and "Clear filters" there actually recovers the list.
  • npm run check:outstanding-issues — 306 rows (115 open, 191 archived), no ids deleted from base

Risk and rollout

  • Risk: UI-only change to the services filter surface and a shared component (result-filter-control.tsx) already used by 6 other modes. The density addition is gated behind > 3 facet groups, which is false for every existing call site today (formulation has 1), so this PR changes zero rendered output anywhere except services. No schema, Supabase, or provider changes.
  • Rollback: revert. No data migration, no stored-state change (facet selection lives only in the URL).
  • Provider or production effects: None.

Notes


Generated by Claude Code

Five real facets (catchments, age_groups, setting_flags, acuity_flags,
housing_flags) plus substance_flags as a lens (an exact partition: all 219
services carry exactly one of general/aod), a URL round-trip alongside
q/group, the scope segment, and the six query-replacing quick filters
evicted to suggested searches below the band.
Fixes catalogToServiceRecord never populating ServiceRecord.catalogPayload,
which the rollout plan flagged as a spike to verify — it was undefined in
demo mode, anonymous/public access, and for any owner who had never run
ensureRegistrySeeded. Also fixes onClearAll wiping the search box (a
docs/filter-contract.md section 6 violation) and a stale/dead-end
zero-results empty state.
Adds the >3-facet-groups density tier (find-a-filter + collapse-by-default)
directly to the shared ResultFilterSheet, since services is the first mode
dense enough to need it. Below the threshold every existing sheet renders
unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5c1ZWQUTwmhgXXdVd2wuK
@supabase

supabaseBot commented Aug 12, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in:48 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d6b5ad3-07ca-4569-9e7d-1e999db24bec

📥 Commits

Reviewing files that changed from the base of the PR and between 5480de1 and 8fe2831.

📒 Files selected for processing (12)
  • .github/workflows/pr1866-repository-bundle.yml
  • docs/branch-review-ledger.md
  • docs/design-system/COMPONENTS.md
  • docs/design-system/adoption-manifest.json
  • docs/filter-contract.md
  • docs/outstanding-issues.md
  • src/components/clinical-dashboard/result-filter-control.tsx
  • src/components/services/services-navigator-page.tsx
  • src/lib/service-catalog-mapper.ts
  • src/lib/service-facets.ts
  • tests/search-results-header-band.dom.test.tsx
  • tests/service-facets.test.ts

Comment @coderabbitai help to get the list of available commands.

@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:30619385bc

ℹ️ 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/services/services-navigator-page.tsx

CopilotAI 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 is PR C of the filter-contract rollout, migrating services to the shared filter contract by introducing a typed facet/lens model with URL round-tripping, adding the scope segment support, and extending the shared ResultFilterSheet to handle “dense” facet surfaces (>3 facet groups).

Changes:

  • Add a new typed services facet/lens module (src/lib/service-facets.ts) and wire services filtering state into URL params (facets + lens + scope) in ServicesNavigatorPage.
  • Fix a real data-plumbing gap by ensuring every ServiceRecord produced from the services catalog carries catalogPayload.
  • Extend the shared ResultFilterSheet with dense-mode chrome (find-a-filter + collapse-by-default) and document the updated contract + rollout state.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
tests/service-facets.test.tsAdds unit coverage for services facet/lens behavior, option derivation, union counts, and URL round-trip.
tests/search-results-header-band.dom.test.tsxAdds DOM tests for the new dense facet-group chrome and scope slot rendering in ResultFilterSheet.
src/lib/service-facets.tsIntroduces typed services facet dimensions, labels, matching/counting helpers, and URL param serialization.
src/lib/service-catalog-mapper.tsEnsures mapped service records retain the full catalog payload (catalogPayload).
src/components/services/services-navigator-page.tsxAdopts the shared filter contract for services: facets + lens + scope in URL; quick filters moved to suggestions; empty state updated.
src/components/clinical-dashboard/result-filter-control.tsxEnhances ResultFilterSheet with dense-mode UI (find field, collapsible facet groups, scope slot).
docs/outstanding-issues.mdUpdates rollout tracking row #170 to mark services complete and record remaining modes.
docs/filter-contract.mdUpdates contract docs for scope slot and dense-mode behavior, and refreshes rollout section.
docs/design-system/COMPONENTS.mdRegenerated design-system inventory (SegmentedControl adoption count).
docs/design-system/adoption-manifest.jsonRegenerated adoption manifest (services now consumes SegmentedControl).
docs/branch-review-ledger.mdAppends a ledger entry recording this PR’s review/verification summary.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment threadsrc/components/clinical-dashboard/result-filter-control.tsx
@BigSimmoChatGPT Codex Connector

Copy link
Copy Markdown
OwnerAuthor

@codex Address all currently unresolved inline review comments on this PR against the current GitHub head. Make minimal repository-grounded fixes, add/update focused regression tests, run proportionate verification, and push the fixes to this PR branch. Do not merge, close, rebase, force-push, deploy, or alter auto-merge. Please report the exact commit SHA and test results.

@BigSimmoChatGPT Codex Connector

Copy link
Copy Markdown
OwnerAuthor

@claude Fix both currently unresolved review threads on this PR at the current GitHub head. (1) An unmatched service query followed by Result scope = All items must render faceted catalogue cards rather than the earlier zero-query empty state. Add a focused regression. (2) ResultFilterSheet must not call setChrome during render when chromeResetKey changes; use an effect or preferably a keyed inner state subtree so reset is deterministic without render-phase state mutation, with a regression. Make minimal changes, run focused tests/format/typecheck as proportionate, push only to this PR branch, and report exact SHA/tests. Do not merge, close, rebase, force-push, deploy, alter auto-merge, or add temporary write-capable workflows.

@github-actions

Copy link
Copy Markdown
Contributor

Hi @BigSimmo — thanks for the @claude mention.

Open this in Claude Code on the web to continue here: https://claude.ai/code

Context: BigSimmo/Database · #1866

Automated backlink from the Claude backlink workflow. Claude Code can pick up the mention and push a scoped fix to the branch.

@claude

claudeBot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 2s —— View job


I'll analyze this and get back to you.

BigSimmo pushed a commit that referenced this pull request Aug 12, 2026
…matched nothing
rankedMatches is query-only and never reflects resultScope, so a search
that matched zero services in the whole catalogue always showed the
query-empty state — even after the reader explicitly widened to "All
items", which exists precisely to bypass that. Gate the query-empty
branch on resultScope === "results" so "All items" falls through to the
facet-driven branches, which correctly ignore the query in that scope.
Independently found and verified per docs/filter-contract.md section 4;
not imported from the untrusted commits reported on PR #1866/branch
claude/plan-implementation-3t2vl5.
@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

Testing

  • node scripts/run-vitest.mjs run tests/service-result-state.test.ts tests/search-results-header-band.dom.test.tsx --reporter=dot — 2 files and 59 tests passed.
  • npm run typecheck
  • npm run check:branch-review-ledger
  • npm run format
  • git diff --check
  • ⚠️npm run test — 6,126 tests passed and 1 skipped, but 4 unrelated tests/pr-handoff-stop.test.ts cases timed out in the broad suite; the same jq-unavailable test path remained abnormally slow when isolated.
  • ⚠️git push -u origin codex/pr1866-review-fixes — not published because the environment has no GitHub authentication.
  • ⚠️gh auth status — no authenticated GitHub host is configured, preventing PR creation and review-thread resolution on GitHub.

View task →

@BigSimmoChatGPT Codex Connector

Copy link
Copy Markdown
OwnerAuthor

Closing this stale PR because #1878 is the explicitly rebased successor for the same Services filter-contract work. Please track fixes and review on #1878.

auto-merge was automatically disabled August 12, 2026 20:24

Pull request was closed

@BigSimmoChatGPT Codex Connector

Copy link
Copy Markdown
OwnerAuthor

Final closeout — PR #1866

  • State: closed unmerged at head 8fe28310c170f22bdbe948c13c91dfa4d730387e. There is no merge commit. Auto-merge is no longer armed. Current main is 3dc1f37337d27b1c1496a224b2748ae315c8348e.
  • Sync / merge-tree: GitHub’s final state is dirty / not mergeable. An isolated local merge against the then-current base 439f09656c87f95d37ec479cae9007c53f6cc80c was resolved cleanly, but it was not pushed and became stale when main advanced. No rebase, force-push, merge, deployment, release, Lighthouse, live evaluation, or production test was performed.
  • Review outcomes: the All-items zero-query rendering defect and render-phase setChrome update were both reproduced as real P2 defects. Narrow fixes and regression tests were prepared locally, but publication stopped when this PR was closed. The independent manual adversarial pass also identified an overbroad public/demo catalogPayload projection; a tags-only local fix was prepared but not published.
  • Threads: both actionable inline threads are resolved. Their recorded disposition is that feat(filters): services adopts the filter contract (PR C) #1866 is superseded by replacement PR feat(filters): services adopts the filter contract (PR C, rebased — supersedes #1866) #1878, where the two inherited defects are intended to be maintained and verified. No unresolved review thread remains on feat(filters): services adopts the filter contract (PR C) #1866.
  • Local evidence: merge-conflict resolution was completed in an isolated checkout; ledger/outstanding-issues/docs guards and final merge-tree audit passed against the earlier base; the All-items regression was first proven failing on the unfixed head. Full Node 24/npm 11 verification was not available in the local runtime, so no unrun check is represented as green.
  • Exact-head required CI:PR required is missing and Production UI is missing for 8fe28310…. PR mergeability completed failure. These are blockers, not green checks.
  • Advisory / separate checks:PR Policy completed success; the temporary read-only repository-bundle export completed success; CodeRabbit did not complete a substantive review because its usage limit/spending cap was reached; Supabase correctly skipped because no supabase/ files changed.
  • Ledger: no new exact-head heavy-review ledger record was pushed after closure. The prepared local ledger entry was intentionally not published to a closed, superseded branch.
  • Replacement: PR feat(filters): services adopts the filter contract (PR C, rebased — supersedes #1866) #1878 is the open replacement. It was not modified or reviewed in this feat(filters): services adopts the filter contract (PR C) #1866 closeout.

The PR was not merged. Final merge is left to the user.

BigSimmo added a commit that referenced this pull request Aug 13, 2026
…upersedes #1866) (#1878)
* feat(filters): services adopts the filter contract (PR C)
Five real facets (catchments, age_groups, setting_flags, acuity_flags,
housing_flags) plus substance_flags as a lens (an exact partition: all 219
services carry exactly one of general/aod), a URL round-trip alongside
q/group, the scope segment, and the six query-replacing quick filters
evicted to suggested searches below the band.
Fixes catalogToServiceRecord never populating ServiceRecord.catalogPayload,
which the rollout plan flagged as a spike to verify — it was undefined in
demo mode, anonymous/public access, and for any owner who had never run
ensureRegistrySeeded. Also fixes onClearAll wiping the search box (a
docs/filter-contract.md section 6 violation) and a stale/dead-end
zero-results empty state.
Adds the >3-facet-groups density tier (find-a-filter + collapse-by-default)
directly to the shared ResultFilterSheet, since services is the first mode
dense enough to need it. Below the threshold every existing sheet renders
unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5c1ZWQUTwmhgXXdVd2wuK
* chore(ledger): record PR C review (services filter-contract adoption)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5c1ZWQUTwmhgXXdVd2wuK
* chore(ledger): record services filter-contract adoption (PR C rebase, superseding #1866)
* fix(services): fall through to All items scope when the query itself matched nothing
rankedMatches is query-only and never reflects resultScope, so a search
that matched zero services in the whole catalogue always showed the
query-empty state — even after the reader explicitly widened to "All
items", which exists precisely to bypass that. Gate the query-empty
branch on resultScope === "results" so "All items" falls through to the
facet-driven branches, which correctly ignore the query in that scope.
Independently found and verified per docs/filter-contract.md section 4;
not imported from the untrusted commits reported on PR #1866/branch
claude/plan-implementation-3t2vl5.
* chore(ledger): record the resultScope query-empty fallthrough fix on PR #1878
* fix(filter-sheet): use useEffect instead of setState-in-render for chromeResetKey changes
Co-authored-by: BigSimmo <87357024+BigSimmo@users.noreply.github.com>
* docs: point services ledger to superseding PR 1878
* fix(filters): keep selected facet groups discoverable
* perf(services): retain only facet tags in registry payload
* fix(services): preserve legacy records during facet filtering
* test(services): cover legacy records under active facets
* fix(services): preserve comma-bearing facet values
* test(services): cover comma-bearing facet URLs
* test(services): follow quick search and facet clearing journeys
* test(services): remove obsolete filter clear journey
* test(services): correct URL regex escaping
* perf(services): memoize filter group construction
* refactor(services): stabilize filter callbacks
* fix(services): include stable filter handlers in memo dependencies
* docs(ledger): preserve current rows and append PR 1878 records
* docs(ledger): preserve the current main history before appending review records
* docs(ledger): append new records without rewriting history
* perf(services): keep seeded registry payloads facet-sized
* fix(services): narrow legacy seeded payloads at response merge
* Fix merged services test syntax
* Repair merged services selection handler
* Remove stale service filter memo dependency
* fix: mark unavailable ledger head explicitly
* Restore canonical ledgers after main sync
* Avoid effect state reset in filter sheet
* Align services journeys with filter sheet contract
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.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.

3 participants

@BigSimmo@claude