The base VIEW table and any open viewbox on a different table were sharing a single
cached filter-dialog state whenever the viewbox was opened from the VIEW page — filtering
one would silently show up in the other's filter dialog, in both directions.
Root cause: QueryComponent (the shared filter dialog used by the VIEW page, the EDITOR
page, and viewboxes alike) picks which bucket of the globals filter cache to read/write
via an inline computation duplicated four times in query.component.ts. All four checked viewboxId only when rootParam was 'home'/'editor' — never when it was 'view', so
a viewbox opened from the VIEW page fell straight through to the base table's own globals.viewer.filter bucket instead of its own globals.viewboxes[id].filter.
Fix: extracted the routing logic into one pure, unit-tested function, getFilterObjPath(rootParam, viewboxId), which checks viewboxId first and
unconditionally. All four call sites now delegate to it instead of repeating the
conditional.
Also fixed a related crash found while reproducing: an unguarded $dataformats.vars[column].type access in viewboxes.component.html that throws when column isn't a key in the map. Investigated first — traced hotColumns
(colHeadersVisible) back to the same selectedViewboxTable.hotTable source as $dataformats, so this is not the same root cause as the filter-crossing bug (both
are read synchronously from one object); it's a separate, narrower guard.
7 unit tests, including the exact regression case (('view', 42) → 'viewboxes.42', previously 'viewer').
client/src/app/query/query.component.ts
All four inline objPath computations (setToGlobals, getFromGlobals, the columnsSub/valuesSub subscriptions) replaced with calls to getFilterObjPath. Also collapses valuesSub's two now-identical branches (previously duplicated only because of the hardcoded bucket names) into one.
Added optional chaining through $dataformats?.vars?.[column]?.type to stop a TypeError when a visible column isn't present in the data-formats map.
client/cypress/e2e/viewbox.cy.ts
Replaced a long-disabled, unrelated commented-out test with an active one covering both directions of the bug: filter the base table → open a viewbox on a different table → its dialog must not show the base filter; then filter the viewbox → reopen the base table's dialog → it must still show its own filter.
# ViewBox filter incorrect when base table is filtered
## Issue
Closes #239
## Summary
- The base VIEW table and any open viewbox on a different table were sharing a single
cached filter-dialog state whenever the viewbox was opened from the VIEW page — filtering
one would silently show up in the other's filter dialog, in both directions.
- Root cause: `QueryComponent` (the shared filter dialog used by the VIEW page, the EDITOR
page, and viewboxes alike) picks which bucket of the `globals` filter cache to read/write
via an inline computation duplicated four times in `query.component.ts`. All four checked
`viewboxId` only when `rootParam` was `'home'`/`'editor'` — never when it was `'view'`, so
a viewbox opened from the VIEW page fell straight through to the base table's own
`globals.viewer.filter` bucket instead of its own `globals.viewboxes[id].filter`.
- Fix: extracted the routing logic into one pure, unit-tested function,
`getFilterObjPath(rootParam, viewboxId)`, which checks `viewboxId` first and
unconditionally. All four call sites now delegate to it instead of repeating the
conditional.
- Also fixed a related crash found while reproducing: an unguarded
`$dataformats.vars[column].type` access in `viewboxes.component.html` that throws when
`column` isn't a key in the map. Investigated first — traced `hotColumns`
(`colHeadersVisible`) back to the same `selectedViewboxTable.hotTable` source as
`$dataformats`, so this is **not** the same root cause as the filter-crossing bug (both
are read synchronously from one object); it's a separate, narrower guard.
## Changes
| File | Change |
|---|---|
| `client/src/app/query/utils/getFilterObjPath.ts` (new) | Pure function resolving the filter-cache bucket path. `viewboxId` wins, unconditionally. |
| `client/src/app/query/utils/getFilterObjPath.spec.ts` (new) | 7 unit tests, including the exact regression case (`('view', 42)` → `'viewboxes.42'`, previously `'viewer'`). |
| `client/src/app/query/query.component.ts` | All four inline `objPath` computations (`setToGlobals`, `getFromGlobals`, the `columnsSub`/`valuesSub` subscriptions) replaced with calls to `getFilterObjPath`. Also collapses `valuesSub`'s two now-identical branches (previously duplicated only because of the hardcoded bucket names) into one. |
| `client/src/app/shared/viewboxes/viewboxes.component.html` | Added optional chaining through `$dataformats?.vars?.[column]?.type` to stop a `TypeError` when a visible column isn't present in the data-formats map. |
| `client/cypress/e2e/viewbox.cy.ts` | Replaced a long-disabled, unrelated commented-out test with an active one covering both directions of the bug: filter the base table → open a viewbox on a different table → its dialog must not show the base filter; then filter the viewbox → reopen the base table's dialog → it must still show its own filter. |
Replace one-shot .then()/for-loop matching with retrying .should()/cy.contains()
lookups, drop stale cached DOM node references (viyaLib) in favor of live re-queries.
Yury
requested review from allan 2026-07-24 13:54:09 +00:00
Yury
requested review from sead 2026-07-24 13:54:10 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
ViewBox filter incorrect when base table is filtered
Issue
Closes #239
Summary
cached filter-dialog state whenever the viewbox was opened from the VIEW page — filtering
one would silently show up in the other's filter dialog, in both directions.
QueryComponent(the shared filter dialog used by the VIEW page, the EDITORpage, and viewboxes alike) picks which bucket of the
globalsfilter cache to read/writevia an inline computation duplicated four times in
query.component.ts. All four checkedviewboxIdonly whenrootParamwas'home'/'editor'— never when it was'view', soa viewbox opened from the VIEW page fell straight through to the base table's own
globals.viewer.filterbucket instead of its ownglobals.viewboxes[id].filter.getFilterObjPath(rootParam, viewboxId), which checksviewboxIdfirst andunconditionally. All four call sites now delegate to it instead of repeating the
conditional.
$dataformats.vars[column].typeaccess inviewboxes.component.htmlthat throws whencolumnisn't a key in the map. Investigated first — tracedhotColumns(
colHeadersVisible) back to the sameselectedViewboxTable.hotTablesource as$dataformats, so this is not the same root cause as the filter-crossing bug (bothare read synchronously from one object); it's a separate, narrower guard.
Changes
client/src/app/query/utils/getFilterObjPath.ts(new)viewboxIdwins, unconditionally.client/src/app/query/utils/getFilterObjPath.spec.ts(new)('view', 42)→'viewboxes.42', previously'viewer').client/src/app/query/query.component.tsobjPathcomputations (setToGlobals,getFromGlobals, thecolumnsSub/valuesSubsubscriptions) replaced with calls togetFilterObjPath. Also collapsesvaluesSub's two now-identical branches (previously duplicated only because of the hardcoded bucket names) into one.client/src/app/shared/viewboxes/viewboxes.component.html$dataformats?.vars?.[column]?.typeto stop aTypeErrorwhen a visible column isn't present in the data-formats map.client/cypress/e2e/viewbox.cy.ts