Skip to content

fix(frontend): rename filter Clear to Select none, inline user errors - #1368

Merged
IAmJSD merged 5 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-727
Oct 24, 2026
Merged

IAmJSD merged 5 commits into
LeanerCloud:mainfrom
IAmJSD:ui/issue-727

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

In a categorical column filter, the footer button labelled "Clear" selects nothing, so a user trying to reset the filter got an empty table (UX-28). A refused user-form save was reported only through a fading toast or a native bubble that showed one problem at a time, with no field-level cue for screen readers (UX-33). This PR renames the categorical button to "Select none" and shows user-form errors inline under each field.

Refs #727
Partial: this PR does not change the group form. Main's #1339 already shows inline notes there for a blank name or permission row (groups/groupFormValidation.ts); its Max Amount, unrepresentable-constraint and empty-permission refusals stay toast-only.

What changed

  • Filter label (994a233a, re-applied in the merge): the categorical footer button reads "Select none" (behavior unchanged); numeric filters keep "Clear". Applied in the shared frontend/src/lib/column-filter-popover.ts (Plans and both RI Exchange tables), frontend/src/lib/history-filter-popover.ts (Purchase History) and recommendations.ts (Opportunities).
  • Inline user-form errors (89a109a5):
    • New frontend/src/lib/field-errors.ts (showFieldError, clearFieldError, clearFieldErrors): a .field-error message under the field or after a given anchor row, aria-invalid, the message appended to aria-describedby (existing help text stays announced), cleared on the next input/change.
    • users/userModals.ts: saveUser collects every problem at once (blank or malformed email, password rules on create, no group, contradictory groups), marks each field and focuses the first. The existing group and password toasts are kept. Opening the modal clears old messages.
    • index.html: #user-form is novalidate.
    • styles/forms.css: lets the red border show on unfocused fields, since the .form-group border rule outranks the global [aria-invalid] one.
  • Tests (4968c82c, 24fc242c): new src/__tests__/form-validation-727.test.ts and tests-e2e/filter-select-none-and-user-form.spec.ts; main's column-filter-popover.test.ts now also asserts the "Select none" and "Clear" labels.
  • Commits: 994a233a, 89a109a5, 4968c82c, the merge with main 9ab53a9a, and 24fc242c (tests updated for main's scoped and shared filter popovers).

Why this approach

Screenshots

Screenshots were taken before this branch was merged with main; before = main at a14dc8f. After is this branch at that point. Both are headless Chromium at 1440x900 against the repo's mocked e2e API.

Before After
filter-popover-footer before filter-popover-footer after
user-form-validation before user-form-validation after
  1. Opportunities > Provider filter after pressing the footer button: before, it is labelled "Clear", every box is unchecked and the table shows "No rows match these filters". After, the same state, but the button reads "Select none".
  2. Create User with a 5-character password and no group, after Save: before, only a transient native bubble on the password, and the missing group is not mentioned until that is fixed. After, both problems show under their fields with red borders, focus is on the first, and the password message is also toasted as before.

Verification

Run on this branch's head 24fc242c.

  • npm run lint: 0 errors. Typecheck and build pass.
  • Jest: 3877 passed, 1 skipped. New form-validation-727.test.ts (11 tests: helper placement, aria-describedby kept and restored, clear on edit; user form shows all problems with focus on the first, blank email, contradictory groups inline plus toast, clear then save, hidden password ignored on edit, reopen resets; history popover "Select none" commits an empty set and numeric "Clear" commits null). column-filter-popover.test.ts checks both labels on the shared popover.
  • Playwright: new filter-select-none-and-user-form.spec.ts (2 tests). Full suite via e2e-local.sh: 203 passed.
  • Secrets scan clean, planted key flagged. test-git-secrets-allowlist: 137 PASS.
  • Merged with main at 5fbedae; conflicts in frontend/src/plans.ts and frontend/src/riexchange.ts resolved, see the merge commit.

Notes for review

  • Design decision: "Select none" rename versus making Clear remove the filter. This branch keeps the empty allow-list behavior and renames the button.
  • Group form: not changed here (see the Partial line). With this PR the user and group forms use parallel helpers (lib/field-errors and main's groups/groupFormValidation.ts). They differ on aria-describedby: this one appends to it, main's replaces it. They could be unified in a follow-up, which could also move group Max Amount onto inline errors.
  • Copy to check: "Enter an email address." and "Enter a valid email address, like name@example.com."
  • Toasts still appear alongside the inline group and password messages. They can be dropped if double messaging is unwanted.

Overlaps

Note for the UI owner

This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.

🤖 Generated with Claude Code

IAmJSD and others added 3 commits October 23, 2026 23:54
In a categorical column-filter popover the footer button sets an
explicit empty allow-list, which hides every row. Labelled "Clear", it
reads as "remove this filter", so a user resetting a filter got an empty
table instead (UX-28). The empty allow-list is deliberate (it lets you
start from nothing and tick one value), so keep the behavior and name it
for what it does. Numeric filters keep "Clear", which does remove the
filter; (All) is how a categorical filter is removed.

Applied to every popover that shares the pattern: Purchase History,
the two RI Exchange tables, Plans' planned purchases and Opportunities.

Refs LeanerCloud#727

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refused user save was reported only through a toast (UX-33): it fades,
sits away from the field, and gives screen-reader users no cue on the
control. The email, required-groups and minlength checks were native
bubbles that vanish on the next click, and they pre-empted the app's own
group checks, so the admin met problems one at a time.

New lib/field-errors.ts puts a .field-error message under the field,
sets aria-invalid and appends the message to aria-describedby (existing
help text stays announced), and clears it on the next edit of that
field. #user-form is now novalidate; saveUser collects every problem at
once (blank or malformed email, password requirements on create, no
group or a contradictory group combination), marks each field and
focuses the first. The group and password messages still toast as
before. Opening the modal clears old messages. A forms.css rule lets the
red border show on unfocused fields, since .form-group's border rule
outranks the global [aria-invalid] one.

Refs LeanerCloud#727

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e label

Jest: the field-errors helper (placement after an anchor row,
aria-describedby kept and restored, clear on edit), the user form
(all problems at once with focus on the first, blank email, contradictory
groups inline and toasted, clear then save, hidden password ignored on
edit, reset on reopen) and the history popover footer label and commit
for categorical vs numeric filters. Playwright: the Opportunities
provider filter reads "Select none", and the user form flags fields in a
real browser.

Refs LeanerCloud#727

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD IAmJSD added triaged Item has been triaged priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/eventually No deadline impact/few Limited audience effort/s Hours type/bug Defect labels Oct 24, 2026
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

IAmJSD and others added 2 commits October 24, 2026 17:07
Conflicts:
- frontend/src/plans.ts, frontend/src/riexchange.ts: took main. LeanerCloud#1315
  replaced the per-table Plans, Active RI and RI Exchange popover builders
  this branch had relabelled with the shared lib/column-filter-popover.ts,
  so those local edits have nothing left to apply to.
- frontend/src/lib/column-filter-popover.ts (merged cleanly, edited here):
  the shared popover's footer button now reads "Select none" on
  categorical filters and "Clear" on numeric ones, carrying this branch's
  LeanerCloud#727 label over to Plans and both RI Exchange tables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…red filter popovers

main's LeanerCloud#1342 made `scope` a required field of the history popover config,
so the LeanerCloud#727 footer-label test no longer typechecked; pass scope 'history'.
Also assert the "Select none" / "Clear" labels in main's shared
column-filter-popover tests, which now carry the label for Plans and
RI Exchange.

Refs LeanerCloud#727

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE 24fc242cc6a436d3e0ca2cbd4fd23c84651a6060

Blocking:

  • none
    Non-blocking:
  • frontend/src/lib/field-errors.ts:31 vs frontend/src/groups/groupFormValidation.ts:39 (main, fix(frontend): wire group form Save and explain blank permission rows #1339): main now has its own field-error mechanism for the group form. setInvalid there overwrites aria-describedby wholesale and adds a .field-error note. That gives two parallel helpers for the same job, and they treat existing aria-describedby differently: this one appends, main's replaces. Fine to land, but consolidate them in a follow-up.
  • Main has moved 7 commits past the merge base, including fix(frontend): stop Enter on a column-filter button from sorting #1388 in recommendations.ts and the column-filter code. merge-tree is clean, and full jest plus tsc on HEAD+main pass (3926/3927).
    Attacked: Merge: plans.ts and riexchange.ts took main's refactor(frontend): share one column-filter popover across Plans and RI #1315 shared popover. I checked that the label was re-applied in lib/column-filter-popover.ts (config.kind === 'numeric' ? 'Clear' : 'Select none') and that no categorical 'Clear' label is left in non-test src (I grepped textContent = 'Clear'). No main e2e or jest test selects the button by its "Clear" text. Follow-up 24fc242 only adds the now-required scope to the history config and strengthens main's column-filter-popover tests with label assertions; nothing is loosened. userModals: with novalidate on the form, I confirmed that every native constraint (email required/type, password minlength=12, groups required) has a JS equivalent in userFormProblems.

@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification at 24fc242c merged onto current main (d169b00f; merge-tree 4609853f; Linux, Node, headless Chromium):

  • This branch's own checks passed at this head: lint, typecheck, build, the full jest suite, the full Playwright e2e suite, and the git-secrets checks (see Verification in the PR body).
  • The combined tree of every UI PR in this merge wave (tree dc090cc2, main plus 17 PRs) passes npm ci, lint, typecheck, build, jest (1 skipped, 4097 passed, 4098 total), and the full Playwright suite (232 passed).
  • CI is green on 24fc242c (CI Success, Run pre-commit hooks).
  • The independent review above approves this exact head.
  • Nothing was applied to any cloud.

@IAmJSD
IAmJSD marked this pull request as ready for review October 24, 2026 19:06
@IAmJSD
IAmJSD merged commit f9c85b7 into LeanerCloud:main Oct 24, 2026
28 of 29 checks passed
@IAmJSD

IAmJSD commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at 24fc242c.

IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Conflicts:
- frontend/src/styles/forms.css: both sides inserted text before
  .form-help. Kept main's .form-group [aria-invalid="true"] border rule
  (LeanerCloud#1368) and then this branch's comment and display: block on
  .form-help; the two rules are independent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
IAmJSD added a commit to IAmJSD/cloud-commitments-platform that referenced this pull request Oct 24, 2026
Conflicts:
- frontend/src/app.ts: import block only. This branch imports
  setOpenAccountsHandler (first-run card's Admin > Accounts navigation);
  main's LeanerCloud#1336 imports initPurchasingNav. Kept both imports; both call
  sites merged cleanly and are unchanged.

Also checked: main's LeanerCloud#1397 envelope typing for getUpcomingPurchases sits
next to this branch's 4th allSettled entry (listAccountsMinimal) in
loadDashboard and merged cleanly. LeanerCloud#1368's "Select none" rename is the
per-column popover button, separate from this branch's "Clear filters"
(clearAllColumnFilters) action.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/few Limited audience priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/bug Defect urgency/eventually No deadline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant