The folded query summary never says the local-only filter is armed
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 88/100
- Issue type
- Feature
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- frontend
Research direction
Start with src/components/features/JobQuerySummary.tsx:41-60 and src/lib/job-search/query-steps.ts:93-101, then run the focused tests in JobQuerySummary.test.ts and query-steps.test.ts. Update both summaries so an armed location-only filter with a location is represented while inactive or locationless states remain unchanged, and pin both directions in the tests.
Written by the indexing model from the issue text.
Description
Problem
locationOnly (#809, shipped in #905) is the only control in the job-search lane that lets
location remove a posting. It is the one axis the folded query summary does not mention,
so the summary reads identically whether the hard filter is armed or not.
Both summarizers enumerate the query's axes field by field, and neither reads the flag:
src/components/features/JobQuerySummary.tsx:41-60—summarizeQuerypushes titles,
location, seniority, a skill count, an excluded count, the comp floor and a company count.src/lib/job-search/query-steps.ts:93-101—filtersSummarypushes location, the excluded
count, the comp floor and a board count. This is thefiltersstep summary in the query
rail.
Why this is a contract violation, not just a missing nicety
JobQuerySummary.tsx's own docblock states the rule the omission breaks:
/jobs/gives the results the full page width by FOLDING the query controls away once a
search has run (FindJobsPanel). Folding a form the user has just filled in is only safe if
what it contained stays legible — otherwise the collapsed state is amnesia... every axis
that can change the result set gets a segment, so the summary and the ranking can't silently
disagree.
It also states the reason silent axes are omitted — "printing it would imply a filter is doing
work when it isn't" — which is the exact inverse of this case: the filter is doing work and
nothing says so.
The lane's other hard filter already follows the rule. excludeTerms gets 1 excluded, and
the comp floor gets ≥ $185k. locationOnly is the third remover (the lane's CLAUDE.md
now names all three) and the only one absent from both summaries.
Failing scenario
- Search for a role with
location: "Austin, TX", local-only off. Summary reads
Frontend Engineer · Austin, TX · 3 skills. - Tick Only jobs near Austin, TX in the results strip. Postings elsewhere are dropped.
- The summary still reads
Frontend Engineer · Austin, TX · 3 skills— byte-identical.
The two states differ in what the result set contains, and the line whose stated job is to keep
the folded query legible cannot tell them apart. Collapsing the form and returning later — or
reading the rail's filters step — gives no indication that a hard filter is removing postings.
Scope of the impact
Bounded, which is why this is not a blocker on #905:
- The strip's checkbox is rendered whenever the ranked set is non-empty, so the armed state is
visible on screen while the user is looking at the results. locationFilteredOutalready states a count in a notice ("N postings hidden as too far away
— untick the local-only filter above"), which is what satisfies #809's "stated as a count and
recoverable" criterion.
What is missing is the summary's account of it: the one-line description of the query behind
the results, and the rail step that claims to carry the current value of its own fields.
Both summaries are pinned by tests that will need the new segment
src/components/features/JobQuerySummary.test.ts:43asserts the whole array by equality
(toEqual([...])), so a new segment must be added there deliberately.src/lib/job-search/query-steps.test.ts:63— "assembles the filters summary from only the
axes that are set" — enumerates the axes and asserts the location-only query renders exactly
"Remote".
That is a feature, not an obstacle: both tests are the place to state what the new segment says.
Proposed fix
One segment in each summarizer, rendered only when the flag is armed and a location is set
(the flag is inert without one, and refineSearchResult ignores it):
// JobQuerySummary.ts — replace the unconditional location push
const location = query.location?.trim();
parts.push(location ? location : "anywhere");
if (location && query.locationOnly) parts.push("local only");
// query-steps.ts — filtersSummary
if (query.location) parts.push(query.location);
if (query.location && query.locationOnly) parts.push("local only");
Wording is the open question, not the placement. local only matches the control's own name
("Only jobs near {location}") without repeating the location that already sits beside it, and
the lane's copy rule in JobSearchNotices.tsx — name the control the way the control names
itself — applies here too. An alternative worth considering is folding it into the location
segment (Austin, TX (only)), which costs a segment but reads closer to the toggle.
Acceptance criteria
- With
locationOnlyarmed and a location set, bothsummarizeQueryandfiltersSummary
carry a segment saying so. - With the flag off, or with no location set, both summaries are byte-identical to today —
no empty segment, no "not local only". JobQuerySummary.test.tsandquery-steps.test.tspin both directions.- The wording matches the checkbox's own label register, per the copy rule in
JobSearchNotices.tsx.
Provenance
Found reviewing #905 (which introduced locationOnly). The flag is correct and the strip's
checkbox is the right control; only the two summarizers were not extended to read it. Both live
outside #905's diff, so this is filed rather than folded into that PR.
- Dominant language
- TypeScript
- Stars
- 11
- Forks
- 4
- Avg merge
- 1d 5h
- Merged PRs (30d)
- 66
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from offlinecv/OfflineCV
-
chore gaal
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
documentation
Difficulty 1/5 Under an hour Newbie friendliness 92/100
Maintainers usually reply within 1 day
-
documentation
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
documentation
Difficulty 1/5 Under an hour Newbie friendliness 92/100
Maintainers usually reply within 1 day
-
chore refactor
Difficulty 4/5 3-5 days Newbie friendliness 48/100
Maintainers usually reply within 1 day
All issues in offlinecv/OfflineCV
Similar issues
-
resources
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
railmapgen/rmg-palette#2445 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
danielmiessler/LifeOS#2242 ·
Maintainers usually reply within 5 days
-
good first issue hacktoberfest help wanted translation
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
callstackincubator/appduct#129 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 88/100