Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3061938
feat(filters): services adopts the filter contract (PR C)
claude Aug 12, 2026
5cf82c5
chore(ledger): record PR C review (services filter-contract adoption)
claude Aug 12, 2026
69dd04d
Merge origin/main into claude/plan-implementation-3t2vl5
claude Aug 12, 2026
ecec65d
chore(ledger): record services filter-contract adoption (PR C rebase,…
claude Aug 12, 2026
119d169
fix(services): fall through to All items scope when the query itself …
claude Aug 12, 2026
13dfbd4
chore(ledger): record the resultScope query-empty fallthrough fix on …
claude Aug 12, 2026
7881885
fix(filter-sheet): use useEffect instead of setState-in-render for ch…
Copilot Aug 12, 2026
c1e09ba
docs: point services ledger to superseding PR 1878
BigSimmo Aug 12, 2026
24c6d96
fix(filters): keep selected facet groups discoverable
BigSimmo Aug 12, 2026
6d376c6
perf(services): retain only facet tags in registry payload
BigSimmo Aug 12, 2026
d98129e
fix(services): preserve legacy records during facet filtering
BigSimmo Aug 12, 2026
130c5a8
test(services): cover legacy records under active facets
BigSimmo Aug 12, 2026
1c60a8b
fix(services): preserve comma-bearing facet values
BigSimmo Aug 12, 2026
ba62865
test(services): cover comma-bearing facet URLs
BigSimmo Aug 12, 2026
4c366a3
test(services): follow quick search and facet clearing journeys
BigSimmo Aug 12, 2026
319f880
test(services): remove obsolete filter clear journey
BigSimmo Aug 12, 2026
d6be0b3
test(services): correct URL regex escaping
BigSimmo Aug 12, 2026
6a4fbb1
perf(services): memoize filter group construction
BigSimmo Aug 12, 2026
56c2d02
refactor(services): stabilize filter callbacks
BigSimmo Aug 12, 2026
b76d39d
fix(services): include stable filter handlers in memo dependencies
BigSimmo Aug 12, 2026
f2febe1
docs(ledger): preserve current rows and append PR 1878 records
BigSimmo Aug 12, 2026
24c12df
docs(ledger): preserve the current main history before appending revi…
BigSimmo Aug 12, 2026
edbcc0e
docs(ledger): append new records without rewriting history
BigSimmo Aug 12, 2026
4b0da78
perf(services): keep seeded registry payloads facet-sized
BigSimmo Aug 12, 2026
f693223
fix(services): narrow legacy seeded payloads at response merge
BigSimmo Aug 12, 2026
2d1fd58
Merge remote-tracking branch 'refs/remotes/origin/main' into HEAD
BigSimmo Aug 12, 2026
748bb37
Fix merged services test syntax
BigSimmo Aug 12, 2026
f24c86c
Repair merged services selection handler
BigSimmo Aug 12, 2026
99c1850
Remove stale service filter memo dependency
BigSimmo Aug 12, 2026
4c63eab
fix: mark unavailable ledger head explicitly
BigSimmo Aug 13, 2026
0b29391
Merge current main into PR 1878
BigSimmo Aug 13, 2026
729ecc7
Restore canonical ledgers after main sync
BigSimmo Aug 13, 2026
8003209
Avoid effect state reset in filter sheet
BigSimmo Aug 13, 2026
219f15a
Merge branch 'main' into claude/filter-services-v2
BigSimmo Aug 13, 2026
27844e8
Align services journeys with filter sheet contract
BigSimmo Aug 13, 2026
4947b6b
Merge remote-tracking branch 'refs/remotes/origin/claude/filter-servi…
BigSimmo Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/design-system/COMPONENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,7 @@ This generated snapshot is a local source-derived inventory. It does not assert
| `RetrievalStateBanner` | answer | yes | yes | inherited-global-root | yes | no | 1 |
| `SafeBoldText` | layout | yes | yes | inherited-global-root | yes | no | 8 |
| `SearchField` | controls | yes | yes | no | yes | no | 0 |
| `SegmentedControl` | controls | yes | yes | inherited-global-root | yes | no | 8 |
| `SegmentedControl` | controls | yes | yes | inherited-global-root | yes | no | 9 |
| `Select` | controls | yes | yes | inherited-global-root | yes | no | 2 |
| `Sheet` | layout | yes | yes | inherited-global-root | yes | no | 27 |
| `Skeleton` | feedback | yes | yes | inherited-global-root | yes | no | 6 |
Expand Down
2 changes: 2 additions & 0 deletions docs/design-system/adoption-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,7 @@
"src/components/clinical-dashboard/settings-dialog.tsx",
"src/components/factsheets/factsheets-search-page.tsx",
"src/components/in-page-nav/in-page-nav-header.tsx",
"src/components/services/services-navigator-page.tsx",
"src/components/specifiers/specifiers-home-page.tsx"
],
"productImportFiles": [
Expand All @@ -1410,6 +1411,7 @@
"src/components/clinical-dashboard/settings-dialog.tsx",
"src/components/factsheets/factsheets-search-page.tsx",
"src/components/in-page-nav/in-page-nav-header.tsx",
"src/components/services/services-navigator-page.tsx",
"src/components/specifiers/specifiers-home-page.tsx"
],
"designSync": {
Expand Down
46 changes: 37 additions & 9 deletions docs/filter-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ which choice did it.
Where a mode has a catalogue meaningfully larger than the current result set, the sheet offers a
scope segment — `These results N | All items N`, counts on both — built from the shared
`SegmentedControl` (`src/components/ui/segmented-control.tsx`), which already has the roving
tabindex and radio semantics.
tabindex and radio semantics. `ResultFilterSheet` reserves the slot (`scopeControl`) but does not
build the segment itself: "meaningfully larger" and what the two counts mean are per-mode
judgements the shared renderer cannot make. Services is the first mode to use it — see
`services-navigator-page.tsx`: the segment is gated on the catalogue exceeding the query/group
scoped result set (not the facet-narrowed one, so the segment does not flicker away as facets are
applied), and both counts reflect the current facet/lens selection.

It earns its place because it is the only escape from a filtered-to-zero state that does not
discard the query: the commit becomes "Show N in all items" instead of a dead end.
Expand All @@ -110,16 +115,30 @@ corpus of that size, and it stays.

Facet groups only. Thresholds match the rule documents already uses (`dense = groups.length > 3`).

| Options | Renderer |
| ------------------- | ----------------------------------------------------------------------- |
| ≤ 5 | chips, single row where they fit |
| 6 – 20 | dense list: full-width rows, right-aligned count column, group headings |
| > 20, or > 3 groups | dense list plus find-a-filter and collapse-by-default |
| Options | Renderer |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| ≤ 5 | chips, single row where they fit |
| 6 – 20 | chips, same rendering as above — the group is still small enough on its own; it is the >3-groups tier below that adds the chrome |
| > 20, or > 3 groups | chips plus find-a-filter and collapse-by-default, per group |

The `> 3 groups` tier lives directly in `ResultFilterSheet` (`result-filter-control.tsx`), not as a
second hand-rolled panel: a "Find a filter…" field appears once, above the groups, and every facet
group becomes a disclosure. Below the threshold every group renders exactly as it did before this
was added — formulation (one facet group) never crosses it, so its sheet is byte-for-byte
unchanged. Services (five facet groups) is the first mode to cross it.

This intentionally does not yet build the "full-width rows, right-aligned count column" list
layout an earlier draft of this section described for the 6–20 band — a group that size still
renders as wrapping chips, just collapsible. `document-search-results.tsx` keeps its own,
separate dense-list implementation (needle + collapse, chip rendering) for now; porting it up as
the shared `> 20` tier and converging both call sites is PR F's job, not this one's — see
"Rollout" below and `docs/outstanding-issues.md` `#170`.

Collapse rules, when they apply: groups start collapsed; a group holding a selection opens
itself; an explicit user collapse beats that; an active needle forces every matched group open
and owns openness. A selected option always survives the needle, so an active constraint can
never become unreachable.
never become unreachable. A group whose options are all filtered out by the needle disappears
rather than showing an empty heading.

## 6. Invariants

Expand Down Expand Up @@ -153,8 +172,17 @@ Contract first, then one PR per mode:
so this PR converges its desktop rail onto `SegmentedControl` sharing one counted option array
with the phone sheet, rather than evicting anything — there was no query-replacing preset to
evict.
3. **Services** — evict the six query-replacing quick filters to the composer, alongside its own
facet/scope work (open as a separate PR).
applications, specifiers (all `lens`, PR A) and formulation (`facet`, PR B). Also done for
factsheets: its category dimension was already a real `lens` (see the corrected note above),
so this PR converges its desktop rail onto `SegmentedControl` sharing one counted option array
with the phone sheet, rather than evicting anything — there was no query-replacing preset to
evict.
3. **Services** — evict the six query-replacing quick filters to the composer. Done for services:
five facets (catchments, age_groups, setting_flags, acuity_flags, housing_flags),
substance_flags as a lens (an exact partition, not an accumulating constraint — see
`src/lib/service-facets.ts`), a URL round-trip alongside `q`/`group`, and the scope segment
(section 4). Services is also the first mode dense enough (5 facet groups) to exercise the
`> 3 groups` chrome added to the shared sheet for this — see section 5.
4. **Documents last** — port its needle and collapse up into the shared component as the
`> 20` tier, then converge. It is the largest surface and should move once the contract is
proven elsewhere.
Loading
Loading