#474 fixed one screen. The state shape that caused it — one error slot shared by a screen's dialogs, its background reads and its page-level writes — is still the shape on every other dialog screen, so the same two failures are reachable there.
Found by codex reviewing #478, which narrowed the new Help text precisely because the app cannot honour the wider claim yet.
The two failures
A background read's failure is presented as the open form's own. web/src/routes/CustomersPage.tsx:41-46 writes a listCustomerBalances rejection into the same error the New customer dialog renders at :104. That read does not set busy and the New customer trigger is not gated on it, so an admin who opens the dialog before the balance request rejects sees "could not load balances" sitting under the name and phone fields, as though the form had refused them.
A dismissed dialog's message reappears at page level. :113 renders {error && !creating && …}, so cancelling a failed create moves its message onto the page, where it reads as a screen-level error about nothing the user is looking at any more — the original #474 complaint, one screen over.
Where else
Same shape, one shared error plus in-dialog copies: FlocksPage (3 dialogs), InventoryPage (4), ProductsPage (3), UsersPage (4), GradesPage (2), ExpensesPage (2), HistoryPage, DailyEntryPage. Each needs checking for a background read or a non-dialog write that can land while a dialog is open — the screens with an admin-only or supplementary read (balances, conversions, payments) are the ones where it is reachable today; a screen whose only writes are its dialogs' has the shape but perhaps no path.
The fix Sales uses (#477 / #478)
Two slots. error is the page's, untagged; dialogError is whichever dialog is open (they are modal, so at most one ever is, and each clears it on open). The failure handler routes by a DIALOG_SCOPES list. Neither slot can overwrite the other; each attempt clears only its own; dismissal drops nothing the page still owns. It is a net removal of code on Sales — the page copy's guard and the in-dialog suppression both disappear.
StockPage already keeps a separate dialogError and is not affected.
Done when
#474 fixed one screen. The state shape that caused it — one
errorslot shared by a screen's dialogs, its background reads and its page-level writes — is still the shape on every other dialog screen, so the same two failures are reachable there.Found by codex reviewing #478, which narrowed the new Help text precisely because the app cannot honour the wider claim yet.
The two failures
A background read's failure is presented as the open form's own.
web/src/routes/CustomersPage.tsx:41-46writes alistCustomerBalancesrejection into the sameerrorthe New customer dialog renders at:104. That read does not setbusyand the New customer trigger is not gated on it, so an admin who opens the dialog before the balance request rejects sees "could not load balances" sitting under the name and phone fields, as though the form had refused them.A dismissed dialog's message reappears at page level.
:113renders{error && !creating && …}, so cancelling a failed create moves its message onto the page, where it reads as a screen-level error about nothing the user is looking at any more — the original #474 complaint, one screen over.Where else
Same shape, one shared
errorplus in-dialog copies:FlocksPage(3 dialogs),InventoryPage(4),ProductsPage(3),UsersPage(4),GradesPage(2),ExpensesPage(2),HistoryPage,DailyEntryPage. Each needs checking for a background read or a non-dialog write that can land while a dialog is open — the screens with an admin-only or supplementary read (balances, conversions, payments) are the ones where it is reachable today; a screen whose only writes are its dialogs' has the shape but perhaps no path.The fix Sales uses (#477 / #478)
Two slots.
erroris the page's, untagged;dialogErroris whichever dialog is open (they are modal, so at most one ever is, and each clears it on open). The failure handler routes by aDIALOG_SCOPESlist. Neither slot can overwrite the other; each attempt clears only its own; dismissal drops nothing the page still owns. It is a net removal of code on Sales — the page copy's guard and the in-dialog suppression both disappear.StockPagealready keeps a separatedialogErrorand is not affected.Done when