Skip to content

Item-catalog · UX direction

Generated 2026-05-20, revised 2026-05-21. Two React variants per screen archetype, strictly inside the design system, mounted at /ux/item-catalog/<screen>-<a|b> in dev under the real AppShell chrome. Pick A or B per screen in the Decisions section at the bottom; frontend-module-planner reads that section to know which components to budget for.

The BE OpenAPI slice this exploration consumes lives at openapi/item-catalog.json (10 paths, 29 schemas, extracted from ../grinsystem-api/docs/api/openapi.json).

2026-05-21 revision notes

  • Wrapped the dev route in AppShell so variants render with the real Sidebar + Header chrome.
  • Fixed Radix Select bug: the "No group" option no longer carries an empty-string value (sentinel __none used until the planner ticket wires a Zod schema).
  • List pages now follow the visual language of 02-production-list.html: stats banner above the list, labelled filter bar (status / type / group as Radix dropdowns), denser table, numbered prev/next pagination.
  • Filter bar now uses the shared shadcn Select (the same look as the rest of the app) and a real Button for "Reset all" — no more browser-default selects or ghost-text reset.
  • item-list-b puts the pagination in its own card with a small gap above, so the planner can compare "footer-glued" (variant A) vs "split" (variant B).
  • New dedicated item-group-detail page replaces the previous inline drawer / split pane. Both group-list variants link to it; items in the group render as the same table style used on the item-list page (consistent navigation feel).
  • item-detail-a redesigned per 03-production-order-detail.html: dense 2-column grid (main 1fr + side rail 380 px), info-card with info-row (label/value separator lines), only two tabs on the side rail (Activity / Usage). Less scroll, no oversized empty space.

Screen inventory

# Screen Archetype Variant A URL Variant B URL UX axis
1 item-list list /ux/item-catalog/item-list-a /ux/item-catalog/item-list-b A: single /catalog; type filter chips in sticky filter bar. B: type-segmented tabs at top; one denser table per type.
2 item-detail detail /ux/item-catalog/item-detail-a /ux/item-catalog/item-detail-b A: tabbed sections (Overview · Group · Audit · Activity). B: long-scroll with sticky section nav on left rail.
3 item-create form-create /ux/item-catalog/item-create-a /ux/item-catalog/item-create-b A: single-column form, grouped fieldsets, sticky FormActions. B: two-pane — form left, live preview card right.
4 item-edit form-edit /ux/item-catalog/item-edit-a /ux/item-catalog/item-edit-b A: same single-column layout as create-a; immutable fields disabled with lock hint. B: same two-pane as create-b; preview pane shows audit metadata.
5 item-group-list list (small) /ux/item-catalog/item-group-list-a /ux/item-catalog/item-group-list-b A: flat table → row-click opens the dedicated item-group-detail page. B: card-grid of groups → click a card opens the same detail page.
5b item-group-detail detail (no A/B) /ux/item-catalog/item-group-detail Dedicated group page — header card with edit/delete, items-in-group as a full table (same look as item-list), audit sidebar.
6 item-import wizard /ux/item-catalog/item-import-a /ux/item-catalog/item-import-b A: horizontal stepper, single panel. B: vertical step list with persistent step summaries.

Total variant files: 12, plus shared _stubs.tsx. Deactivate/reactivate and group create/edit are surfaced as ConfirmDialog / FormDialog from screens 2 and 5 respectively; no dedicated screens.

Per-screen synthesis

1. item-list — 05-product-catalog.html, 05b-ingredients.html

  • Controls: search input, type filter (chips or tabs depending on variant), group filter, status filter (active / archived / all), sort dropdown, primary "New item" button, "Import" secondary button.
  • Actions: open item-detail (row click), create new (header CTA), import (header CTA), export (table-toolbar — UX axis-independent, kept in A).
  • Flow: row → item-detail; New item → item-create; Import → item-import wizard.
  • Notable patterns kept: chip-style filter bar feels right; status filter as a small segmented control next to the search bar keeps the surface area focused.
  • Pitfalls handled: the seven item_type values would clutter a single chip row. Variant B explores tabs-with-counts as a remedy.

