All changes are gated behind max-md:/md:hidden so the >=768px desktop layout is untouched (verified with lint + build + live checks at 375px).
- globals.css: one mobile block — 16px inputs (no iOS auto-zoom), units sidebar stacks above content, wizard item rows become 2-col (text fields full-width, delete button full-width), dd-panels / flow diagram / conv-form stack, larger touch targets
- ui/dialog.tsx: dialogs become full-width bottom sheets on mobile (all modals inherit it); desktop keeps the centered dialog
- Header: sticky on mobile only + PageLayout bottom clearance
- New MobileTabBar: role-aware bottom tabs (office admin/manager, inventory PM, inventory admin); hidden on /wizard
- Wizard step 4: TouchSensor (150ms delay) so touch drag works without stealing scroll
- JalaliDatePicker: dropdown capped to viewport; bigger day cells and time inputs on mobile
- PWA: manifest.webmanifest + 192/512 icons (scripts/gen-pwa-icons.ps1) + theme-color/manifest links in index.html
- New docs/mobile-responsive.md documenting the full change set
Domain model (v2.6): suppliers.category_id NOT NULL (1:N from the 8 fixed
categories), materials are global and link to suppliers via
supplier_materials. This fixes three regressions of the old inverted tree:
- remove the intermediate 'یک متریال را انتخاب کنید' screen: a category
card now routes straight to that category's supplier list
(/suppliers/category/:category); TopicPage is deleted and legacy
/topics/:topic[/:category] URLs redirect to the new hierarchy
- kill the accidental-category bug: api.materials.create silently POSTed
/material-categories for any unknown 'category' (a material name!), which
is how the rogue «پریز» category (+ junk material) appeared. Unknown
categories now fail loudly instead of creating catalog rows (DB cleaned:
exactly the 8 canonical categories remain)
- fix inverted wording: supplier list is «تأمینکنندههای این دسته» with
category-first breadcrumbs; the legacy material multi-select on the
add-supplier form is gone (a supplier gets the page's category)
- supplier details screen logic untouched — only its breadcrumb link
targets were updated to the new routes
- bug1: quote header fulfillment counter («رسید: X از Y قلم» amber / «تکمیل
شد» green / «در راه»); expanded line items visually split into the
received part (رسید شده ✓ + date + target warehouse from the receipt
allocations) and the in-transit remainder — quantities always sum to the
original line (invariant D holds, display-only split)
- bug2: 8-topic grids (suppliers/wizard/units) now come from
/material-categories via the store (new lib/topics.ts; fixed the mismatched
«ابزار و تجهیزات ساخت» label); suppliers topic->category resolution no
longer falls back to an arbitrary category; duplicate month/status lists
reuse the shared dictionaries; TopicGrid gained a loading EmptyState
- bug3: hub-and-spoke — the tab strip renders only on the warehouse
dashboard; sub-views hide it and navigate back via «بازگشت به داشبورد»;
«افزایش موجود» became the «تغییر موجودی» entry button
- bug4: stock count goes through a confirmation modal (current vs counted
vs variance with کسری/مازاد badges) before posting STOCK_COUNT; numbers
parsed defensively
- bug5: found qty is capped client-side (red input + inline warning, submit
disabled) and backend returns 422 when exceeding the missing balance;
fully-recovered losses drop off the pending list
- bug6+7: quantities render as absolute values with semantic color coding
(emerald inflow / rose outflow / blue neutral) across dashboard timeline,
daily control and the change-inventory form