Internal admin
Who runs MerchOS
The internal admin (internal.yourcustommerch.ca) is the surface YCM’s two cofounders use every day:
- Jess — runs the business side: customer acquisition, billing, design queue oversight, brand voice.
- Joy — runs the catalog: curation, vertical decks, supplier relationships.
Plus INTERNAL_DESIGNER users (junior team members) who work the design queue without business-side access.
The internal admin is the only portal with all roles. The other three apps (storefront, company-admin, marketing) are role-specific.
The left rail — every surface at a glance
Pipeline └── (CRM-style leads → opportunities → customers funnel)
Companies ├── List + filter + search ├── /companies/[id] — full company detail + onboarding state └── /companies/[id]/billing — tier swap, invoices, refunds
Orders ├── All orders (every company) ├── Per-company drill-in └── Refund interface (SUPER_ADMIN only)
Design queue ├── PRINT_FILE tickets (INTERNAL_DESIGNER works this) └── LOGO tickets (logo add-on customers)
Catalog ├── Raw — supplier mirror, triage entry point ├── Master — the canonical Skus ├── Verticals — per-vertical published catalogs ├── Categories — YcmCategory editor ├── Audit trail — every catalog mutation ├── Vertical curation health — orphan detection ├── Cost alerts — supplier price-drift alarms └── Digest — Monday 9am weekly email preview
Fundraising markup └── Outstanding balances + monthly payout batches
Tickets ├── Refund requests └── Issue reports (employee-submitted)
Launch readiness └── Pre-go-live checklist per Company
Tasks └── Operator todo list (cross-cutting)Every page is brand-themed via the internal admin’s theme picker (default tidewater; six alt-themes — three cosmic, three beach). Theme switcher in the topbar.
The catalog pipeline — Joy’s daily loop
The four modes from Chapter 6 each have a dedicated page:
Raw — /catalog/raw
Joy’s entry point most mornings. Lists the supplier-mirror as ~5K style-group rows (matview-backed). Filters: vertical, CA-fulfillable, category. Each row carries a representative image, variant count, price range, CA-fulfillable count, suppliers list.
Hover a row → quick-action drawer with:
- Promote — creates a DRAFT Sku from the RawStyle. Auto-prices at 2.5× supplier cost; copies sizes/colors/image; carries over ≥ MID-confidence AI vertical suggestions into
SkuVerticalDraft. - Defer — flags as MAYBE.
- Reject — structured
RejectReasonenum (NOT_MERCH / POOR_QUALITY / DUPLICATE / UNSHIPPABLE / WRONG_PRICE_POINT / WRONG_AUDIENCE / OTHER) + required note for OTHER. - Pull back — REJECTED/MAYBE → NEW.
All triage actions are bulk + idempotent — Joy multi-selects rows, action applies to all, partial failure doesn’t kill the batch.
Master — /catalog/master
The canonical Sku list. Three-pane drawer per Sku:
- Identity tab — name, code, category, brand, supplier, status (DRAFT / ACTIVE / DISCONTINUED).
- Colors / Sizes / Print Options tabs — axis editing. Add a color → it’s available across every size + print automatically (per the axis-model rationale in Chapter 6).
- Fulfillment tab — supplier coverage, per-(color × size × print) cell-state, manual exclusions. Refresh button re-runs the supplier-sync hook on demand.
- Curation tab — per-vertical print-option curation (ADR-0071) — Joy picks which print methods to recommend per vertical.
- Audit tab — every mutation on this Sku since promote, with actor + timestamp.
Demote (Phase 0155, 2026-05-19) — the “drop accidentally-promoted Sku back to Raw” escape hatch. Visible only on DRAFT Skus. Two-step modal:
previewDemoteImpact(skuId)lists any Company Products that would block the demote (NoAction FK gate).- Confirm → CASCADE wipes the Sku + all child rows;
CuratedStyle.statusflips back toNEWso the underlying RawStyle re-appears in Joy’s Raw queue.
Audit-event written for forensics. Integration-tested against real Postgres (master.service.int.spec.ts, 7 tests).
Verticals — /catalog/verticals
One row per active vertical. Each carries:
- Published Sku count
- Draft Sku count
- “Has unpublished changes” flag
- Last published date
- Published-vs-draft diff summary
{added, removed, reordered}
Drill into /catalog/verticals/[code] for the per-vertical editor:
- Draft layer — drag-to-reorder + per-Sku in/out + per-print-option curation.
- Publish button — runs the
VerticalsService.publishatomic-replace tx (Chapter 6 for the load-bearing details). Storefronts pick up the new order on the next request. - Revert button — copies published rows back into the draft layer.
- Snapshot history — Phase 0122 / ADR-0073 added auto-snapshots on every publish; revert against any past snapshot.
Vertical curation health — /catalog/vertical-curation-health
Cross-vertical orphan scan. A SkuVertical row with zero current SkuVerticalPrintOption recommendations + a Sku that has at least one active SkuPrintOption is flagged as a probable orphan (per the heuristic in isOrphanedCuration).
Each row links back to /catalog/master?skuId=… for in-place re-curation. Heartbeat icon next to the page link in the left rail when count > 0.
Audit trail — /catalog/audit-trail
Every catalog mutation since system inception: promotes, rejects, demotes, merges, vertical publishes, axis edits, mapping inserts. SUPER_ADMIN-only.
Filters: by Sku, by actor, by action type, by date range. CSV export.
The audit trail is the ground truth when something goes wrong — Joy can ask “who reordered this vertical last week?” and the answer is one query away.
Companies — /companies
Per-Company view with:
- Identity — slug, displayName, country, billingCurrency, vertical, pricingModel.
- Onboarding state — current
OnboardingStatus+ transition history viaOnboardingService.getHistory(companyId). - Billing — tier, subscription state, next invoice, refund history, SUBSCRIPTION_WAIVED toggle.
- Employees — invited / active / churned counts + roster.
- Products — every Product in this company’s catalog.
- Orders — every order, paginated.
Lifecycle actions (SUPER_ADMIN only):
- Pause / Resume the storefront.
- Force a state-machine transition (with audit trail).
- Activate manually (override
activatedAtif Stripe drift). - Issue a setup-fee credit-back (when SUBSCRIPTION_WAIVED isn’t enough).
Design queue — /design-queue
DesignTicket rows grouped by status:
- PENDING_DESIGN
- IN_REVIEW
- APPROVED
- REJECTED (needs rework)
INTERNAL_DESIGNER users see only their assigned tickets by default; SUPER_ADMIN sees all. Per-ticket page has upload + revision history + approve/reject buttons.
When all DesignTicket rows for a Company reach APPROVED, the activation cron flips the Company to LIVE. The activation is automatic but requires the human to approve the last ticket.
Orders — /orders
All orders across all companies + a per-company filter. Each row shows the multi-package status (FulfillmentTask aggregation) and links to the full order detail.
Refunds (SUPER_ADMIN only) — /orders/[id]/refund lets the operator issue:
- A Stripe refund on anonymous / MEMBER_PAYS / split-tender orders (full or partial).
- A
RefundRequestrow for gated company-paid orders (manual operator follow-up — credit on next invoice).
Split-tender orders refund LIFO (employee portion first via Stripe, then company rail via SpendRecord restore — see Pricing models).
Fundraising markup — /fundraising-markup
The custodial-liability ledger. Three views:
- Outstanding balances — per-Company
FundraisingMarkupEntryrows wherestatus != PAID. Aggregate balance per Company. - Cut a payout batch — multi-select unpaid entries + create a
FundraisingMarkupPayoutBatch. Marks entriesASSIGNED_TO_BATCH. - Mark batch paid — flip batch + all child entries to
PAID. Audit-logged.
CSV export at every level for the bookkeeper’s monthly close.
Tickets — /refund-requests + /tickets
RefundRequest rows: anonymous shoppers / employees flagging a problem on a delivered order. Each carries status, reason, conversation thread.
The Postmark Inbound integration (apps/api/src/modules/tickets/) creates ticket rows from inbound emails to support@yourcustommerch.ca — the operator can reply from inside the internal admin and the customer sees a normal email thread.
Launch readiness — /launch-readiness
Per-Company pre-go-live checklist (Phase 4 / ADR-0089). 12 checks per Product covering:
- Has approved print file?
- Has zone coverage matching the print method?
- Has color + size axis populated?
- Has at least one fulfillable variant per supplier link?
- (more — see ADR-0089 §Checks)
Failing checks gate Sku.activate() server-side. Launch readiness is the surface where Joy + the customer’s admin walk the failures together pre-go-live.
Tasks — /tasks
A cross-cutting operator todo list (Phase 0144). Auto-populated by system events (e.g. “Company Acme — print files for SKU-1234 missing”, “Customer-photo upload from Beta Co needs approval”) + manually-added items.
Search — ⌘K
SearchService cross-mode palette (Chapter 6). Searches Skus, RawStyles, CuratedStyles, SupplierProducts, Companies simultaneously. Ranked by relevance.
The most-used operator surface after Raw + Master — Joy can search “gildan 64000” and jump to whichever mode is most useful.
Themes — /settings/themes
The internal admin has seven themes:
tidewater(default — Stripe/Notion-style light)- 3 cosmic (
twilight,aurora,nova) - 3 beach (
coastline,lagoon,sunset)
Topbar picker switches live. Theme choice is per-user (stored in User.preferences), not per-company.
Guidance system (Phase 0121)
ADR-0072. Every internal-admin page uses <PageHeader> with both title and description props. Help-popovers (<HelpPopover>) explain non-obvious surfaces in plain English so the next operator (or a returning operator after months away) can self-onboard.
<EmptyState>, <NextStepCallout>, <DisabledReason> are the three other reusable guidance components. Custom ESLint rule to enforce <PageHeader> description is in POST_DEPLOY_BACKLOG.md.
What’s next
- Billing + Stripe — the per-flow billing implementation.
- Operations + reliability (Chapter 11) — Fly deploy cadence, migrations, integration tests, observability.
Canonical sources
apps/internal-admin/— the Next.js app rendering this surface.apps/api/src/modules/catalog-pipeline/— backing services (MasterService, InboxService, VerticalsService, SuppliersViewService, SearchService, CatalogDigestService).- ADR-0070 — Raw Catalog UI architecture.
- ADR-0071 — per-vertical print-option curation.
- ADR-0072 — guidance system.
- ADR-0089 — launch readiness model.
- ADR-0134 — design queue spec.
docs/services/catalog-pipeline.md— catalog-pipeline service-level deep read.
Triggers for update
Update this chapter if you:
- Add or retire a left-rail surface.
- Add a new drawer tab to the Master Sku detail view.
- Change the demote, promote, or vertical-publish flow shape.
- Add a new theme (and update the seven listed here).
- Add or change a role in the role-to-surface matrix.
- Move
RefundRequest/Tickets/Fundraising markupto a different module. - Add a new guidance component beyond the five named above.