2. item-detail — 11-product-detail.html

  • Controls: detail header (title + status badge + type badge + actions), stat chip row (shelf life, EAN, tags, updated by), section content.
  • Actions: Edit, Deactivate / Reactivate (status-dependent — only one shows at a time), Move to another group.
  • Flow: Edit → item-edit; Deactivate → ConfirmDialog (with IC-UC-03 constraint copy) → list refresh.
  • Notable patterns kept: stat chips above section content surface high-value metadata without forcing the user into a tab.
  • Important detail: is_system: true items must hide Edit + Deactivate (read-only). Not gated in the mockups — flagged for ticketing.

3. item-create — 12-product-edit.html

  • Controls: form fieldsets (Identity / Classification / Inventory / Description-tags), tag input, FormActions bar.
  • Validation: Zod-driven (downstream); examples in OpenAPI: code max 50, name max 200, description max 500, tags array max 20, shelf_life > 0.
  • Flow: Save → item-detail; Cancel → catalog.
  • Notable patterns kept: grouping identity + classification fields signals which become immutable in edit.

4. item-edit — 12-product-edit.html

  • Controls: same as create, but code, storage_unit, item_type render disabled with a Lock hint.
  • Flow: Save → item-detail (refreshed); Cancel → item-detail.
  • Notable patterns kept: showing locked fields rather than hiding them makes them debuggable in support tickets.

5. item-group-list — 13-ingredient-groups.html

  • Controls: filter bar (search + type chips), table of groups (name, type, item count, edit / delete actions), "New group" CTA, group editor FormDialog, plus selection UI (drawer in A, split-view in B).
  • Constraint surfaced: only empty groups deletable — delete button visible but the action surfaces a ConfirmDialog that explains the constraint when applied.
  • Flow: New / Edit → FormDialog; Delete → ConfirmDialog; row click → drawer (A) or pane (B) with items-in-group.

6. item-import — no prototype (IC-UC-06)

  • Controls: stepper, download-template card, file dropzone, error list, parsed-rows preview, confirm cards.
  • Steps: Download template → Upload file → Review (errors OR success preview) → Confirm.
  • Flow: all-or-nothing import; errors prevent advancing to Confirm; on success redirect to catalog.
  • Notable patterns kept: explicit "all-or-nothing" copy on the review step matches IC-UC-06's BE semantics.

MISSING_COMPONENT shopping list

Sorted by reference count desc. Lives in src/ux-exploration/item-catalog/_stubs.tsx until promoted. This is the ticketing input for the planner.

Stub Proposed home Used by Refs
MISSING_COMPONENT_FilterBar src/components/common/FilterBar (labelled-select facets + search; see prototype 02-production-list.html) item-list-a, item-list-b, item-group-list-a, item-group-list-b 4
MISSING_COMPONENT_StatsBar src/components/display/StatsBar (BE /stats consumer; horizontal stat-items + dividers; matches prototype .stats-bar) item-list-a, item-list-b, item-group-list-a, item-group-list-b 4
MISSING_COMPONENT_Pagination src/components/common/Pagination (numbered page buttons + prev/next, matches prototype .pagination) item-list-a, item-list-b, item-group-list-a, item-group-list-b 4
MISSING_COMPONENT_TagInput src/components/forms/TagInput item-create-a, item-create-b, item-edit-a, item-edit-b 4
MISSING_COMPONENT_FormActions src/components/common/FormActions item-create-a, item-create-b, item-edit-a, item-edit-b 4
MISSING_COMPONENT_FormDialog src/components/common/FormDialog item-group-list-a, item-group-list-b 2
MISSING_COMPONENT_PreviewPane src/components/display/PreviewPane item-create-b, item-edit-b 2
MISSING_COMPONENT_FileDropzone src/components/forms/FileDropzone item-import-a, item-import-b 2
MISSING_COMPONENT_ImportErrorList src/components/display/ImportErrorList item-import-a, item-import-b 2
MISSING_COMPONENT_DownloadCard src/components/display/DownloadCard item-import-a, item-import-b 2
MISSING_COMPONENT_DataTable src/components/display/DataTable (TanStack Table v8 headless wrapper — needed for the import-preview step) item-import-b 1
MISSING_COMPONENT_DetailHeader src/components/display/DetailHeader item-detail-b 1
MISSING_COMPONENT_SectionNav src/components/display/SectionNav item-detail-b 1
MISSING_COMPONENT_Stepper src/components/common/Stepper (horizontal) item-import-a 1
MISSING_COMPONENT_VerticalStepList src/components/common/VerticalStepList item-import-b 1

