wrolpi/app/src/components/inventory
Roland afa127a992
ui: only the top modal answers Escape, and the audited widths
Two things, both reported from the running app.

Nested modals could not exist.  Mantine binds a window-level keydown listener
per open modal, gated only on whether that modal is open -- nothing checks which
one the user is looking at:

    useWindowEvent('keydown', (event) => {
      if (event.key === 'Escape' && closeOnEscape && ... && opened) onClose();
    }, {capture: true});

Two open modals meant two listeners, so one Escape closed both.  Reproduced on
the dashboard: the download modal plus the search modal over it, one press, both
gone, back to the page.  Both also sat at z-index 200, so which one drew on top
was DOM order rather than which the user opened.

Mantine's own answer is Modal.Stack with a stackId, which is the only thing that
gates closeOnEscape.  Not used, for two reasons: every call site would have to
be rewritten to hoist its modal into a stack, and it sets `__hidden` on
everything below the top -- the parent disappears while the child is open.  The
parent should be dimmed and still there, as it was before the migration.

So the wrapper keeps the register itself: a module-level list of open modals,
most recent on top, from which each modal takes closeOnEscape, trapFocus and a
z-index two above its parent.  Module-level rather than React context because
the two modals in the bug are not nested in the JSX at all -- the search modal
is rendered by KeyboardShortcutsProvider near the app root and the download
modal by the dashboard.  They are siblings that happen to be open together, and
no context relates them.  The increment is small on purpose: Mantine puts
popovers at 300, and a stack that climbed past it would paint over the select
dropdowns inside its own forms.

Widths: Roland audited the modal list and chose sizes against Mantine's scale.
Sixteen get wider -- twelve to `large` (620), four to `fullscreen` -- and eleven
keep the width they had, with the size now written down so the decision is in
the source rather than in a default.  Flasher was passing Mantine's raw `sm`,
which worked but bypassed the naming the audit used; it says `tiny` now.

The size table itself is guarded, because it is the single thing that halved
every modal during the migration and a quiet edit to it moves seventy at once.
Semantic's widths, measured on the pre-migration build still running on the QA
Pi: mini 360, tiny 540, small 720, large 1080, no-size 900.  Mantine's, read out
of its stylesheet: 320/380/440/620/780.  A `small` modal went 720 -> 440 and one
with no size went 900 -> 440.  Three cases now hold the scale, the mapping, and
the rule that no call site may name a size the table does not know -- an unknown
name is passed to Mantine, ignored, and silently rendered at the default.

Tested in a real browser: none of it is visible to jsdom -- the listener is on
window with capture, the stacking is z-index, and "still visible behind" is
geometry.  Note that `should('be.visible')` is the wrong assertion for the
parent: it is an occlusion check, so a child drawn correctly over its parent
trips it.  The test uses a wide viewport and asserts the parent is rendered and
shows to both sides of the child.  Planting the pre-fix behaviour reddens three.

1124 jest / 62 suites, 8 in modal-stack.cy.js, clean npm run build.  The 12
failures in Tags/Channels/Download/Inventory.cy.js are the known pre-existing
Semantic-era selector failures, unchanged.
2026-08-05 18:38:07 -06:00
..
CatalogEditor.js ui: Common.js and thirteen more files off Semantic 2026-08-05 18:34:50 -06:00
computeFields.js Add "Count by Weight" to estimate item counts from weighing 2026-06-19 14:36:56 -06:00
computeFields.test.js Add "Count by Weight" to estimate item counts from weighing 2026-06-19 14:36:56 -06:00
FieldCell.js ui: thirty-five files off Semantic, leaving fifteen 2026-08-05 18:34:58 -06:00
FieldSchemaEditor.js ui: thirty-five files off Semantic, leaving fifteen 2026-08-05 18:34:58 -06:00
FieldSchemaEditor.test.js ui: thirty-five files off Semantic, leaving fifteen 2026-08-05 18:34:58 -06:00
Inventory.cy.js Rebuild Inventory as a config-only system with food catalog and calculators 2026-06-14 17:14:54 -06:00
inventoryExport.js Add a recommended Food Storage example, a Ration tab, and a supply planner 2026-06-19 12:40:30 -06:00
inventoryExport.test.js Add a recommended Food Storage example, a Ration tab, and a supply planner 2026-06-19 12:40:30 -06:00
InventoryExportPanel.js ui: Common.js and thirteen more files off Semantic 2026-08-05 18:34:50 -06:00
InventoryImportModal.js ui: thirty-five files off Semantic, leaving fifteen 2026-08-05 18:34:58 -06:00
InventoryImportModal.test.js ui: thirty-five files off Semantic, leaving fifteen 2026-08-05 18:34:58 -06:00
InventoryItemsMobile.js ui: route genuine success and failure through the roles 2026-08-05 18:37:30 -06:00
InventoryItemsMobile.test.js ui: Common.js and thirteen more files off Semantic 2026-08-05 18:34:50 -06:00
InventoryPrint.js Add CSV/PDF export and a search filter to Inventory 2026-06-19 11:06:38 -06:00
InventoryPrint.test.js Add CSV/PDF export and a search filter to Inventory 2026-06-19 11:06:38 -06:00
InventoryRoute.js ui: only the top modal answers Escape, and the audited widths 2026-08-05 18:38:07 -06:00
InventorySummary.js ui: Common.js and thirteen more files off Semantic 2026-08-05 18:34:50 -06:00
InventorySummary.test.js ui: Common.js and thirteen more files off Semantic 2026-08-05 18:34:50 -06:00
InventoryTable.js ui: route genuine success and failure through the roles 2026-08-05 18:37:30 -06:00
InventoryTable.test.js ui: thirty-five files off Semantic, leaving fifteen 2026-08-05 18:34:58 -06:00
summarize.js Add a recommended Food Storage example, a Ration tab, and a supply planner 2026-06-19 12:40:30 -06:00
summarize.test.js Add a recommended Food Storage example, a Ration tab, and a supply planner 2026-06-19 12:40:30 -06:00
units.js Allow arithmetic expressions in inventory number fields 2026-06-19 15:14:10 -06:00
units.test.js Allow arithmetic expressions in inventory number fields 2026-06-19 15:14:10 -06:00