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
AppShellso variants render with the real Sidebar + Header chrome. - Fixed Radix Select bug: the "No group" option no longer carries an empty-string value (sentinel
__noneused 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 realButtonfor "Reset all" — no more browser-default selects or ghost-text reset. item-list-bputs 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-detailpage 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-aredesigned per03-production-order-detail.html: dense 2-column grid (main 1fr + side rail 380 px),info-cardwithinfo-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_typevalues 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: trueitems 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,
FormActionsbar. - 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_typerender disabled with aLockhint. - 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
ConfirmDialogthat 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+DataTableare 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.TagInputis RHF-aware and needs to surface the BE's 20-tag cap. A small Zod helper in the planner'sschemas.tswill validate.FormActionsis a thin composition overSaveButton+CancelButton+ optional sticky positioning — small ticket, blocks every form.ImportErrorListsignature should accept the BE's row-level error envelope verbatim ({ row, column, message }).StatusBadgeknown limitation: the existing component (src/components/common/StatusBadge.tsx) references CSS variables--status-{success,info,warning,neutral,muted}-{bg,text}that are not defined insrc/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¶
- Run
pnpm dev. - Open each of the 12 URLs in the inventory table above. Compare A and B side by side per screen.
- For each screen, write your choice + a one-line reason in the Decisions section below.
- The planner reads
## Decisionsto: - Pick which variant's component set to ticket;
- Decide route shape (e.g. item-list-A means one
/catalogroute; 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
/catalogpage 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-rowseparator 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-detailpage (no inline drawer or split pane). SameStatsBar+ 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 theButtoncomponent. AppShellwraps every variant in the exploration route so the planner can size the inner content correctly.MISSING_COMPONENT_DetailDrawerremoved from the shopping list — not used by the chosen direction.MISSING_COMPONENT_DetailHeader,MISSING_COMPONENT_SectionNav,MISSING_COMPONENT_PreviewPane,MISSING_COMPONENT_VerticalStepListwere 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 aSheettrigger;<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<smwith 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, …).