Skip to content

Table: quick text filter across all columns (#1881) - #1894

Merged
joepio merged 2 commits into
developfrom
claude/table-quick-filter
Oct 5, 2026
Merged

joepio merged 2 commits into
developfrom
claude/table-quick-filter

Conversation

@michielbdejong

@michielbdejong michielbdejong commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1881

Adds a Find rows… field to the table view's toolbar, next to Filter and Columns. It keeps the rows where any visible column shows the text, ignoring case.

What it matches (the displayed value, per datatype)

  • text, slug, URI: the text itself
  • markdown: the source as shown, plus a plain-text version, so a b finds a **b**
  • integer / decimal: as formatted (grouping, percentage, currency, decimal places) and as typed (1234.5)
  • date: as displayed with the column's date format (calendarDateToLocalDate + formatDate, Show date-only values as dates, not UTC-midnight timestamps #1812). Timestamps are matched with their time
  • checkbox: a ticked box reads as its column label, so "paid" finds the paid rows. An unticked box matches nothing
  • reference: by title (name, shortname or filename), falling back to the subject until it loads
  • multi-value (resource array, select tags): by any item's title
  • localized text: in the content language, or the split column's language
  • JSON: its serialization

Computed columns (durations, days-since) and row-action buttons are not searched.

How it combines with filters and sorting

It narrows the view's own collection, which already has the column filters and sort applied, on the client, and keeps that order. The table, timer, board, issue list and calendar all read the same narrowed rows through one small adapter (subjectListCollection). So the field works in every view kind that shows rows. It is hidden for dashboards and app views.

While it is active:

  • New draft rows are hidden, so a row you are typing into can't vanish mid-edit. They come back when the field is cleared.
  • Shift+Enter "insert below" falls back to the trailing row, because hidden rows sit in between.
  • The footer's row count shows the matching rows, and the column totals (sum, average, min, max, count, and the per-group breakdown under the grid) follow the quick filter too, per Michiel: they describe the rows you see. The store can't be asked about the quick filter's rows, so while the field has text the totals are computed client-side from the matching rows (aggregateRows in tableAggregates.ts, mirroring the store's rules in aggregate.rs: count counts rows with a value, the other functions skip rows without a number, computed columns are evaluated with the column's own generator). With the field empty, the store's totals are used unchanged.
  • Editing a matching cell so that it no longer matches removes the row from view, the same as in most spreadsheet quick filters.

Persistence: none

The text is session state in TableResource, tagged with the view it was typed in. Switching views or leaving the table resets it. It is never written to the View resource. Clear it with the ✕ or with Escape. Escape on an empty field folds it closed on a phone.

Paging and performance

The grid fetches 30 rows per page. While the field has text, the quick filter walks every page of the view's collection (getAllMembers, as the board and calendar already do). So it also finds rows you haven't scrolled to, not just the loaded page. Nothing extra is fetched while the field is empty. Each row's display text is built once, and rebuilt only when a row, column or title changes. A keystroke is then just a substring test per row, which is fine for a few thousand rows. The first keystroke on a very large table pays for loading every row once.

Phone width

Below 40rem the field folds into a search icon. Tapping it opens the field. It stays open while it has text and folds back when you leave it empty.

Tests

  • quickFilter.test.ts (vitest, 18 tests): matching for string, markdown, integer, float/percentage, date (long and default numeric formats), timestamp, checkbox, reference (by title and by subject fallback), multi-value tags, localized text, any-column row matching, order preservation, empty query, unloaded rows, and collecting reference subjects.
  • tableAggregates.test.ts: aggregateRows follows the store's semantics (count vs. sum/avg over missing values, numeric strings, date max, a computed product column, no value vs. zero on no rows, breakdown by first tag and by month).
  • QuickFilterField.test.tsx (component, 7 tests): a real Store with local rows. Covers: typing filters (by text, by reference title, by checkbox label); clearing with ✕ restores all rows; Escape clears; ✕ only while there is text; combining with a column filter (it only narrows what the column filter lets through, and dropping the column filter keeps the text). Totals: with the quick filter active, sum and average cover only the matching rows and agree with the row count; clearing restores the full totals; with a column filter as well, the totals cover only what both let through, and no match reads "—", not 0.

Screenshots

Before (develop) and after, at desktop and at 375 px. The PNGs are handed to the developer in chat, because gh can't upload images. I'll attach them here on request.

before, desktop 1881-before-desktop.png
before, 375 px 1881-before-375.png
after, desktop, "milk" 1881-after-desktop-filtered.png
after, desktop, status tag "doing" 1881-after-desktop-status.png
after, desktop, column filter Name contains "milk" + quick filter "done" 1881-after-desktop-with-column-filter.png
after, desktop, no match 1881-after-desktop-nomatch.png
after, 375 px, collapsed icon 1881-after-375-collapsed.png
after, 375 px, "milk" 1881-after-375-filtered.png
totals, desktop, before (sum 137 over 5 rows) 1881-totals-desktop-before.png
totals, desktop, "coffee" (sum 30 over 3 rows) 1881-totals-desktop-after.png
totals, 375 px, before 1881-totals-mobile-before.png
totals, 375 px, "coffee" 1881-totals-mobile-after.png

Translations

New strings: "Find rows", "Find rows…", "Find rows containing text", "No rows show “{0}”.". "Clear" is reused. The .po files were settled with one vite run after rm -rf src/locales/.wuchale, and a second fresh run left them unchanged.

🤖 Generated with Claude Code

@joepio

joepio commented Sep 30, 2026

Copy link
Copy Markdown
Member

Trekmeester: included in batch #1915 (run 36779344325). It lands with it when green; if this PR's own tests fail there, I take it out and say why here.

@joepio

joepio commented Sep 30, 2026

Copy link
Copy Markdown
Member

Trekmeester: included in batch #1947 (run 36781515104; #1915 had been merged without it). It lands when that run is green; if this PR breaks something there, I take it out and say why here.

joepio added a commit that referenced this pull request Oct 1, 2026

joepio commented Oct 1, 2026

Copy link
Copy Markdown
Member

In the batch run on current develop (run 36828210149), the only failing test was table-refresh.spec.ts:26 "reloading a table does not add empty rows": after a reload, editable-title was not visible within 30 s, on all three attempts. The same test was flaky in the earlier batch run too. The remaining batch members barely touch the table page, and this PR changes TableResource.tsx by 170 lines, so I took it out of the batch to test that suspicion (new run on 371de8c). If that run is green, this PR needs a look at what a reload of a freshly created table does with the quick filter state; it did not fail in its own earlier run, so it may depend on develop's current boot path.


Generated by Claude Code

Michiel de Jong and others added 2 commits October 5, 2026 11:32
A "Find rows…" field next to Filter and Columns keeps the rows where any
visible column shows the text, case-insensitive: text and markdown as
plain text, numbers as formatted (and as typed), dates and timestamps as
displayed, a ticked checkbox as its column label, references and select
tags by title, multi-value cells by any item.

It narrows the view's own column-filtered, sorted collection on the
client, so it combines with both. While active it walks every page of
that collection, not just the 30 rows loaded. The same filtered rows
feed the table, timer, board, issue list and calendar views; dashboards
and app views don't show it. Session state only: it resets when you
switch views or leave the table. Clear with the x or Escape; below 40rem
it folds into a search icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With text in "Find rows…", the totals under the table (sum, average, min,
max, count, and the per-group breakdown) now cover the matching rows — the
rows on screen and the ones the footer counts — instead of every row the
column filters let through. The store can't be asked about the quick
filter's rows, so they are computed client-side by `aggregateRows`, which
mirrors the store's rules (`aggregate.rs`). With the field empty, the
store's totals pass through unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@joepio
joepio force-pushed the claude/table-quick-filter branch from 95e1c39 to 17dd0ed Compare October 5, 2026 09:33
@joepio
joepio merged commit ee37f3b into develop Oct 5, 2026
8 checks passed
@joepio
joepio deleted the claude/table-quick-filter branch October 5, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

browser enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Table view: quick text filter across all columns

2 participants