0ac4beaaa8
- Add requireAuth() to admin-permissions.ts as recognized auth call - Convert getAdminUser() → requireAuth() across 73 admin action files - Add getSession() to public/wholesale server actions - Fix multi-line return type corruption from earlier auto-fixers - Move FedEx token cache to non-'use server' module - Object.freeze module-level constants: PRICE_KEYS, EMPTY_MOBILE_DASHBOARD, EMPTY_PAY_PERIOD, LOCALE_CART_SUBJECT, WELCOME_EMAILS - Update Stripe API version 2026-05-27 → 2026-06-24 - Fix wholesale employee portal: getEmployeeSessionAction + EmployeePortalClient - Fix 51 TypeScript errors (return type corruption, missing imports)
22 KiB
22 KiB
Acceptance Criteria — 2026-06-25
Format per item: AC-NN.SS (module.section) — Given/When/Then or assertion. Edge cases follow each AC block prefixed
EC-NN.SS.x.
Risk tier definitions
- C — Critical: any data loss, RBAC bypass, payment error, auth failure, or cross-tenant data leak. Must pass before sign-off.
- H — High: core CRUD flows used daily by admin/brand_admin. Pass-blocking.
- M — Medium: secondary features (filters, exports, analytics). Pass-blocking for release quality, acceptable for hot-fix.
- L — Low: cosmetics, marketing pages, static content. Best-effort.
ROLES & IDENTITY (C)
AC-R1 — dev_session cookie impersonates roles in dev mode
- Given NODE_ENV !== "production"
- When request carries
Cookie: dev_session=platform_admin - Then
getAdminUser()returns{ role: 'platform_admin', brand_id: null, brandIds: [...], email: 'qa-platform@...' } - And every
/admin/*route renders without<AdminAccessDenied />
EC-R1:
- EC-R1.1: invalid value (
dev_session=hacker) → returns null (no impersonation) - EC-R1.2: missing cookie → null →
/admin/*shows Access Denied - EC-R1.3: NODE_ENV=production → dev_session is ignored, real Neon Auth required
AC-R2 — RBAC enforcement
- Given admin user with
can_manage_orders=false - When visiting
/admin/orders/[id] - Then redirect to
/admin/pickup - And
/admin/ordersshows Access Denied or redirects similarly
EC-R2:
- EC-R2.1: any permission flag
can_manage_X=falseenforces redirect on/admin/X - EC-R2.2: all flags
true→ no redirects - EC-R2.3: brand_admin without brand link → null user → Access Denied
AC-R3 — Tenant scoping
- Given brand_admin of brand A
- When calling RPCs with
p_brand_id=brand_A_id - Then only brand A rows are returned
- When calling with
p_brand_id=brand_B_id - Then zero rows or 403/empty depending on the action
EC-R3:
- EC-R3.1: platform_admin with
p_brand_id=nullreturns all brands - EC-R3.2: brand_admin calling with
p_brand_id=nullfalls back to theirbrand_id - EC-R3.3: brand_admin calling with
p_brand_id=other_brand_id→ zero/empty
ADMIN SHELL (C)
AC-A1 — /admin/page.tsx redirects to /admin/v2
- Then 307 →
/admin/v2
AC-A2 — /admin/v2 dashboard renders for platform_admin
- Then shows "Today" heading + stat cards + stops + orders
- And no Access Denied
EC-A2:
- EC-A2.1: 0 stops today → "No stops scheduled today" empty state
- EC-A2.2: 0 pending orders → empty state
- EC-A2.3: activeBrandId null → "No brand selected" empty state
AC-A3 — AdminSidebar nav links navigate correctly
- Then every Workspace/Operations/Communications/Growth/Tracking/Insights/Settings link navigates to its target route without error
EC-A3:
- EC-A3.1: Tracking → Water Log hidden when
enabledAddons["water_log"]=false - EC-A3.2: Tracking → Route Trace hidden when
enabledAddons["route_trace"]=false - EC-A3.3:
requiresPlatformAdminitems hidden for non-platform users - EC-A3.4: mobile menu closes on link click
- EC-A3.5: ESC closes mobile menu
- EC-A3.6: focus moves to close button when mobile menu opens
- EC-A3.7: arrow-key navigation loops top-to-bottom-to-top
AC-A4 — MobileTabBar navigates and shows active state
- Then clicking each tab navigates + highlights active tab matching
pathname
EC-A4:
- EC-A4.1: clicking More opens MoreSheet
- EC-A4.2: clicking a MoreSheet link closes sheet and navigates
AC-A5 — CommandPalette opens on Cmd/Ctrl+K
- Then modal appears, search input focused
EC-A5:
- EC-A5.1: ↑↓ navigation, Enter selects, ESC closes
- EC-A5.2: body scroll locked while open
- EC-A5.3: backdrop click closes
- EC-A5.4: empty results → "No results for 'query'"
- EC-A5.5: navigate via result updates URL and closes palette
AC-A6 — BrandSelector switches active brand
- Then selecting a brand sets cookie + refreshes page;
BrandSelectorreflects new active
EC-A6:
- EC-A6.1: "All brands" option visible only to platform_admin
- EC-A6.2: multi-brand admin → "multi" badge
- EC-A6.3: outside click closes
- EC-A6.4: ESC closes
AC-A7 — OfflineBanner shows when offline or pending
- Then offline → danger-soft banner; pending sync → warning-soft banner
- When online + 0 pending → banner hidden
EC-A7:
- EC-A7.1: pre-mount → null (no hydration mismatch)
ORDERS (C)
AC-M1.1 — /admin/orders legacy list loads
- Then status tabs (all/pending/picked_up) render with counts
- And table shows orders sorted by placed_at desc
EC-M1.1:
- EC-M1.1.1: empty state when 0 orders → "No orders yet" + Create CTA
- EC-M1.1.2: filter by status → list filters correctly
- EC-M1.1.3: search by customer name → matches substring
- EC-M1.1.4: search by phone → matches digits
- EC-M1.1.5: search by order id prefix → matches
- EC-M1.1.6: pagination next/prev changes page
- EC-M1.1.7: stop filter multi-select → list filtered by selected stops
- EC-M1.1.8: clearing filters shows all orders
- EC-M1.1.9: bulk select + Mark All Picked Up → all selected marked, toast shows counts
- EC-M1.1.10: partial bulk failure → some succeed, others revert, error toast
- EC-M1.1.11:
?new=trueopens modal on mount - EC-M1.1.12: row click navigates to
/admin/orders/[id]
AC-M1.2 — /admin/orders/new modal
- Then modal opens with required: customer name; optional: email, phone, stop
- And items table with at least 1 row
EC-M1.2:
- EC-M1.2.1: empty customer name → submission blocked
- EC-M1.2.2: 0 items → Create Order disabled
- EC-M1.2.3: invalid price (< 0 or non-numeric) → submission blocked
- EC-M1.2.4: qty < 1 → blocked
- EC-M1.2.5: success → full-page reload to
/admin/orders, modal closes - EC-M1.2.6: server error → red error banner inside modal
- EC-M1.2.7: ESC closes modal
- EC-M1.2.8: backdrop click closes modal
AC-M1.3 — /admin/orders/[id] detail + edit
- Then shows customer, stop, items, totals, payment section, edit form
EC-M1.3:
- EC-M1.3.1: invalid id → "Order not found"
- EC-M1.3.2: empty customer name on save → "Customer name is required"
- EC-M1.3.3: negative discount → "Discount cannot be negative"
- EC-M1.3.4: remove item → marked removed, deleted on save
- EC-M1.3.5: save success → toast + page refreshes
- EC-M1.3.6: status set to fulfilled (not exposed in UI) → should not be reachable
- EC-M1.3.7: pickup toggle: not picked up → "Mark Picked Up"; picked up → button hidden
- EC-M1.3.8: refund amount = 0 → button disabled
- EC-M1.3.9: refund amount > remaining balance → validation error
- EC-M1.3.10: refund amount = remaining balance → success
AC-M1.4 — /admin/v2/orders mobile list
- Then cards sorted; status filter via
?status=
EC-M1.4:
- EC-M1.4.1: 0 orders → "No orders yet"
- EC-M1.4.2: status=placed → only placed orders shown
- EC-M1.4.3: card click → /admin/v2/orders/[id]
AC-M1.5 — /admin/v2/orders/[id] mobile detail
- Then shows timeline + sticky action bar with status buttons
EC-M1.5:
- EC-M1.5.1: status transitions: placed → ready → picked-up; cancel from any state
- EC-M1.5.2: invalid id → notFound()
STOPS (H)
AC-M2.1 — /admin/stops list
- Then table sorted by date asc; tabs filter by status
EC-M2.1:
- EC-M2.1.1: search matches city/location substring
- EC-M2.1.2: Add Stop modal opens
- EC-M2.1.3: Upload Schedule modal opens
- EC-M2.1.4: Edit → /admin/stops/[id]
- EC-M2.1.5: Publish (draft only) → status changes to active, refreshes
- EC-M2.1.6: Duplicate → /admin/stops/new?duplicate={id}
- EC-M2.1.7: Delete → confirm popover → confirm → row removed
AC-M2.2 — /admin/stops/new form
- Then required: city/state/location/date/time/brand
EC-M2.2:
- EC-M2.2.1: missing required field → red border + helper
- EC-M2.2.2:
?duplicate={id}pre-fills from existing stop - EC-M2.2.3: brand selector hidden for non-platform_admin
AC-M2.3 — AddStopModal
- Then opens with autofocus on city
EC-M2.3:
- EC-M2.3.1: missing city/state/location/date → submission blocked
- EC-M2.3.2: state uppercased on input
- EC-M2.3.3: state max 2 chars
- EC-M2.3.4: ESC closes
- EC-M2.3.5: success → modal closes + list refreshes
AC-M2.4 — LocationsTab
- Then table with search + status tabs + pagination
EC-M2.4:
- EC-M2.4.1: Add Venue modal opens
- EC-M2.4.2: Edit modal opens with pre-filled fields
- EC-M2.4.3: Delete with confirm → row removed
- EC-M2.4.4: search matches name/address/city/contact_name
AC-M2.5 — /admin/v2/stops mobile
- Then cards bucketed by Morning/Afternoon/Evening/Anytime
EC-M2.5:
- EC-M2.5.1:
?date=YYYY-MM-DDfilters - EC-M2.5.2: invalid date → empty or error
- EC-M2.5.3: 0 stops → "No stops scheduled"
- EC-M2.5.4: card with address → maps link works
PRODUCTS (H)
AC-M3.1 — /admin/products legacy list
- Then shows all brand-scoped products
EC-M3.1:
- EC-M3.1.1: 0 products → empty state
- EC-M3.1.2: platform_admin sees products from all brands
- EC-M3.1.3: brand_admin sees only their brand
AC-M3.2 — /admin/products/new form
- Then name required, type/brand/taxable/pickup_type/active selects
EC-M3.2:
- EC-M3.2.1: missing name → blocked
- EC-M3.2.2: image upload: drag-drop or click → preview
- EC-M3.2.3: oversized image (>5MB) → error
- EC-M3.2.4: wrong type → error
- EC-M3.2.5: success → /admin/products
- EC-M3.2.6: brand locked for non-platform_admin
AC-M3.3 — /admin/products/import CSV
- Then preview table shows parsed rows
EC-M3.3:
- EC-M3.3.1: malformed CSV → parse errors listed
- EC-M3.3.2: missing brand ID → Import disabled
- EC-M3.3.3: success → counts panel
AC-M3.4 — /admin/v2/products mobile
- Then cards with status pill + StockAdjustButton
EC-M3.4:
- EC-M3.4.1:
?filter=in-stock→ only products with inventory > 0 - EC-M3.4.2:
?filter=out→ only inventory = 0 - EC-M3.4.3:
?filter=hidden→ only active=false - EC-M3.4.4: StockAdjustButton increments/decrements inventory
COMMUNICATIONS (H)
AC-M6.1 — Campaigns list + create + edit
- Then list with type/status filters
EC-M6.1:
- EC-M6.1.1: New Campaign → name + type required → Create
- EC-M6.1.2: Save Draft → status=draft, no send
- EC-M6.1.3: Schedule Campaign with future datetime → status=scheduled
- EC-M6.1.4: Send Campaign → calls sendCampaign → status=sent
- EC-M6.1.5: Schedule with past datetime → blocked (datetime-local min = now)
- EC-M6.1.6: missing subject/body → blocked
- EC-M6.1.7: previewAudience → shows count + sample emails
AC-M6.2 — Templates list + edit
- Then edit fields: subject, body_text, body_html, template_type, campaign_type
EC-M6.2:
- EC-M6.2.1: missing subject → blocked (subject is NOT NULL)
- EC-M6.2.2: success → list refreshes
AC-M6.3 — Contacts list
- Then search + source filter + pagination
EC-M6.3:
- EC-M6.3.1: delete with confirm → row removed
- EC-M6.3.2: Export CSV → file downloads
- EC-M6.3.3: pagination prev/next
AC-M6.4 — Message logs
- Then status filter + search + pagination (1-5 pages)
EC-M6.4:
- EC-M6.4.1: filter by status=failed → only failed shown
- EC-M6.4.2: stats cards reflect filtered count
AC-M6.5 — Abandoned Carts
- Then stats + filter (all/active/recovered) + pagination
EC-M6.5:
- EC-M6.5.1: View → detail modal with items + timestamps
- EC-M6.5.2: Close → status=manually_closed
- EC-M6.5.3: Resend → calls resendAbandonedCartEmail
AC-M6.6 — Communication Settings
- Then senderEmail/senderName/replyTo/footerHtml form
EC-M6.6:
- EC-M6.6.1: invalid email → error
- EC-M6.6.2:
{unsubscribe_url}placeholder preserved in footerHtml
AC-M6.7 — ContactImportForm (multi-step)
- Then drag/drop or click → preview → import
EC-M6.7:
- EC-M6.7.1: file >5MB → bucket path
- EC-M6.7.2: missing required mapping → blocked
- EC-M6.7.3: success → counts panel
WHOLESALE (H)
AC-M7.1 — Wholesale dashboard loads
- Then shows tabs for Customers / Orders / Pricing / Settings
EC-M7.1:
- EC-M7.1.1: 0 customers → empty state
- EC-M7.1.2: 0 orders → empty state
AC-M7.2 — Wholesale Customers
- Then list with search + status filter
EC-M7.2:
- EC-M7.2.1: bulk "Send Price Sheet" → calls sendPriceSheetToCustomer N times
AC-M7.3 — Wholesale Orders
- Then list with status filter
EC-M7.3:
- EC-M7.3.1: bulk Fulfill → multiple fulfillments
- EC-M7.3.2: bulk Deposit → multiple deposits
AC-M7.4 — Wholesale Settings
- Then form saves via updateWholesaleSettings
EC-M7.4:
- EC-M7.4.1: invalid email → error
- EC-M7.4.2: min_order_amount < 0 → validation
SETTINGS (H)
AC-M8.1 — BrandSettingsForm
- Then all sections save atomically
EC-M8.1:
- EC-M8.1.1: logo upload → preview + URL stored
- EC-M8.1.2: nexus state pill: add via Enter or comma → chip appears
- EC-M8.1.3: nexus state pill: click ✕ → chip removed
- EC-M8.1.4: state field: lowercased input → uppercased display
- EC-M8.1.5: brand colors: text input + color picker stay in sync
- EC-M8.1.6: feature toggles: each toggle saves independently
AC-M8.2 — BrandFeatureCards
- Then each card shows status + Enable/Disable
EC-M8.2:
- EC-M8.2.1: Enable → confirmation modal → success toast + card shows Active
- EC-M8.2.2: Disable → optimistic toggle → rollback on failure
- EC-M8.2.3: "Open →" link visible only when enabled + adminRoute set
AC-M8.3 — CreateUserModal
- Then email/password (≥6) required
EC-M8.3:
- EC-M8.3.1: invalid email (no @) → blocked
- EC-M8.3.2: password < 6 → blocked
- EC-M8.3.3: missing brand for brand_admin → blocked
- EC-M8.3.4: success → temp password surfaced + email-sent status
- EC-M8.3.5: Copy button copies temp password
- EC-M8.3.6: role options depend on caller role
AC-M8.4 — PaymentSettingsForm
- Then provider radio + Stripe/Square OAuth buttons + Square Location ID
EC-M8.4:
- EC-M8.4.1: Square Location ID without
Lprefix → blocked - EC-M8.4.2: Save disabled when not dirty
- EC-M8.4.3: Stripe OAuth flow → return with ?stripe_connected=true → URL param stripped
- EC-M8.4.4: Square OAuth flow → return with ?square_connected=true → URL param stripped
- EC-M8.4.5: Sync Products/Orders/All Now → shows result banner
AC-M8.5 — AdvancedSettingsClient tabs
- Then tabs render Integrations / AI / Square / Shipping / Webhooks / Payments
EC-M8.5:
- EC-M8.5.1: Integrations: Resend + Twilio save independently
- EC-M8.5.2: AI Tools: provider selection changes default model
- EC-M8.5.3: AI Tools: API key required for Test
- EC-M8.5.4: Square Sync: App ID + Access Token required for Test
- EC-M8.5.5: Shipping: Test Connection (simulated) shows success after 1s
- EC-M8.5.6: Webhooks tab shows "Coming Soon"
- EC-M8.5.7: Payments: Connect with Stripe → redirects to Stripe
ANALYTICS (M)
AC-M9.1 — AnalyticsDashboard loads
- Then KPI cards + revenue chart + top products + recent orders + customer growth + funnel
EC-M9.1:
- EC-M9.1.1: period selector change → chart re-renders (visual only, no refetch)
- EC-M9.1.2: Refresh → fetches all
- EC-M9.1.3: Export Report → no-op
- EC-M9.1.4: 0 revenue → "No revenue data available yet"
- EC-M9.1.5: error → retry button works
TIME TRACKING (H)
AC-M11.1 — TimeTrackingAdminPanel tabs
- Then Summary / Workers / Tasks / Logs / Settings
EC-M11.1:
- EC-M11.1.1: 0 workers → empty
- EC-M11.1.2: Add Worker → name/role/language/active → Create
- EC-M11.1.3: Reset PIN → calls resetTimeWorkerPin
- EC-M11.1.4: Delete Worker → confirm → removed
- EC-M11.1.5: Logs filter by worker + task + date range → list updates
- EC-M11.1.6: Logs pagination 50/page
- EC-M11.1.7: Export → CSV downloads from /api/time-tracking/export
WATER LOG (H)
AC-M11.2 — WaterLogAdminPanel
- Then Add Headgate inline + Add User inline + filter chips + Export CSV
EC-M11.2:
- EC-M11.2.1: missing name → blocked
- EC-M11.2.2: PIN auto-generated on user create → PinBanner shows
- EC-M11.2.3: Rotate token → token changes
- EC-M11.2.4: Filter chips → entries filtered
- EC-M11.2.5: Export CSV → downloads
- EC-M11.2.6: Preview Report → window.alert
AC-M11.3 — HeadgatesManager bulk QR
- Then select multiple + Print → new window with QR sheet
EC-M11.3:
- EC-M11.3.1: 0 selected → Print disabled
- EC-M11.3.2: regenerate token → existing printed QRs invalidated
- EC-M11.3.3: per-headgate QR Download PNG
AC-M11.4 — Water Admin Settings
- Then toggles + session duration slider + alert phone
EC-M11.4:
- EC-M11.4.1: Regenerate PIN → confirm → PIN revealed
- EC-M11.4.2: session duration 1-168 hours only
- EC-M11.4.3: Save Settings → success banner
ROUTE TRACE (M)
AC-M11.5 — RouteTracePage loads
- Then shell renders with stat cards + lot list
EC-M11.5:
- EC-M11.5.1: missing feature flag → redirect
- EC-M11.5.2: lot detail → timeline + orders
LAUNCH CHECKLIST (L — known cosmetic)
AC-M11.6 — LaunchChecklist renders
- Then 8 categories × 4 tasks
EC-M11.6:
- EC-M11.6.1: "Mark All Complete" → no-op (documented limitation)
- EC-M11.6.2: "Export PDF" → no-op
- EC-M11.6.3: Per-task checkbox → no-op
- EC-M11.6.4: "Go →" links work
IMPORT CENTER (M)
AC-M11.7 — ImportCenterClient wizard
- Then Upload → Analyze → Preview → Import
EC-M11.7:
- EC-M11.7.1: drag/drop or click upload
- EC-M11.7.2: file >10MB → blocked
- EC-M11.7.3: AI detection confidence <80% → type override buttons shown
- EC-M11.7.4: column mapping dropdowns
- EC-M11.7.5: Import disabled when type=unknown
- EC-M11.7.6: success → counts + "View" link
PICKUP (C)
AC-M4.1 — DriverPickupPanel
- Then pending orders + picked-up orders collapsible
EC-M4.1:
- EC-M4.1.1: Pick Up → calls markPickupComplete → toast 3s
- EC-M4.1.2: stop filter → list filtered
- EC-M4.1.3: search → matches name/phone/order id prefix
- EC-M4.1.4: 0 pending → empty state
- EC-M4.1.5: picked-up older than 72h → hidden
SHIPPING (C)
AC-M5.1 — ShippingFulfillmentPanel
- Then shipping orders list
EC-M5.1:
- EC-M5.1.1: 0 shipping orders → empty
- EC-M5.1.2: filter by status
PUBLIC/AUTH (H)
AC-P1 — Login page
- Then email + password submit to /api/auth/sign-in
EC-P1:
- EC-P1.1: missing email → HTML5 validation blocks submit
- EC-P1.2: missing password → blocked
- EC-P1.3: invalid credentials → error banner
- EC-P1.4: dev_session=platform_admin → sets cookie and redirects /admin
- EC-P1.5: Google button disabled while loading
- EC-P1.6: Forgot? link → /forgot-password (or /api/forgot-password?)
AC-P2 — Change password
- Then new password + confirm match + ≥8 chars
EC-P2:
- EC-P2.1: passwords don't match → error
- EC-P2.2: <8 chars → error
- EC-P2.3: success → /admin
- EC-P2.4: "Sign out instead" → /logout
AC-P3 — Reset password
- Then new password + confirm
EC-P3:
- EC-P3.1: same as AC-P2
AC-P4 — Logout
- Then spinner → /api/auth/sign-out → /login
AC-P5 — Maintenance splash
- Then no controls, only mailto + /contact links
AC-P6 — Homepage
- Then hero + CTA links
AC-P7 — Pricing
- Then BillingToggle, FAQ accordion, Compare table expand
EC-P7:
- EC-P7.1: Monthly ↔ Annual swap content
- EC-P7.2: FAQ accordion: each opens/closes independently
- EC-P7.3: Compare table expand/collapse
AC-P8 — Contact
- Then form simulated submit (setTimeout)
EC-P8:
- EC-P8.1: missing required field → blocked
- EC-P8.2: success card replaces form
AC-P9 — Blog
- Then static + decorative newsletter form
AC-P10 — Changelog
- Then static timeline + RSS link
AC-P11 — Roadmap
- Then 3-column + Suggestion form (no submit) + Upvote buttons (no handlers)
EC-P11:
- EC-P11.1: form submit does nothing (reloads page)
- EC-P11.2: upvote buttons no-op
AC-P12 — Security
- Then static + mailto
AC-P13 — Privacy / Terms
- Then static text
AC-P14 — Brands showcase
- Then per-brand visit links
AC-P15 — Waitlist
- Then form posts to /api/waitlist
EC-P15:
- EC-P15.1: missing email → blocked
- EC-P15.2: success card replaces form
- EC-P15.3: server error → inline error
AC-P16 — Cart
- Then items list + qty controls + stop picker
EC-P16:
- EC-P16.1: − button decreases qty; at qty=1, "Remove" is the only path
- EC-P16.2: + increases qty
- EC-P16.3: Remove → item disappears
- EC-P16.4: stop mismatch → "Choose Correct Stop" alert
- EC-P16.5: incompatible items → "Remove Incompatible Items First" CTA
- EC-P16.6: empty cart → "Cart is Empty" CTA
- EC-P16.7: availability error → "Remove Incompatible Items First"
- EC-P16.8: ESC closes stop picker
- EC-P16.9: backdrop closes stop picker
AC-P17 — Checkout
- Then customer details + stop select + shipping (if ship) + Stripe Express iframe
EC-P17:
- EC-P17.1: missing email → blocked
- EC-P17.2: no stop + ship items → blocked (?)
- EC-P17.3: state field uppercased
- EC-P17.4: "Use secure hosted checkout" → Stripe redirect (uses placeholder key, will fail in QA)
- EC-P17.5: empty cart → "Back to storefront" link
AC-P18 — Auth API routes
- /api/auth/sign-in: 400/401/500 surfaces error
- /api/auth/sign-out: redirect /login
- /api/auth/forgot-password: always success (anti-enumeration)
- /api/auth/reset-password: 400 on <8 chars
- /api/auth/change-password: auth required
Known cosmetic-only behaviors (NOT bugs)
/admin/launch-checklistcheckboxes, "Mark All Complete", "Export PDF" are all no-ops (M11.6)/admin/analytics"Export Report" is no-op (M9.1 EC-3)/admin/analyticsperiod selector doesn't refetch (M9.1 EC-1)/admin/meprofile + email change forms always show "temporarily unavailable" (AdminMeClient)/admin/advanced/shippingand/admin/settings/square-sync/advancedare static mocks with setTimeout simulators/admin/communications/compose,/contacts,/segments,/logs,/analyticsall redirect to?tab=query (preserved backward compat)/blog/newsletterand/blog/downloadbuttons have no handlers/roadmap/suggestionform and upvotes have no handlers/admin/orders/[id]does NOT expose "fulfilled" status (deliberate)
These are documented limitations, not bugs. Any change to them should be reviewed for product intent first.