fix(list,data-objectstack,types): exporting a searched list no longer downloads the unsearched superset - #3078
Merged
Conversation
… downloads the unsearched superset
The server-streamed export mirrored the view's `filter` and `sort`, and the
code comment claimed that made the file match the screen:
Mirrors the active view's filter + sort so the exported file matches
what the user sees.
It mirrored one half. There was no way to carry the term a user had typed into
the search box — `ExportDownloadRequest` had no field for one — so exporting
during a search produced MORE ROWS than the list showed, in a file that looks
authoritative, with nothing indicating the difference. The client-side fallback
was always correct (it serializes the already-searched `data`); only the server
path was wrong, and it is the one that handles xlsx.
Same family as a dropped filter (objectstack#3948, objectstack#4181): a
plausible answer that is quietly broader than the one asked for.
- `ExportDownloadRequest` gains `search` / `searchFields`.
- `ObjectStackAdapter.exportDownload` sends them as `search=` / `searchFields=`,
trimming the term and omitting both when it is blank (`searchFields` alone
means nothing).
- `ListView` passes the active `searchTerm` and the view's `searchableFields`,
and both are now in the export callback's dependency array — a stale closure
would export the wrong row set.
Requires a server with objectstack#4230. Older servers ignore unknown query
params on this route, so they keep today's behaviour rather than erroring.
Also: the filter merge is no longer written twice. The three filter sources
(view filter, filter-panel group, per-field user filters) were merged by
verbatim copies in the data fetch and in the export — two copies that must
agree, deciding respectively what the user SEES and what they DOWNLOAD. Both
now call `buildEffectiveFilter`.
That half is a PURE EXTRACTION, and the tests say so: the four parity tests
added for it pass against the old duplicated code too. They exist to keep it
that way — the adapter's duplicated filter-shape check had already drifted
apart unnoticed (#3072). The parity tests compare the arguments of the two real
calls rather than asserting an expected AST twice, which would go on passing if
both copies drifted the same wrong way.
Verification: 9 new tests (4 ListView export/search, 4 fetch-vs-export parity,
5 adapter query params). Reverting ListView.tsx fails the 2 search tests and
passes the 4 parity ones; reverting the adapter fails 4. Full suite 758 files /
8819 tests green; tsc clean across types, data-objectstack, plugin-list;
eslint 0 errors.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
… as a predicate on columns that don't exist (#3081) Sweeping the other `$filter` producers after #3078 turned up two live defects in `ObjectView`, which fetches its own data for calendar / kanban / gallery / timeline (grid delegates to `ObjectGrid`). 1. AN OBJECT FILTER WAS DROPPED, AND ONLY FOR NON-GRID VIEWS. `table.defaultFilters` is declared `Record<string, any>`, and the merge tested `baseFilter.length > 0` — `undefined > 0` for an object. The filter vanished and the view returned EVERY RECORD. `ObjectGrid` assigns the same value straight to `params.$filter`, so one view definition filtered correctly as a grid and returned everything as a calendar. 2. RULE OBJECTS WERE SPREAD INTO THE `and`, NOT WRAPPED. `['and', ...baseFilter, ...userFilter]` is only correct when the source is an array of AST nodes. `activeView.filter` is a spec `ViewFilterRule[]`, so spreading put bare rule objects where the AST expects nodes: isFilterAST(['and', {field:'stage',operator:'eq',value:'won'}, ['owner','=','me']]) // false → 400 since objectstack#4121 parseFilterAST(same) // {$and:[{field:'stage',operator:'eq',value:'won'}, {owner:'me'}]} The second line is a predicate over three columns named `field`, `operator` and `value` — which do not exist. Reachable whenever a view with a filter meets a user filter value. New in core: `toFilterNode` normalizes one source (rule array / AST / MongoDB object) and `mergeFilterNodes` combines sources as siblings under one `and`. `ObjectView` and `ListView.buildEffectiveFilter` both use them, so the three filter shapes are reconciled in one place instead of by hand at each renderer. The adapter also now translates a bare rule object sitting directly under a logical node — the chokepoint defence for any producer still emitting the spread shape. Only rule-SHAPED objects are touched; a child with no `field` is a genuine MongoDB condition and passes through untouched. CORRECTING A COMMENT SHIPPED IN #3078. `buildEffectiveFilter` documented the dropped-object case as unreachable, "nothing in this repo produces one for a list view". That was wrong: `ObjectView` passes `mergedFilters` straight into that schema's `filter`, and its last fallback is `table.defaultFilters`. The case is now handled rather than explained away. Also checked and clean: the dashboard widgets (metric/chart/pivot/data-table) pass a single resolved filter with no cross-vocabulary merge; gallery, gantt, calendar and map forward `schema.filter` unchanged; `filter-converter`'s own `['and', ...conditions]` spreads tuples it built itself. `ObjectView` was the only other producer merging two vocabularies. Verification: 19 tests across the four packages; reverting each source file fails the ones covering it. Emitted filters are asserted against the spec's own `isFilterAST` / `parseFilterAST`, including an executable pin on what the old spread shape produced. Full suite 761 files / 8856 tests green; tsc clean; eslint 0 errors. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The objectui half of objectstack#4230. Both items came out of auditing
ListView's filter construction after #3072.The bug
The server-streamed export mirrored the view's
filterandsort, and the code comment claimed that made the file match the screen:It mirrored one half. There was no way to carry the term a user had typed into the search box —
ExportDownloadRequesthad no field for one — so exporting during a search produced more rows than the list showed, in a file that looks authoritative, with nothing indicating the difference.The client-side fallback was always correct (it serializes the already-searched
data). Only the server path was wrong — and it is the one that handles xlsx.Same family as a dropped filter (objectstack#3948, objectstack#4181): a plausible answer that is quietly broader than the one asked for.
@object-ui/typesExportDownloadRequestgainssearch/searchFields@object-ui/data-objectstackexportDownloadsendssearch=/searchFields=, trims the term, omits both when blank (searchFieldsalone means nothing)@object-ui/plugin-listListViewpasses the activesearchTerm+ the view'ssearchableFields, both now in the export callback's dep array — a stale closure would export the wrong row setRequires a server with objectstack#4230. Older servers ignore unknown query params on that route, so they keep today's behaviour rather than erroring — which is why this can land independently of the server PR.
Also: the filter merge is no longer written twice
The three filter sources (view filter, filter-panel group, per-field user filters) were merged by verbatim copies in the data fetch and in the export — two copies that must agree, deciding respectively what the user sees and what they download. Both now call
buildEffectiveFilter.This half is a pure extraction, and the tests say so: the four parity tests added for it pass against the old duplicated code too. They are not fixing a divergence; they exist to keep one from appearing — the adapter's duplicated filter-shape check had already drifted apart unnoticed (#3072).
The parity tests compare the arguments of the two real calls (
find's$filtervsexportDownload'sfilter) rather than asserting an expected AST twice — an assertion written out twice would go on passing if both copies drifted the same wrong way.One gap preserved deliberately and documented in the helper: a MongoDB-style object
schema.filterhas no.lengthand is dropped, as it always has been. Nothing in this repo produces one for a list view (those come from dashboard widgets, which query directly), so changing it would be a behaviour change on speculation.Verification
ListView.tsxfails the 2 search tests and passes the 4 parity ones — exactly the split that tells you which half was a bug and which was a refactor. Reverting the adapter fails 4.tscclean across all three packages; eslint 0 errors (296 pre-existingno-explicit-anywarnings, untouched).Note:
data-objectstacktypechecks against the built@object-ui/typesdist, sopnpm --filter @object-ui/types buildis needed before itstscsees the new fields — 7 errors there were all dist staleness, including one unrelated to this change.Refs objectstack#4230, #3072, objectstack#3948
🤖 Generated with Claude Code