Lovable dashboard — exhaustive persona × case matrix (A-Z 100%)
Mục tiêu: liệt kê TẤT CẢ các case (persona × route × state × điều kiện kích hoạt) của app Lovable
fibre-trace-studio, để hiểu hết mọi màn hình mà không cần bấm tay qua browser. Phương pháp: code-first fan-out — 7 Explore agent đọc toàn bộ source (read-only) và enumerate mọi nhánh render. Code là ground-truth: một case chỉ chạy khi seed data đúng điều kiện vẫn được liệt kê ở đây (browser thì sẽ bỏ sót). Nguồn: clonefibre-trace-studio-8b5c03d8(origin/main), read-only. Ngày dựng: 2026-07-16. Feeds task T74. Mọi dòng đều cófile:line— đây là bằng chứng, không phải diễn giải.
PART A — Access / persona model (backbone)
A1. Persona được suy ra từ dữ liệu, KHÔNG có field role cứng
Một company map sang persona 100% từ currentUser.company_id + tín hiệu dữ liệu. Hai hook song song:
usePersona (src/hooks/usePersona.ts) → "producer" | "brand" | "scanner":
| Flag | Rule | file:line |
|---|---|---|
isProducer |
company_modules(module_name="sdu").enabled OR có ≥1 programme_producers row |
usePersona.ts:34 (query :28-31) |
isBrand |
!isProducer && (có production_claims OR programme_reservations) |
usePersona.ts:38 |
isScanner |
!isProducer && !isBrand (fallback) |
usePersona.ts:39 |
primary |
thứ tự producer → brand → scanner, mặc định “scanner” | usePersona.ts:47 |
Lưu ý (usePersona.ts:35-37): chỉ tham gia programme (Tier1 được thêm vào để scan/share) không đủ thành “brand” — phải có claim/reservation thật; còn lại rơi về scanner.
usePersonaContext (src/hooks/usePersonaContext.ts) → workflow PO/linking mới:
| Flag | Rule | file:line |
|---|---|---|
isProducer |
có ≥1 programme_producers row |
usePersonaContext.ts:36 |
isRetailer |
!isProducer && company_type ∈ {brand,retailer} && có programme_participants |
usePersonaContext.ts:37-39 |
hasIncomingPoShares |
có purchase_orders với partner_company_id=companyId AND archived=false |
usePersonaContext.ts:32,40 |
isManufacturer |
= hasIncomingPoShares (cùng tín hiệu, alias) |
usePersonaContext.ts:41 |
A2. Demo customer types (nút “Switch Customer Type”)
CUSTOMER_TYPES (DemoNavBar.tsx:15-23) — 7 persona demo:
| Label | Company | userId |
|---|---|---|
| Fibre Producer GIN (Tier4) | ACME Cotton GIN | ee5a149d-…c83d2b |
| Fibre Producer rPET (Tier4) | ACME rPET Recyclers | f1e7bfde-…d6049d6 |
| Apparel Manufacturer (Tier1) | ACME Apparel Tier1 | f1f1f1f1-…111111 |
| Retail Brand (Tier0) | ACME Retail Global Inc | e5f6a7b8-…901234 (= DEFAULT_USER_ID) |
| Program Owner | ACME Program Office | 22222222-…222222 |
| Retail Brand v2 (Empty State) | Retail Brand - Empty State | a1a1a1a1-…000002 |
| Manufacturer v2 (Empty State) | Apparel Manufacturer - Empty State | b1b1b1b1-…000002 |
Cơ chế switchUser() (DemoNavBar.tsx:37-52): query company_users theo userId → setCurrentUser({...user, company_name}) → ghi localStorage["fibretrace_current_user_id"] → window.location.href="/" (full reload). Đây là swap context client-side thuần trên demo Supabase backend: KHÔNG login lại, KHÔNG đổi token — chỉ đổi currentUser/company_id rồi reload để mọi query chạy lại.
“Tier” chỉ là chữ trong label; tier thật lấy từ company_details.supply_chain_tier lúc runtime (AppSidebar.tsx:94-106).
Empty-state personas (v2): KHÔNG phải component riêng — chỉ là company seed không có facility/session/programme, nên mọi query trả []/0 và mỗi widget render nhánh empty bình thường của nó.
A3. Demo actions (panel “Demo Controls”)
| Control | Hành động | file:line |
|---|---|---|
| Website Directory | mở <DirectorySearchModal> |
DemoNavBar.tsx:127-131 |
| SDU Automation | mở <DemoSDUModal> |
DemoNavBar.tsx:132-137 |
| EWR Automation | navigate("/fibre-creation?ewr=1") (mở DemoEWRModal) |
DemoNavBar.tsx:139-143 |
| Scanner Demo | mở <DemoScanModal> |
DemoNavBar.tsx:144-149 |
| Scanner Demo v2 | mở <DemoScanModalV2> |
DemoNavBar.tsx:150-155 |
| Admin | navigate("/admin") |
DemoNavBar.tsx:156-161 |
| Developer Tool Tips | toggle DevToolTipsContext |
DemoNavBar.tsx:90-101 |
| Show as Wireframe | toggle WireframeContext (thêm class wf-on) |
DemoNavBar.tsx:108-119 |
A4. Route access tiers (App.tsx)
- Public (không layout/auth):
/login,/register-interest(/register→redirect),/directory,/v/:token,/c/:token,/verify/:guid,/verifications/preview/:sessionId(/scan/:auditId),/verifications/webhook/:sessionId,/collections/create,/facilities/add,/team/invite,*(404). (App.tsx:124-136,214) - Redirects:
/register→/register-interest;/allocations,/allocations/new,/report/:claimId→/programs. (App.tsx:134-140) <AppLayout>(App.tsx:141-186): toàn bộ app end-user. GuardAppLayout.tsx:22-24:if (loginRequired && !userAuthed) → Navigate /login.<AdminLayout>(App.tsx:187-213):/admin/*(24 route). GuardAdminLayout.tsx:59-61:if (adminLoginRequired && !adminAuthed) → /admin/login. Mặc địnhadminLoginRequired=false→ /admin mở cho bất kỳ ai (xem Part C).
A5. Nav-gating matrix (AppSidebar.tsx) — persona nào thấy mục nào
| Nav item | Điều kiện hiển thị | file:line |
|---|---|---|
| Verification History | verificationsMenuEnabled (module, default ON) |
:138-139 |
| Production Records / SDU status | fibreCreationEnabled (có producer programme) hoặc sduEnabled |
:111-113,418-481 |
| Owner nav (Program record/uptake/Documents/Certificates) | programOwnerEnabled (module, default OFF) |
:483-495 |
| Fibre Programs | !isTier4 (tier ≠ “Tier 4”) |
:106,272-274 |
| Claim Position / Evidence Packs / Request Evidence | !persona.isRetailer |
:148,275-281 |
| Evidence section wrapper | hasParticipantProgrammes || showRetailerNav |
:498 |
| Purchase Orders / Ready to Claim / Claims | showRetailerNav = persona.isRetailer |
:148,283-287,508 |
| Supply Chain (dưới PO) | participantProgrammes.length > 0 |
:514-516 |
| Data Connections | !isExclusiveProgramOwner |
:136-137,521,528 |
| Link to PO | persona.isManufacturer || persona.hasIncomingPoShares |
:149,533-542 |
| Scanners (settings) | scannersMenuEnabled (module, default ON) |
:130 |
| API keys (settings) | company_api_access.enabled |
:117-128 |
| Billing (settings) | useBillingVisible() (site-wide flag AND company module) |
:126,129 |
PART B — Full case matrix by domain
Cột chung: State/Case = tình huống render; Trigger = điều kiện code (file:line); Renders = hiển thị gì.
B0. Home (/, Index → Home.tsx) — điểm rẽ persona
| Persona | Case | Trigger | Renders |
|---|---|---|---|
role auditor |
Lockout | Home.tsx:60 currentUser.role === "auditor" |
force sign-out → /login |
| Program Owner co. | Redirect | Home.tsx:82 isProgramOwner |
→ /owner |
| Producer co. | Branch | Home.tsx:337 isProducer |
<ProducerDashboard> |
| Manufacturer-only | Branch | Home.tsx:343 isManufacturer && !isRetailer |
<ManufacturerDashboard> |
| Brand/Retailer (Tier0) | Pipeline scanner vs brand | Home.tsx:348 isScannerPersona ? 4-step : 3-step |
pipeline cards |
| Tier0 | Onboarding widget | Home.tsx:365 onboardingEnabled && tasks>0 |
checklist / null |
| Tier0 | Pipeline vẫn hiện tới khi onboarding xong | Home.tsx:534 | pipeline / ẩn |
| Tier0 | Heatmap vs fallback | Home.tsx:609 heatmapProgrammes>0 |
world heatmap / Recent Verifications |
| Tier0 | Thứ tự widget (drag) | Home.tsx:309-311 localStorage | layout 2 cột |
| Tier0 | KPI khi chưa có programme | Tier0HomeWidgets.tsx:74 !current |
mọi KPI = 0 / “Cap pending” |
| Tier0 | Per-KPI chip | Tier0HomeWidgets.tsx:97-139 ceiling>0 ? … : "Cap pending" |
tone/label chip |
| Tier0 | ActiveProgrammesGrid empty | Tier0HomeWidgets.tsx:296 !active.length |
ẩn widget |
| Tier0 | EvidenceReadiness blockers | Tier0HomeWidgets.tsx:458-463 | red tile + bullet blockers |
| Tier0 | RecentActivity empty | Tier0HomeWidgets.tsx:742 items.length===0 |
“No recent activity yet.” |
B1. Producer (Tier4 GIN + rPET)
/programs + /programs/:id (Programs → ProgrammeDetail)
| Case | Trigger | Renders |
|---|---|---|
| Loading / Empty | Programs.tsx:91 / :93 ordered.length===0 |
“Loading programs…” / “No programs available.” |
| Single vs multiple (carousel) | Programs.tsx:97 ordered.length>1 |
carousel bar prev/next / ẩn |
| Current archived | Programs.tsx:111 status==="expired" |
badge “Archived” |
| Có archived | Programs.tsx:105 archivedCount>0 |
chip “N archived” |
| Ordering | Programs.tsx:25-27 | active trước, expired sau |
| Detail loading / not-found | ProgrammeDetail.tsx:179 / :180 | “Loading…” / “Program not found” |
| status active vs other | ProgrammeDetail.tsx:243/246 | badge “Active” / raw status |
| Expiring ≤90d | ProgrammeDetail.tsx:183 | badge “Expires soon” |
| cardLayout default/bar/gauge | ProgrammeDetail.tsx:297/307/333 | stat rows / bars / radial gauge |
| canReserve gate | ProgrammeDetail.tsx:187,283 remaining_capacity_mt>0 |
“Add reservation” enabled/disabled |
| canClaim gate | ProgrammeDetail.tsx:188,286 available_to_claim_mt>0 |
“Make claim” enabled/disabled |
| Add-reservation modal + validate/success/error | ProgrammeDetail.tsx:359,192,143-145 | dialog + mailto sales; toast error/success |
| Make-claim modal: eligible sessions loading/empty/populated | ProgrammeDetail.tsx:401/403/407-429 | picker checkbox own/shared badge |
| Claim validate: no amount / no evidence | ProgrammeDetail.tsx:216/220 | toast error |
| Claim rollback / success | ProgrammeDetail.tsx:163-166 / :174 | xoá claim khi link fail / toast created |
| Back button chỉ khi standalone | ProgrammeDetail.tsx:229 !embedId |
link “Back to Fibre Programs” |
/fibre-creation (FibreCreation) — điểm áp pigment / log production của gin
| Case | Trigger | Renders |
|---|---|---|
| hasSdu banner | FibreCreation.tsx:1107 | promo QR “Start producing” |
| No programmes / no active | :1142 / :1180 | empty states |
| Active/Expired tabs (expired ẩn nếu 0) | :1149-1219 | tabs |
| Single vs multi active (carousel) | :1182 / :1158 | card / prev-next |
| cardLayout default/bar/gauge | :913/958/1002 | 3 layout |
“Log Production Manually” chỉ khi facility có manual_logging_enabled |
:931,970,1032 | nút hiện/ẩn |
| isArchived dimming | :922,961,1004 | border mờ |
| Production tab empty/populated + pager | :607 / :820 | “No production records yet.” / table |
| Verifications tab empty/populated | :834 / :838-895 | “No activation verifications yet.” / rows |
| Per-record status derivation | :649-655 | verified / more scans needed / awaiting verification |
| Record excluded | :754 r.excluded |
badge “excluded” + row mờ |
| Shortcode inline edit + errors (empty/<3/duplicate) | :663,467,468,477 | input / inline error |
| Source badge manual/sdu_demo/ewr_demo | :730-737 | “Manual”/“SDU Demo”/“EWR eCotton” |
| Expandable linked sessions | :786 | session sub-rows |
| Order Pigment dialog (submit disabled tới khi đủ field) | :1224,1310 | modal form |
| Log Production dialog (facility filtered; file upload “coming soon”) | :1320,1348,1381 | modal |
| production/pigment submit success/error | :452,458,426,431 | toast |
EWR demo auto-open (?ewr=1) |
:167 | DemoEWRModal |
| Link verification modal | :1404 | modal |
/sdu (SDU list + detail + FibreProductionTab)
| Case | Trigger | Renders |
|---|---|---|
| Detail vs list | SDU.tsx:191 detailId && detail |
detail (Device/Fibre tabs) / list |
| List loading / empty(no SDU) / empty(filtered) / populated+pager | :343/345/348/394 | 3 empty variants / table |
| Status chip (feeding/ended/idle/starting/paused/blocked/fault/inactive) | :32-52 | badge màu |
| Demo SDU modal | :323-328 | modal |
| Fibre tab: loading/empty | :970/972 | “Loading…” / “No bales match…” |
| Bale verified vs not | :1034-1039 | “Verified” / “Not scanned” |
| Weight inline edit + invalid | :1043,753 | input / toast “Enter a valid weight” |
| Bulk action bar | :937 selected.size>0 |
Show/Hide/Add-to-Verification/Clear |
| Add-to-verification modal (empty note) | :1117,1130 | picker / “No verifications found” |
| Link error: no scans | :697-700 | toast “verify them first” |
| CSV import errors (no facility / no programme / parse) | :797,801,809-820 | toast error |
| CSV import success | :839 | toast “Imported N bale…” |
| Export nothing selected | :715 | toast “Nothing to export” |
| kg/lb toggle; pageSize “All” | :889-895,640,1103 | recompute; ẩn pager |
B2. Program Owner
/owner (OwnerDashboard)
| Case | Trigger | Renders |
|---|---|---|
| Loading / no owned programmes | OwnerDashboard.tsx:82 / :84 | “Loading…” / “does not own any programs” |
| Admin banners | :131 (AdminBanner null nếu 0) | banner dismissible / nothing |
| Programme filter (all/one) | :74-77,150-158 | KPI scope |
| Cert count active+valid | :116-118 | KPI number |
| “Activated” tone: có production vs Pending | :226 | icon tone khác |
| Logo present/absent | :237 | img / Package icon |
| Producers/Participants table empty | :289 / :313 | “No producers assigned.” / “No participants yet.” |
BUG: “View details” link → /owner/programme-uptake nhưng route là /owner/program-uptake |
:254 vs App.tsx:170 | link 404 (xem Part C) |
/owner/documents — CRUD tài liệu programme: loading/empty (:195/196), Add vs Edit dialog (:136-181), status draft/published toggle (:204-207), no owned programmes note (:156), title required (:60), save success/error (:94/98), delete confirm() (:105), existing-file label (:172-174).
/owner/certificates — loading/empty (:189/190); status revoked/expired/active (:200-202); Revoke chỉ khi active (:205) + prompt() reason (:105); Add/Edit dialog (:136-175, upload đã bỏ); validate type/valid_from (:65); delete confirm() (:112).
/owner/program-record — loading / no programmes (:146/147); programme selector (:183-190); logo present/absent + upload/replace/remove (:219-238); description conditional (:253); FibreTrace items empty + chỉ toggle/reorder, không edit/delete (:270,262-292); Program items empty / row editing vs view (:307,384); move up/down disabled ở biên (:282-283,319-320); title required (:359); delete confirm() (:372); wording sửa ở /admin/programs, không ở đây (:327-328).
/owner/program-uptake — loading / no programmes (:232/233); quarterly chart empty production vs claims (:442); chart filter producer/participant (:208,215); confidence pill null vs 3-tier (>80/>50/else) (:28-32); participants/producers table empty (:331/381); producer không có facility → row dấu “—” (:158-174); ClaimedCell no-claims vs popover (:478-506).
B3. Brand / Retailer (Tier0)
/claim-position(/:id) (ClaimPosition) — KPI derivation (:101-107): activated = total_produced_mt (toàn programme), reserved=my_reserved, claimed=my_claimed, available=available_to_claim, pending=max(0,activated-claimed-available), pct=v/ceiling.
| Case | Trigger | Renders |
|---|---|---|
| Loading / no programs | ClaimPosition.tsx:79 / :82 | “Loading…” / empty |
| 7 KPI tiles | :158-199 | Reserved/Activated/Claimed/Available/Pending/Ceiling/Expiry |
| Capacity flow bar 6 stage | :169-176 | arrow chain + segmented % bar |
| Remaining ceiling segment | :360 remaining>0 |
grey bar / ẩn |
| Wording panels | :416 | Supported / Not-yet cards / ẩn |
/claim-position/list — loading/empty (:120/122), active-first ordering (:29-31).
/claims (Claims) — no-reservation gate: “Make a claim” disabled + tooltip (:311,319-326); view mode active/list (:69); deep-link ?claim= (:73-77); loading/empty (:492/497); featured card (:397); filter loại proposed/rejected (:99-100); pack preview/share nav (:449,566,582); MakeClaimModal (:65,310). MakeClaimModal: validate no-programme/qty≤0/no-sessions (:123-127), no eligible sessions (:220-222), success/error toast (:153-160).
/purchase-orders — status enum draft/shared(“Awaiting link”)/linked/ready/partially_claimed/fully_claimed (:20-36); loading (:257); empty vs no-match (:260); delete guard nếu đã claim (:139); edit lock nếu có pack (:304); archive toggle (:152-159); Create/Import-CSV modals (:341-342); coming-soon ERP banner (:326).
/ready-to-claim — eligibility filter (:42); loading/empty (:189-192); trạng thái proposed → Review button + ReviewClaimProposalModal (:223-237); confirmed packs (:239-251); rejected (:253-260); default “Awaiting partner” (:262); modal chỉ mở khi proposed (:275). ReviewClaimProposalModal: confirm/reject toast (:79-101), reject cần reason (:88-89), confirm disabled nếu 0 session (:210).
Evidence packs — Index loading/not-found (:21-26), pack status ready/pending (:29,86); Detail legacy redirect (:34), unknown pack (:44), export/share CTAs (:170-217), PDF fail (:109-112), no documents info (:210).
Evidence requests — sent/received tabs + counts (:126-133); status pending/responded/cancelled (:46-48); empty per tab (:57-67); new-request gate no-reservation (:28); New: no partners (:52) / no reservation (:71); Detail requester vs recipient (:32-33), responded banner → LogProductionModal (:101), no sessions to share (:158).
/connections — Claims tab chỉ khi isParticipant (:495); 3 tabs sessions/groups/claims (:87-93); highlight deep-link (:94-101); public-link on/off (:686-707); webhook on/off (:710-728); empty claims (:834); claim row expand → per-pack share (:863); type switches CSV/JSON/PDF/public/webhook toast (:1057-1063); coming-soon vendors (:1082-1109).
/programs/:programmeId/supply-chain (RetailerSupplyChain) — loading “Assembling the chain…” (:155); no anchors (:157); no shared verifications banner (:164); per-tier placeholders Tier1/2/3 (:183-213); identity hidden nếu không phải partner → Lock icon (:45); connector labels giữa tier (Shipment joined / No active production / Tier Activated / Awaiting scans) (:262-273).
B4. Manufacturer (Tier1/Tier2) + shared verification/collection/facility/scanner
/link-to-po (LinkToPo) — 3 nhóm chính:
- Awaiting evidence pack: loading/empty/populated (:130/132/70-75); sub-case rejected → “Rejected — revise” + reason (:218,282-289); canPublishMore vs “Fully claimed” (:217,276).
- Awaiting brand confirmation: empty/populated (:146/77-82) → “Awaiting brand” clock badge.
- Confirmed packs: empty/populated (:160/84-87) → “View pack” links.
- LinkVerificationsToPoModal: fully-claimed guard (:37,121); step1 loading/empty/no-match/populated (:132-138); step 1/2/3 (:106-108); validate toasts (:86-89); publish success (:99).
- PO status enum (usePurchaseOrders.ts:5-11); confirm→pack / reject→PO về “shared” (:453,469,563,581).
- Invite-partners callout luôn hiện (:117).
/verifications (Verifications) — session detail drill-down (:130); tabs mine/shared-with-me/shared-by-me + counts (:141-144,177-181); loading/empty (:397/399); “Needs shipping link” filter chip (:301); row badges: needs_shipping_link (:529), is_locked (:545), linked-to-production (:505), shared (:524); unshared-only filter disabled ngoài tab mine (:344); share blocked (production/claim) → toast (:227-234) vs allowed → ShareSessionModal (:236); share disabled reason tooltip (:492-494); blockchain sync mock badge (:666); column visibility localStorage (:74-80); pager >1 (:742). ShareSessionModal: no partners → CTA /settings/partners (:187); already shared card (:160); duplicate/success toast (:103-107).
/verifications/preview/:sessionId (VerificationPreview) — loading/not-found (:177/185); claim claimed vs unclaimed (:278-297); scan rows empty/list (:356); row clickable real vs synthetic (:371); pager (:400); copy public link (:44-51).
/…/scan/:auditId (ScanPreview) — loading/not-found (:62/70); status label pass→“Verified” (:111,125).
Collections — detail drill-down (:115); loading/empty (:189/191); status open/active vs other (:243); GUID vs “—” (:221). CollectionPreview: loading/not-found (:80/88); sessions empty/list (:156).
Facilities — detail vs list (:195); empty (:373); row dimmed inactive (:396); add blocked no-license → toast (:314-316); license buy dialog select/processing/done (:495,520,526); delete confirm dialog (:539); status active/inactive toast (:146-159). CreateFacility: no-license block (:50-53).
Scanners — loading/empty/no-match (:319/320/394); assigned vs “Pending assignment” (:177,401-405); Demo Scan: no facility toast (:110-113) / single auto (:114-115) / multi picker (:117-119); Buy/Modify/Support dialogs (mailto, backend pending) (:489,532,552); Modify send disabled tới khi chọn action (:582).
B5. Settings / Team / Notifications / Help
/settings/* (SettingsLayout: index → account). Sidebar: Billing ẩn nếu !billingVisible (:129); API keys ẩn nếu !apiEnabled (:128).
- account: field view/edit ternary (:141,101); copy blockchain key (:231); password mismatch (:80) / mock update (:81); update-password disabled (:262); guides toggle (:273-277); save fail toast (:64/68/72).
- company: role gate
isAdmin = role∈{admin,owner}(:21) → non-admin read-only, no Edit (:84,101); required invalid (:78,98); company_type select vs text (:131); supply_chain_tier required (:158-177); hide-from-directory toggle admin-only (:184-188); tax_id_type required (:206-217); confidence card chỉ khi companyId (:230). - notifications: empty/populated templates (:57); per-row enabled fallback default (:44-47).
- partners: connected loading/empty(no-partner)/empty(no-match)/populated (:130,133,135-163); pending empty/incoming(Accept+Decline)/outgoing(Cancel) (:169,174-205,206-229); search <2 chars ẩn (:251); search no-match/results (:253); pager >1 (:101); invite non-FT company mailto (:289-293); invite duplicate toast (usePartners.ts:107); accept adds notification (:118-127); disconnect notification chỉ nếu đã accepted (:145-151).
- api-keys: access disabled → “contact administrator” (:89); 0 keys / có keys (:123,126-154); mask/visible toggle (:132); max 5 keys disabled+throw (:106,50); create dialog (:175-191); Overview vs docs tab (:112).
- billing: role gate edit (:31,128); field editing (:119); invoice status “Pay”→Stripe link vs static (:229); status chip color (:20-24); view/download PDF missing → info toast (:142,146-154); empty (:264); pager >1 (:271); sort toggle (:103-106); Manage licenses → /facilities (:183).
- store: placeholder “Store coming soon.” (:12-14).
Settings.tsx(legacy): KHÔNG mounted trong router — dead code (save error/success/saving states only).
/team — detail vs list (:120); loading (:335); active/pending tabs empty (:339); role filter all/owner/admin/auditor + clear (:216-217,324); row badge active(role-color) vs “Pending” (:378,114-117); “You” tag (:366); pager >1 (:399); edit-role dialog (Admin/Auditor only) (:167-187); remove confirm dialog (:190-203); 3-dot menu (:141,144); column visibility (:63,226-233); CSV export (:235-250). /team/invite: role Admin/Auditor + permission preview (:101-108,24-39); validate no-email (:47-50); submit disabled (:130); insert error (:60-63); success → back (:64-65).
/notifications — search no-match/populated (:46,49-71); unread styling (:57,59,64); mark-all-read disabled (:36); click → markRead + navigate (:53-56); sources merged/deduped (Context:143-153); seeded mock rows luôn có (Context:17-24); realtime insert (Context:92-99); realtime partner-invite (Context:136-139); icon per category (Context:51-57).
/help — audience filter theo persona: producer (:23) vs participant (:25); draft ẩn trừ khi search (:34); loading (:70); no-result có/không query (:73-74); grouped sections (:77-108); draft badge (:97). /help/:slug: loading/not-found/found (:17,19,26); draft badge (:32); audience label (:31); optional summary (:35).
B6. Public / Auth / Demo
| Route | Case | Trigger | Renders |
|---|---|---|---|
| /login | default (loginRequired=false) | Login.tsx:38 DEFAULT_USER_ID |
email/blank → login demo user |
| /login | loginRequired ON: thiếu field / email not found / fail | :31-33 / :47-48 / :69-70 | toast error |
| /register | form vs submitted | Register.tsx:92 / :82 | form mailto sales / “Enquiry sent” |
| /register-interest | canSubmit false / submitted | :257,33-34 / :78 | disabled / “Thanks…” |
| /directory | empty / results | Directory.tsx:138 / :143 | “No organizations…” / cards |
| /v/:token | loading / not-found / disabled / claimed / unclaimed | PublicVerification.tsx:79/43/44/108-121/117-122 | spinner / “not found” / “not publicly visible” / green banner / neutral |
| /c/:token | not-found / disabled / pack variants | PublicClaim.tsx:44/45/8-12 | “Claim not found” / “not visible” / icon per trade/sustainability/procurement |
| /verifications/webhook/:id | loading / not-found / result derivation | WebhookPreview.tsx:77/85/35-41 | JSON page (pending/pass/fail/review) |
| Scanner Demo | multi-facility chưa chọn / gin auto-outbound / 0 pigment / no facility | DemoScanModal.tsx:446/148/585/215 | disabled + hint / skip to ftid / red error / toast |
| Scanner Demo v2 | GPS detecting / mismatch / 0 programme / 0 partner / 0 verification | DemoScanModalV2.tsx:360/380-384/1275/969/921 | spinner / amber banner / error text / empty card |
| Scanner Demo v2 | inbound no-match shipment | :1090,1130 | “No matching shipment” |
| SDU Automation | 0 active SDU | DemoSDUModal.tsx:233 | “No active SDUs found” |
| /placeholder | stub | Placeholder.tsx:9-10 | “coming soon” |
| * | 404 | NotFound.tsx:7-9 | 404 card |
B7. Admin console (/admin/*)
Auth: AdminLayout.tsx:59 gate; adminLoginRequired default false → /admin mở cho bất kỳ ai. Nếu bật: check email/password vs adminAccounts (localStorage plaintext, seed support@fibretrace.io/hZ8nD7vL6nO7, AuthSettingsContext.tsx:36-39,122-125); adminAuthed = sessionStorage (per-tab). Không có enforcement server-side/RLS.
Pages có logic riêng:
- AdminPigmentIds: TAU-range overlap guard, Save cần confirm checkbox khi overlap (:46-58,126,157-172).
- AdminFibreProduction: tabs pending/ready/verified/rejected/all (:135-141); review dialog Reject / Save&approve chỉ khi pending (:150,294-311); Reopen cho rejected (:220-224).
- AdminLicensing: rollup license_count vs used (chỉ
!is_free && active && !deleted) (:51-59); bar đỏ khi over (:144-158). - AdminSDU: status enum; facility filtered theo company + block nếu 0 facility (:122,167-169); soft-delete dim (:214,223).
- AdminOnboarding: filter all vs company + status default active_pending (:74-78); “Seed defaults” chỉ khi chọn company (:234-238); global widget toggle riêng (:145-169).
- AdminCms/Detail: CSV export (:113-128) + import chỉ update key tồn tại (:167-171); PAGE_SIZE=50; “Revert to default” (default_value).
- AdminCompanies: per-company module toggles (billing/verified/verifications/scanners default-ON khi no row :92-93/program owner); billing switch disabled nếu !billingSiteWide (:397-398); confidence manual override (:188-191,111-114).
- AdminFacilities:
effectiveStatusvirtual “expired” từ expires_at (:15-16); expired đỏ (:365); is_free Free/Paid; soft-delete; license editor embedded. - AdminVerifications: 4 tabs collection/verification/session/audit + counts; showDeleted toggle (:392); soft-delete/restore; Edit chỉ safe fields (:311-326).
- AdminProgrammes (lớn nhất): status active/hidden/expired/deleted (list loại deleted :123); 4 dialog per programme (edit / Producers manual_logging / Participants reserved+claimed / Manufacturers) + Provides editor (:1201+).
- AdminCronJobs: 2 job demo (recalc-confidence skip manual-override :48; OS-Hub stub console.log :148-149, không persist).
Standard CRUD (loading/empty/populated + add/edit/delete): AdminProgramOwners, AdminUsers (+impersonate), AdminScanners (+assign), AdminNotifications (templates + API-access switches), AdminBanners (audience all vs companies + active window), AdminHelpPages/Detail (draft/published + markdown preview), AdminBilling (invoices + site-wide switch), AdminEvidencePacks (void-with-confirm, no create), AdminOpenSupplyHub (read-only + fake Sync 900ms), AdminSettings (toggles).
PART C — Notable findings / bugs / gaps
- Persona không có role field cứng — hoàn toàn suy ra từ dữ liệu company (producer rows, claims/reservations, participant rows, company_type, incoming PO). Đây là lý do “switch customer type” hoạt động: chỉ cần trỏ sang company_id khác là toàn bộ UI đổi.
switchUser= client-side context swap thuần, không auth. Không tái hiện được trong monetapps/dashboard(userId demo là seed Supabase; monet route qua Fibretrace REST).- BUG route Owner:
OwnerDashboard.tsx:254link/owner/programme-uptakenhưng route đăng ký/owner/program-uptake(App.tsx:170) → 404. Đáng report cho Lovable/BE. - /admin mở mặc định (
adminLoginRequired=false), gate chỉ client-side, account plaintext trong localStorage — chỉ hợp lệ cho demo, KHÔNG được mang lên production. - Dead/unreachable branches:
DemoScanModalV2banner “warning”/“No scanner connection” định nghĩa nhưng không bao giờ được truyền (:701,718-721);Settings.tsxlegacy không mount trong router. - 7 error bottom-sheet của SAT KHÔNG tồn tại trong dashboard dưới các tên đó (subscription expired / wrong company / GPS mismatch / lost connection…). Grep repo không match; gần nhất chỉ là
facility_licenses.subscription_statusfilter + programme “expired” + GPS-mismatch riêng của Scanner v2. Đây là khác biệt SAT vs Dashboard cần nhớ. - Empty-state personas (v2) không phải feature riêng — chỉ là company seed rỗng data, dùng để demo mọi empty branch cùng lúc.
- Nhiều “backend pending” / mock: Scanners Buy/Modify (mailto), OS-Hub sync (fake 900ms + console.log), password update (mock toast), webhook (JSON page, không phải webhook thật). Khớp với chiến lược adapter của monet (READ unmapped →
[], WRITE unmapped → toast “backend pending”).
PART D — Cách tái hiện / test
- Trên Lovable live: bấm nút “Demo” → “Switch Customer Type” (đổi persona), + Demo Actions (SDU/EWR/Scanner/Admin), + toggle Wireframe/Dev Tips. Đây là quan sát — dùng ma trận này làm checklist để không bỏ sót branch.
- Trong monet
apps/dashboard: switcher này KHÔNG được port; persona suy ra từ tài khoản thật đăng nhập. Muốn test theo tier = login bằng account thật đúng company_type/vai của mỗi tier. - Coverage 100% đến từ tài liệu này (code enumeration), không từ browser.