BUG: "Remaining Amount" column header disappears after enabling "Effective Amount" (Issue #1876 E2E test)
Severity: Minor
Component: Frontend UI — Invoices list DataTable column preferences (useColumnPreferences)
Found in: e2e/tests/invoices/invoices.spec.ts:841 — "Effective Amount" column (Issue #1876) › Toggling "Effective Amount" shows a deposit/refund-aware value distinct from "Remaining Amount" @responsive (desktop project)
Steps to Reproduce
- As an authenticated user, navigate to
/invoices on desktop viewport (≥1024px).
- Reset the
table.invoices.columns preference (DELETE /api/users/me/preferences/table.invoices.columns) so both "Remaining Amount" and "Effective Amount" start hidden (both are defaultVisible: false).
- Open the column settings gear and enable "Remaining Amount" (checkbox click, then close popover with Escape).
- Re-open the column settings gear and enable "Effective Amount" the same way.
- Assert both
columnheader "Effective Amount" and "Remaining Amount" are visible — this assertion passes.
- Immediately read the "Remaining Amount" column's cell value for a known invoice row.
Expected Behavior
"Remaining Amount" stays visible and its cell value is readable after having just been confirmed visible in step 5.
Actual Behavior
The "Remaining Amount" header is no longer found among visible table headers a moment later — Error: Column "Remaining Amount" not found among visible table headers (thrown from a toPass({ timeout: 3_000 }) retry loop, so this isn't a one-frame race; the header stays gone for the full 3s window). This has reproduced deterministically on every single CI run since it was introduced — 9 consecutive beta-targeted PRs (#1880, #1883, #1885, #1887, #1894, #1902, #1903, #1909, #1916), always in E2E Tests (Shard 5/16), always the same test, same error, ~4 minute runtime, both the initial attempt and the automatic retry. The two PRs immediately prior (#1855, #1858) had this shard passing, so the regression window is exactly PR #1880 (feat/1876-deposit-refunds), which is also where this test and the "Effective Amount" column were introduced.
Because E2E Gates is non-blocking on beta (only required on main-targeted PRs), this has been silently merging red for weeks without being caught.
Environment
- Browser: Chromium (desktop project, 1440px viewport per CI config)
- Viewport: desktop
- Docker: yes, CI-built image (testcontainers)
Evidence
CI log excerpt (PR #1916, run 30675997438, job 91303406982):
✘ 96 [desktop] › tests/invoices/invoices.spec.ts:841:3 › "Effective Amount" column (Issue #1876) › Toggling "Effective Amount" shows a deposit/refund-aware value distinct from "Remaining Amount" @responsive (5.9s)
✘ 101 [desktop] › ... (retry #1) (6.2s)
Error: Column "Remaining Amount" not found among visible table headers
- Timeout 3000ms exceeded while waiting on the predicate
> 398 | }).toPass({ timeout: 3_000 });
1 failed
Same shard/test/error also present in runs for PRs #1880 (90597079671), #1883 (90646967242, note: PR #1883 additionally had 3 other unrelated shard failures that day — 11/16, 15/16, 6/16 — those look like independent flakes, not this issue), #1885 (90682391864), #1887 (90773257757), #1894 (90887939821), #1902 (91115031204), #1903 (91134593031), #1909 (91175477994).
Notes
Likely root cause area: client/src/hooks/useColumnPreferences.ts. The hook's own local toggleColumn/savePreferences functional-setState logic looks race-free for two back-to-back toggles in the same component instance (each savePreferences(updated, columnOrder) call captures the full accumulated visibleColumns set, and the shared debounce ref just re-schedules to the latest call). Test failure timing (assertion at "both headers visible" passes, then the very next statement — reading "Remaining Amount"'s cell — fails to even find the header) points instead at the debounced upsert() (fires 500ms after the last toggle) racing against the "load preferences on mount" useEffect ([preferences, preferenceKey] deps) — i.e. once the debounced save lands and the preferences array from usePreferences() is refetched/updated, that load effect re-runs and may re-hydrate visibleColumns from a preferences payload that doesn't yet (or no longer) reflects both toggles, dropping "Remaining Amount" back to hidden. Recommend the frontend/backend agents trace usePreferences()'s upsert/refetch sequencing and whether the server-persisted table.invoices.columns value actually contains both keys at the moment the effect re-fires. This is unrelated to the #1895/#1896/#1918 claim/deposit-scope work in the current branch — filed here per E2E process because it was discovered during prior-failure triage and has never been fixed since introduction.
BUG: "Remaining Amount" column header disappears after enabling "Effective Amount" (Issue #1876 E2E test)
Severity: Minor
Component: Frontend UI — Invoices list DataTable column preferences (
useColumnPreferences)Found in:
e2e/tests/invoices/invoices.spec.ts:841—"Effective Amount" column (Issue #1876) › Toggling "Effective Amount" shows a deposit/refund-aware value distinct from "Remaining Amount" @responsive(desktop project)Steps to Reproduce
/invoiceson desktop viewport (≥1024px).table.invoices.columnspreference (DELETE /api/users/me/preferences/table.invoices.columns) so both "Remaining Amount" and "Effective Amount" start hidden (both aredefaultVisible: false).columnheader"Effective Amount" and "Remaining Amount" are visible — this assertion passes.Expected Behavior
"Remaining Amount" stays visible and its cell value is readable after having just been confirmed visible in step 5.
Actual Behavior
The "Remaining Amount" header is no longer found among visible table headers a moment later —
Error: Column "Remaining Amount" not found among visible table headers(thrown from atoPass({ timeout: 3_000 })retry loop, so this isn't a one-frame race; the header stays gone for the full 3s window). This has reproduced deterministically on every single CI run since it was introduced — 9 consecutivebeta-targeted PRs (#1880, #1883, #1885, #1887, #1894, #1902, #1903, #1909, #1916), always inE2E Tests (Shard 5/16), always the same test, same error, ~4 minute runtime, both the initial attempt and the automatic retry. The two PRs immediately prior (#1855, #1858) had this shard passing, so the regression window is exactly PR #1880 (feat/1876-deposit-refunds), which is also where this test and the "Effective Amount" column were introduced.Because
E2E Gatesis non-blocking onbeta(only required onmain-targeted PRs), this has been silently merging red for weeks without being caught.Environment
Evidence
CI log excerpt (PR #1916, run 30675997438, job 91303406982):
Same shard/test/error also present in runs for PRs #1880 (90597079671), #1883 (90646967242, note: PR #1883 additionally had 3 other unrelated shard failures that day — 11/16, 15/16, 6/16 — those look like independent flakes, not this issue), #1885 (90682391864), #1887 (90773257757), #1894 (90887939821), #1902 (91115031204), #1903 (91134593031), #1909 (91175477994).
Notes
Likely root cause area:
client/src/hooks/useColumnPreferences.ts. The hook's own localtoggleColumn/savePreferencesfunctional-setState logic looks race-free for two back-to-back toggles in the same component instance (eachsavePreferences(updated, columnOrder)call captures the full accumulatedvisibleColumnsset, and the shared debounce ref just re-schedules to the latest call). Test failure timing (assertion at "both headers visible" passes, then the very next statement — reading "Remaining Amount"'s cell — fails to even find the header) points instead at the debouncedupsert()(fires 500ms after the last toggle) racing against the "load preferences on mount"useEffect([preferences, preferenceKey]deps) — i.e. once the debounced save lands and thepreferencesarray fromusePreferences()is refetched/updated, that load effect re-runs and may re-hydratevisibleColumnsfrom a preferences payload that doesn't yet (or no longer) reflects both toggles, dropping "Remaining Amount" back to hidden. Recommend the frontend/backend agents traceusePreferences()'supsert/refetch sequencing and whether the server-persistedtable.invoices.columnsvalue actually contains both keys at the moment the effect re-fires. This is unrelated to the#1895/#1896/#1918claim/deposit-scope work in the current branch — filed here per E2E process because it was discovered during prior-failure triage and has never been fixed since introduction.