HarperFast / HarperFast/studio
Admin "All Orgs" name filter is case-sensitive — typing `col` misses organizations named `Col`
- Dominant language
- TypeScript
- Stars
- 5
- Forks
- 4
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 40
Description
## Summary
With the **All Orgs** toggle on, typing `col` into "Filter by name" on `/organizations` returns
organizations whose name contains `col` but not ones containing `Col`. Turn the same toggle off
and the same input is case-insensitive. One text box, two different matching rules, decided by a
switch sitting next to it.
This hits Harper staff specifically — All Orgs is gated on the `org:read` staff permission — and
it is exactly the population that needs the filter most, because they are the only ones looking
at every organization at once.
## Why
Two different filters back that one input, in `src/features/organizations/index.tsx`:
| Toggle | Path | Case handling |
| --- | --- | --- |
| off | client-side `curryFilterByFuzzySearch` (`index.tsx:97`) | lowercases both sides (`src/lib/string/filterByFuzzySearch.ts:2,15`) — correct |
| on | server-side query from `buildAllOrganizationsUrl` (`src/features/organizations/queries/getAllOrganizations.ts:27`) | `name=ct=` — case-sensitive |
`isServerSearch` at `index.tsx:71` is what selects between them, and the server branch builds:
```ts
...(nameFilter ? [`name=ct=${encodeURIComponent(nameFilter)}`] : []),
```
Harper's `contains` comparator matches exact case, so the filter is exact-case.
`onFilterByNameChanged` (`index.tsx:117-119`) deliberately preserves the raw casing, with a
comment saying the server-side admin filter wants the value as typed — so the casing survives
all the way into the query. The clusters list does the opposite at `ClustersList.tsx:30`
(`.toLowerCase()` on input); that path is client-only so it is fine, but the two filter inputs
are inconsistent with each other as well.
The current behaviour is pinned by a test, which will need updating:
`src/features/organizations/queries/getAllOrganizations.test.ts:143-151` asserts the URL is
`/Admin/Organization/?name=ct=Acme&...`.
## Expected
`col` matches `col`, `Col` and `COL`, with the toggle in either position.
## Fix — the RQL string alone cannot do it
Harper's search comparators are `equals`, `starts_with`, `contains`, `ends_with`, `between`,
`not_equal` and the range operators. None is case-insensitive, so there is no drop-in
replacement for `=ct=`. Two workable routes, both needing a small Central Manager change:
1. **Central Manager exposes a case-insensitive name filter** on the admin organization listing
— a dedicated query param, or a custom `search()` that case-folds before matching. Studio
keeps sending the value as typed and only `buildAllOrganizationsUrl` changes. Simplest on
this side.
2. **Central Manager stores a normalized (trimmed, case-folded) name** next to the display name
and Studio filters against that. More upfront work, but it is indexable — `contains` is
documented as requiring a full scan, so today's filter already scans every organization on
every keystroke-debounce.
Either way the Studio-side change is confined to `buildAllOrganizationsUrl` and its test.
## Also worth checking while in here
The same query pins `sort(name)` (`getAllOrganizations.ts:30`). If that ordering is byte-wise,
every capitalized name sorts ahead of every lowercase one, which would scatter near-identical
names across different pages of the admin list — the same "where did it go" problem by a
different route. **I did not verify this**; it is a question for whoever picks the issue up, not
a claim.
## Related
- HarperFast/studio#1270 — server-side paging and filtering for organizations
- HarperFast/central-manager#805 (internal) covers the other half of this: organization and cluster
names currently admit duplicates that differ only by letter case. Related but separate —
normalization stops new collisions, this issue makes the ones we already have findable.
Verified at `b71d4341e65fbbfae47141dd68a172e5287f2d68`.
Contributor guide
Research direction
Start in src/features/organizations/queries/getAllOrganizations.ts and its test at lines 143-151, then trace isServerSearch and the All Orgs path in src/features/organizations/index.tsx. Confirm with the Central Manager team which case-insensitive name-filter contract is available before changing the Studio query. Done means the test and implementation support matching col, Col, and COL with either toggle state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100