Note: the list-page DataTable role was inlined into the variants as native <table> markup (per visual feedback to match prototype 02-production-list.html). A real reusable DataTable ticket is still a foundation prerequisite — but for the four list screens above the relevant logic ends up split between a list page component and a small <Th> / <Td> / <ActionBtn> set, so the planner should size it based on item-import-b's needs and the cross-module reuse target rather than these four variants.

Cross-cutting notes for ticketing

  • FilterBar + DataTable are foundational — they appear in every list-style screen in the system, not just item-catalog. These should be the first two tickets in the missing-component batch.
  • TagInput is RHF-aware and needs to surface the BE's 20-tag cap. A small Zod helper in the planner's schemas.ts will validate.
  • FormActions is a thin composition over SaveButton + CancelButton + optional sticky positioning — small ticket, blocks every form.
  • ImportErrorList signature should accept the BE's row-level error envelope verbatim ({ row, column, message }).
  • StatusBadge known limitation: the existing component (src/components/common/StatusBadge.tsx) references CSS variables --status-{success,info,warning,neutral,muted}-{bg,text} that are not defined in src/styles/tokens.css (which defines --status-{draft,planned,inprogress,completed,archived}-* instead). Status pills in these variants render with transparent backgrounds. Out of scope for this exploration; flag for a foundation ticket to either define the missing variables or reconcile StatusBadge's variant names to the actual tokens.

Design-token deviations log

The prototypes are vibe-coded and use ad-hoc CSS. Where they suggested a value off the token scale, here is what we rounded to.

Where Prototype value Token used Note
Card padding padding: 18px p-5 (20 px) Closest scale step.
Border radius (cards) border-radius: 12px rounded-xl (--radius-xl 12 px) Exact match.
Accent green #10B981 bg-accent (mapped to --color-accent) Exact match.
Sidebar width --sidebar-width: 260px not reused (foundation Sidebar owns this) Detail-drawer uses w-[--sidebar-col] (380 px) instead — wider for read-pane use.
Body background #FAFAFA bg-bg (mapped to --color-bg) Exact match.
Card shadow 0 4px 20px rgba(0,0,0,0.05) shadow-card (mapped to --shadow-card) Exact match.
Status pill bg hard-coded per status --status-{draft,planned,inprogress,completed,archived}-* tokens via StatusBadge Variant name mismatch noted in shopping list.
Tiny "system" / count badge text font-size: 10px text-xs (12 px) Below-scale 10 px rounded up to the smallest Tailwind size.

No raw hex colours, arbitrary spacing in px, or off-scale shadows in any variant file.

How to pick

  1. Run pnpm dev.
  2. Open each of the 12 URLs in the inventory table above. Compare A and B side by side per screen.
  3. For each screen, write your choice + a one-line reason in the Decisions section below.
  4. The planner reads ## Decisions to:
  5. Pick which variant's component set to ticket;
  6. Decide route shape (e.g. item-list-A means one /catalog route; item-list-B means seven).

Things to weigh:

  • Density vs scannability. Variant A leans dense; Variant B leans guided. Polish ERP users (food production, 2–50 staff) typically have a tighter use loop and reward density once trained.
  • Implementation cost. Two-pane preview (create-b / edit-b) is more component work than single-column (a). Worth it only if the preview pane provides actual value.
  • URL shape. item-list-B implies type-segmented URLs — easier to bookmark, but multiplies the route table.
  • Drawer vs split-view (group list). Drawer keeps a single table on screen; split-view is faster for power users but adds responsive concerns.

Decisions

