fix(admin): refetch Inventory when its tab becomes visible (#327) #328

Merged
bermudalamb merged 2 commits from bugfix/327-inventory-stale-after-publish into main 2026-09-09 13:32:36 -05:00
Owner

Two items published from the Review queue did not appear in Inventory.

Publishing was never at fault. The items were published, the rows were right, and the API returned them. The Inventory tab was showing a list it had fetched earlier and never refreshed.

Mechanism

antd keeps a tab pane mounted once rendered, and Inventory is the default tab — so its pane mounts at page load whether or not anyone looks at it. Its only fetch runs from an effect depending on the filters:

useEffect(() => { void load(filters); }, [load, filters]);

That fires on mount, and when a filter changes. Nothing else.

Open the admin → switch to Review queue → publish → switch back. Nothing re-ran. The table still holds the list built before those submissions existed. A browser reload shows them, which is what makes this read as publishing is broken rather than the table is stale.

The fix

The Tabs are controlled now and tell Inventory whether it is the pane on screen; Inventory refetches when that becomes true. Guarded on visibility rather than fetching unconditionally, because the pane lives for the life of the page and would otherwise keep refetching while hidden.

Rejected: destroyInactiveTabPane. It would fix this by remounting, but discards every tab's state on every switch — filters, scroll position, a half-filled form — and refetches all of them repeatedly. Much larger than this bug warrants.

Why the existing test didn't catch it

publishes a submitted item at an edited price passes against the broken behaviour, and that is the whole reason this shipped. It asserts against GET /api/admin/items, and the API was always correct.

The new test asserts through the UI and never reloads the page. Inventory is opened before publishing, so its list is fetched while the item still carries its submission-timestamp name; the assertion afterwards looks for the name given at publish, which a stale list cannot contain. A page.reload() anywhere in it would make it pass against the bug it exists to catch.

A comment that was actively misleading

AdminPage said "Only the active tab's panel is mounted." Only the active panel is visible — the rest stay in the DOM, which is why scoping to .ant-tabs-tabpane-active is needed at all.

That mistaken belief is the exact shape of this bug: a tab that never unmounts also never re-runs its effects. The comment now says which it is and why it matters.

Not fixed here

Categories, Tags, Upload links and Customers are mounted-once children of the same Tabs and are all changeable from elsewhere, so they very likely share this. Recorded on the issue rather than assumed to be fine — worth a look, but each needs its own thought about what "stale" costs there.

Verification

tsc clean for src and tests · lint 0 errors · production build green.

The new test needs CI to run — the e2e suite wants a browser and a database this machine cannot provide. Worth watching that it passes, since a test that cannot fail against the old code would be worthless.

Note main is currently red from #154's schema-loss recurrence, which is unrelated to this and tracked separately.

Closes #327

🤖 Generated with Claude Code

Two items published from the Review queue did not appear in Inventory. **Publishing was never at fault.** The items were published, the rows were right, and the API returned them. The Inventory tab was showing a list it had fetched earlier and never refreshed. ## Mechanism antd keeps a tab pane **mounted** once rendered, and Inventory is the default tab — so its pane mounts at page load whether or not anyone looks at it. Its only fetch runs from an effect depending on the filters: ```js useEffect(() => { void load(filters); }, [load, filters]); ``` That fires on mount, and when a filter changes. Nothing else. Open the admin → switch to Review queue → publish → switch back. Nothing re-ran. The table still holds the list built before those submissions existed. A browser reload shows them, which is what makes this read as *publishing is broken* rather than *the table is stale*. ## The fix The Tabs are controlled now and tell Inventory whether it is the pane on screen; Inventory refetches when that becomes true. Guarded on visibility rather than fetching unconditionally, because the pane lives for the life of the page and would otherwise keep refetching while hidden. **Rejected:** `destroyInactiveTabPane`. It would fix this by remounting, but discards every tab's state on every switch — filters, scroll position, a half-filled form — and refetches all of them repeatedly. Much larger than this bug warrants. ## Why the existing test didn't catch it `publishes a submitted item at an edited price` **passes against the broken behaviour**, and that is the whole reason this shipped. It asserts against `GET /api/admin/items`, and the API was always correct. The new test asserts through the UI and **never reloads the page**. Inventory is opened *before* publishing, so its list is fetched while the item still carries its submission-timestamp name; the assertion afterwards looks for the name given at publish, which a stale list cannot contain. A `page.reload()` anywhere in it would make it pass against the bug it exists to catch. ## A comment that was actively misleading `AdminPage` said *"Only the active tab's panel is mounted."* Only the active panel is **visible** — the rest stay in the DOM, which is why scoping to `.ant-tabs-tabpane-active` is needed at all. That mistaken belief is the exact shape of this bug: a tab that never unmounts also never re-runs its effects. The comment now says which it is and why it matters. ## Not fixed here **Categories, Tags, Upload links and Customers are mounted-once children of the same Tabs and are all changeable from elsewhere**, so they very likely share this. Recorded on the issue rather than assumed to be fine — worth a look, but each needs its own thought about what "stale" costs there. ## Verification `tsc` clean for src and tests · lint 0 errors · production build green. **The new test needs CI to run** — the e2e suite wants a browser and a database this machine cannot provide. Worth watching that it passes, since a test that cannot fail against the old code would be worthless. Note `main` is currently red from #154's schema-loss recurrence, which is unrelated to this and tracked separately. Closes #327 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bermudalamb added 1 commit 2026-09-09 12:05:36 -05:00
fix(admin): refetch Inventory when its tab becomes visible (#327)
SonarQube Analysis / sonarqube (pull_request) Failing after 32m4s
Linting / lint (pull_request) Successful in 2m54s
07891fe5aa
Two items published from the Review queue did not appear in Inventory. Publishing was never at fault: the items were published, the rows were right, and the API returned them. The Inventory tab was showing a list it had fetched earlier and never refreshed.

antd keeps a tab pane mounted once it has been rendered, and Inventory is the default tab, so its pane mounts at page load whether or not anyone looks at it. Its only fetch runs from an effect depending on the filters, so it fires on mount and when a filter changes and at no other time. Open the admin, switch to the Review queue, publish, switch back, and nothing has re-run — the table still holds the list built before the submissions existed. A browser reload shows them, which is what makes this read as publishing being broken rather than as a stale table.

The tab is controlled now and tells Inventory whether it is the one on screen, and Inventory refetches when that becomes true. Guarded on visibility rather than fetching unconditionally, because the pane lives for the life of the page and would otherwise keep refetching while hidden.

destroyInactiveTabPane on the Tabs would also have fixed it, by remounting, and was rejected: it discards every tab's state on every switch — filters, scroll position, a half-filled form — and refetches all of them repeatedly, which is a much larger behavioural change than this bug is worth.

The existing publish test passes against the broken behaviour, and that is the reason this went unnoticed. It asserts against GET /api/admin/items, and the API was always correct. The new test asserts through the UI and never reloads the page: Inventory is opened before publishing, so its list is fetched while the item still carries its submission-timestamp name, and the assertion afterwards looks for the name given at publish — which a stale list cannot contain. A reload anywhere in it would make it pass against the bug it exists to catch.

AdminPage's comment claimed only the active panel is mounted. It is only the active panel that is visible; the rest stay in the DOM. That mistaken belief is the shape of this bug, so the comment now says which it is and why it matters.

Not fixed here: Categories, Tags, Upload links and Customers are mounted-once children of the same Tabs and are all changeable from elsewhere, so they very likely share this. Recorded on the issue rather than assumed to be fine.

Verified: tsc clean for src and tests, lint 0 errors, production build green. The new test needs CI to run — the e2e suite wants a browser and a database this machine cannot provide.

Closes #327

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bermudalamb added 1 commit 2026-09-09 13:32:07 -05:00
Merge branch 'main' into bugfix/327-inventory-stale-after-publish
SonarQube Analysis / sonarqube (pull_request) Failing after 40m27s
Linting / lint (pull_request) Successful in 2m52s
311345be1f
bermudalamb merged commit f1600774db into main 2026-09-09 13:32:36 -05:00
bermudalamb deleted branch bugfix/327-inventory-stale-after-publish 2026-09-09 13:32:42 -05:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bermudalamb/redefined-designs#328