Locked 2026-05-21 by the project owner. frontend-module-planner reads this to know which variant's component set to budget for.

  • item-list: A — single /catalog page with the labelled filter bar (Type / Status / Group as Radix dropdowns), stats banner above, dense table, split pagination card below.
  • item-detail: A — dense 2-column layout (main 1fr + side rail 380 px) with info-card / info-row separator lines. Side rail shows two cards: Audit (created/updated by/at — straight from the BE schema) and a placeholder Activity timeline that bootstraps from those same fields and grows into a real event feed once foundation ships the audit log (see ../../foundation/audit-standards.md). Recipes-usage, open-orders, stock-movements counts must wait until those endpoints exist; do not fabricate them.
  • item-create: A — single-column form with grouped fieldset cards (Identity / Classification / Inventory parameters / Description & tags), sticky FormActions bar.
  • item-edit: A — same single-column layout as create-a, with immutable fields (code, storage unit, item type) rendered as disabled TextFields and a small "Locked" hint at the top of the Identity card.
  • item-group-list: A — flat table that routes each row to the dedicated item-group-detail page (no inline drawer or split pane). Same StatsBar + filter bar + split pagination pattern as the item list.
  • item-group-detail (no A/B) — dedicated page with header card (edit / delete), items-in-group rendered as the same full table style as the item list, with split pagination below the table (group size is unbounded; needs paging). Audit info on the right rail.
  • item-import: A — horizontal stepper across the top, single panel below per step (Download template → Upload file → Review → Confirm). Errors in step 3 block advancing.

Cross-cutting decisions

  • Pagination is split out as its own card across every list page. Footer-glued pagination from earlier iterations is dropped. Variant B of item-list / item-group-list is kept only for reference; it is not the chosen direction.
  • Filter bar selects use the shared shadcn Select (not native <select>). Reset uses the Button component.
  • AppShell wraps every variant in the exploration route so the planner can size the inner content correctly.
  • MISSING_COMPONENT_DetailDrawer removed from the shopping list — not used by the chosen direction.
  • MISSING_COMPONENT_DetailHeader, MISSING_COMPONENT_SectionNav, MISSING_COMPONENT_PreviewPane, MISSING_COMPONENT_VerticalStepList were only referenced by the rejected B variants — the planner can drop them from the foundation-prerequisite batch.

Responsive treatment (added 2026-05-27 by GRF-113)

Per-page responsive bucket assignment, retrofitted after the foundation responsive policy in ../../../responsive-strategy.md shipped. The rubric and tablet-parity rule live in ../../../standards.md § Responsive. Authoritative per-page table: ../pages.md § Responsive buckets.

  • item-list: Excellent on phone — Excellent on tablet. <DataTable><DataCardList> swap at <md; <FilterBar> collapses to a Sheet trigger; <StatsBar> scroll-snaps; <Pagination> compact mode.
  • item-detail: Excellent on phone — Excellent on tablet. 2-col grid collapses to single column; side rail (Audit + Activity-placeholder) stacks below the main column.
  • item-create: Workable on phone — Excellent on tablet. 9-field single-column form; <FormActions> reverse-stacks at <sm; <TagInput> wraps with 44 px touch targets; group <Combobox> falls back to a wider popover.
  • item-edit: Workable on phone — Excellent on tablet. Same shell as create. Locked Identity fieldset and "Locked" hint stay anchored to the top of the card after stacking. Composite-save partial-failure alerts render inside their owning fieldset card.
  • item-group-list: Excellent on phone — Excellent on tablet. Same swaps as item-list. <FormDialog> for create/edit goes fullscreen + sticky footer at <sm; Delete <ConfirmDialog> stacks buttons.
  • item-group-detail: Excellent on phone — Excellent on tablet. Items-in-group <DataTable> swaps to <DataCardList> at <md; side rail metadata + audit stack below.
  • item-import: Soft-gated on phone — Excellent on tablet. Renders <DesktopOnly> at <sm with continue-anyway. Reason: Excel template + multi-row validation review needs a wider canvas, and bulk import is an admin / setup task — owners do not start it from a phone. If continued: <Stepper> vertical at <md; <FileDropzone> swaps to tap-to-browse on touch; <ImportErrorList> rows stack; <DownloadCard> single-column at <sm.

The chosen-variant TSX files referenced above remain desktop-only baselines — responsive overlays are scheduled separately in GRF-114. Until those land, implementer tickets read the bucket assignment from pages.md § Responsive buckets and consume the M8 RF foundation primitives directly (useBreakpoint, <DataCardList>, <FilterBar>-as-Sheet, <Stepper> vertical, <FormActions> reverse-stack, …).