INT-2682 · Virtual Sales Rep Replatform

State of the Union

2026-04-28 · branch: feature/INT-2699-frontend-wiring
TypeScript: 0 errors React 18 + Apollo pnpm monorepo DEV_MODE active Dev server broken
// overall completion by layer
Frontend Pages
85%
GraphQL Schema
95%
GQL Resolvers
70%
RDS Data Layer
75%
Snowflake Data
60%
Auth (suite-auth)
15%
CDS Integration
20%
CI / Infra
40%
Fully implemented
Implemented, not fully tested or wired end-to-end
Stub / scaffolded — not production-ready
Not started
01

Tech Stack — Frontend

Entry
index.tsx → createRoot() index.pug → HTML shell vsr-theme.scss → global CSS
App shell
BrowserRouter (react-router-dom v5) ApolloProvider (Apollo Client 3) DevModeProvider (lab auth bypass)
Layout
VsrLayout.tsx — shared chrome No initApplication() yet (Phase 10)
Components
ProductRow.tsx ✓ CheckoutSteps.tsx ✓ @theorchard/suite-components v13 (installed, not fully used yet)
State
Apollo InMemoryCache (primary) No Redux / Zustand sessionStorage bridge for checkout
Styling
Global SCSS (BEM classes) No CSS Modules (decision D-VIS-001) No Tailwind
Build
@theorchard/frontend-cli (Vite plugin) ⚠ webpack plugin error — broken
💡 Pure React — Not suite-frontend
The app is wired as pure React 18 + Apollo Client, not through initApplication() from @theorchard/suite-frontend. The suite-frontend bootstrap (Auth0, left nav, top nav) is intentionally deferred to Phase 10 (INT-2697). The DevModeProvider stands in for suite-auth in the lab. When suite-auth lands, DevModeProvider is removed and initApplication() wraps the app — zero downstream changes needed.
02

Routes

Path Component Data Status
/dashboard DashboardPage GET_PRODUCTS, GET_DEALS, GET_ORDER_HISTORY, GET_ME done
/catalog/:tab/:subcategory CatalogPage GET_PRODUCTS, SEARCH_PRODUCTS, GET_DEALS (sidebar) done
/product/:productId ProductDetailPage GET_PRODUCT, GET_RELATED_PRODUCTS, ADD_TO_CART done
/cart CartPage GET_CART, UPDATE_CART_ITEM, REMOVE_CART_ITEM, CLEAR_CART done
/checkout/shipping ShippingPage GET_ME (ship-to accounts), GET_CART (summary) done
/checkout/review ReviewPage GET_CART, PLACE_ORDER done
/orders OrderHistoryPage GET_ORDER_HISTORY (paginated, status filter) done
/orders/:orderId OrderDetailPage GET_ORDER done
/order-tools/:section? OrderToolsPage None (file upload stub) stub
/account/profile ProfilePage GET_ME, UPDATE_PROFILE, CHANGE_PASSWORD done
/register RegisterPage None (client validation only) stub
03

Apollo Queries & Mutations

GET_PRODUCTS
productQueries.ts
  • Params: mediaType, viewType, category, page, pageSize
  • Returns full product rows including dealInfo, stockStatus, thumbnailUrl
  • Used by: CatalogPage, DashboardPage (new releases)
SEARCH_PRODUCTS
productQueries.ts
  • Params: query (string), page, pageSize
  • Returns same shape as GET_PRODUCTS
  • Used by: CatalogPage search sidebar form
GET_PRODUCT
productQueries.ts
  • Single product by id
  • Includes merchandiseRollup, returnsAllowed, alternateCatalogId
  • Used by: ProductDetailPage
GET_RELATED_PRODUCTS
productQueries.ts
  • Params: genreName, configurationName, excludeId, limit
  • Used by: ProductDetailPage bottom section
GET_DEALS
productQueries.ts
  • Returns all active deals with productCount, waiveLooseBulk, waiveMinOrder
  • Used by: DashboardPage deals table, CatalogPage on-deal sidebar
GET_CART
cartOrderQueries.ts
  • Fragment-based: CartItemFields + CartFields
  • Returns computed totals: totalProductAmount, totalLooseCharge, totalMinOrderCharge, grandTotal
GET_ORDER_HISTORY
cartOrderQueries.ts
  • Paginated. Params: userId, page, pageSize, status, fromDate, toDate
  • Returns totalPages, totalCount
GET_ORDER + GET_ME
cartOrderQueries.ts, userQueries.ts
  • GET_ORDER: full order with item-level surcharges
  • GET_ME: user profile + shipToAccounts[] + packlistEmailFlag
ADD_TO_CART / UPDATE / REMOVE / CLEAR
cartOrderMutations.ts
  • All four cart mutation operations defined
  • REMOVE + CLEAR use refetchQueries to keep cache fresh
PLACE_ORDER
cartOrderMutations.ts
  • Params: cartId, poNumber, shipToAccountNo
  • Returns: orderId, status, grandTotal
  • Wires CdsAdapter.submitOrder() server-side (stub in lab)
UPDATE_PROFILE / CHANGE_PASSWORD
userMutations.ts
  • UPDATE_PROFILE: fullName, packlistEmailFlag
  • CHANGE_PASSWORD: currentPassword, newPassword
04

Pricing Logic

⚖️ Dual pricing — server is authoritative
Pricing is implemented in two places. packages/vsr-graphql-server/src/utils/pricing.ts is the authoritative server-side computation. apps/vsr/src/utils/pricing.ts is a mirror used only for display estimates before mutation round-trips complete. If they ever diverge, the server result wins. Server exports: computeCartItemCharges(), computeCartTotals(), computeEffectivePrice(), isDealActive(). Both are unit tested.
05

GraphQL Server Architecture

Runtime
Node ≥ 24 @apollo/server v4 (standalone) ts-node-dev in lab
Schema
root.graphql product.graphql cart.graphql order.graphql user.graphql merged via @graphql-tools
Resolvers
productResolvers.ts cartOrderResolvers.ts userResolvers.ts
Data sources
RdsDataSource.ts (partial) SnowflakeDataSource.ts (partial) CdsAdapter.ts (stub)
Auth
authGuard.ts DEV_MODE: reads x-dev-* headers PROD: JWT validation — Phase 10
06

Data Sources

RdsDataSource
packages/vsr-graphql-server/src/dataSources/RdsDataSource.ts
  • PostgreSQL via raw pg (node-postgres) — no ORM
  • All values parameterized via $N — SQL injection protected
  • Handles: users, accounts, ship-to lists, carts, cart items, order history, order placement
  • ✓ Row types defined for all tables
  • ⚠ Not tested end-to-end against live Docker postgres yet
SnowflakeDataSource
packages/vsr-graphql-server/src/dataSources/SnowflakeDataSource.ts
  • Reads from cross-DB views: V_PRODUCTS, V_DEALS, V_INVENTORY, V_MERCHANDISE_ROLLUP
  • Auth: RSA private key JWT (Secrets Manager). Lab: SNOWFLAKE_* env vars
  • ⚠ Views exist in DDL (snowflake-views.sql) but not yet deployed to VIRTUALSALESREP.DEV
  • ⚠ Blocked by INT-2683 (VSR_DEV_SVC service account pending)
CdsAdapter
packages/vsr-graphql-server/src/dataSources/CdsAdapter.ts
  • Interface defined from INT-2694 consultation
  • Lab stub: logs payload, returns fake CDS order ID
  • Real HTTP client: Phase 11 (INT-2733)
  • submitOrder(payload), getOrderStatus(cdsOrderId)
07

Data Flow

React Page
Apollo Client
GQL Server :4001
Resolver
RdsDataSource
+
SnowflakeDataSource
PLACE_ORDER
cartOrderResolvers
RDS (write order)
CdsAdapter.submitOrder() ← STUB
🧪 Lab mode uses the Stub server on :4000
tools/vsr-graphql-stub/src/index.ts is a 301-line Apollo server with hardcoded mock data. It mirrors the real GQL schema exactly and runs on port 4000. The real server runs on port 4001. VITE_GRAPHQL_URL in .env.local switches between them.
08

Database — RDS (PostgreSQL)

Migration 001 — users + accounts
infra/sql/001_users_accounts.sql
Tables: users, accounts, user_accounts, ship_to_accounts
Migration 002 — cart
infra/sql/002_cart.sql
Tables: carts, cart_items. Cart is per-user persistent (survives sessions).
Migration 003 — orders
infra/sql/003_orders.sql
Tables: orders, order_items. Tracks status, CDS reference, tracking number, surcharges per line.
Migration 004 — notifications
infra/sql/004_notifications.sql
Table: restock_subscriptions. Supports INT-2709 email notifications.
09

Database — Snowflake Views

snowflake-views.sql DDL
infra/sql/snowflake-views.sql
  • Views defined: V_PRODUCTS, V_DEALS, V_INVENTORY, V_MERCHANDISE_ROLLUP
  • Target: VIRTUALSALESREP.DEV schema
  • ⚠ Not yet deployed — blocked by INT-2683 (VSR_DEV_SVC service account)
FIELD_MAPPING.txt
infra/sql/FIELD_MAPPING.txt
DXL field → Snowflake column → GraphQL field mapping table. snake_case columns → camelCase GQL. YN flags → Boolean (e.g. loose_charge_in='Y'looseChargeFlag=true).
10

Monorepo + CI

virtual-sales-rep/
  package.json ✓ pnpm workspace root
  biome.json ✓ linter config
  Jenkinsfile ⚠ scaffold only
  apps/vsr/ ✓ React frontend
  packages/vsr-graphql-server/ ⚠ resolvers partial
  tools/vsr-graphql-stub/ ✓ mock server
  infra/sql/ ✓ 4 migrations + Snowflake DDL
  docker/ ✓ postgres:16-alpine + db-reset.sh
  start-dev.sh ⚠ frontend start broken (webpack plugin)
11

Key Architectural Decisions

ID Decision Why
D-LAB-001 Raw pg for RDS — no ORM No ORM exists in orchard-suite; matches connector-neo4j pattern of raw driver queries
D-LAB-002 exceljs server-side only Org pattern: backend generates file → presigned S3 URL → download link. No client-side xls parsing.
D-LAB-004 DEV_MODE auth bypass via .env.local Enables lab testing without Auth0. Fully removable at Phase 10 with zero downstream changes.
D-LAB-005 Snowflake MCP uses SSO (externalbrowser) VSR_DEV_SVC service account not yet provisioned. SSO is safe for interactive exploration.
D-VIS-001 Global SCSS (BEM) instead of CSS Modules CSS Modules per page creates duplicate theme values. VsrLayout + all pages share the same BEM class set — global is correct here.
D-VIS-002 Plain <table> + theme classes instead of GridTable from suite-components GridTable requires column definitions incompatible with the variable layout of product rows (thumbnail, price block, inline CTA). Plain table gives full control.
D-VIS-003 /product/:productId instead of /catalog/:productId New /catalog/:tab/:subcategory URL structure conflicts with a :productId param at the same depth.
D-VIS-004 CoverFlow omitted from replatform Required Flash/Java plugin — confirmed dead feature. Not in scope.
D-VIS-005 Product detail as full page, not modal Shadowbox.js is dead. Full page is simpler and more accessible. Modal overlay can be a follow-up ticket.
12

Blockers & Open Gaps

🔴 BLOCKER — Dev server broken
pnpm dev fails: "Failed to find the plugin @theorchard/frontend-cli-webpack"
Root cause: @theorchard/frontend-cli is resolving a webpack plugin loader but frontend.json specifies "plugins": ["vite"] and @theorchard/frontend-cli-vite: ^2.3.0 is installed. Version mismatch in cli wrapper.
Fix: Pin @theorchard/frontend-cli to a version that correctly hands off to the vite plugin, or wire pnpm dev directly to frontend-cli-vite start.
🔴 BLOCKER — Snowflake VSR_DEV_SVC service account missing
INT-2683 merged the VIRTUALSALESREP database PR but the VSR_DEV_SVC service account was not provisioned. SnowflakeDataSource cannot connect without it. Views are defined in DDL but not deployed. Blocks: catalog data, deals, inventory, and related products in the real server.
🟡 GAP — CDS Adapter is a stub (INT-2733)
CdsAdapter.ts logs payloads and returns a fake CDS order ID. Real HTTP client with polling Lambda + error handling is Phase 11. PLACE_ORDER mutation works end-to-end in lab because the stub is transparent — the interface contract is defined and stable.
🟡 GAP — Auth is DEV_MODE only (INT-2697)
DevModeProvider + x-dev-* headers bypass Auth0. Suite-auth integration is Phase 10. Open question: does Orchard Permissions Platform support external B2B users? Decision needed before implementation. Contact: Wilson Cheong.
🟡 GAP — Order Tools are stubs (INT-2705)
Excel download, Excel upload, and RecordTrak upload all render UI but don't call any backend. File chooser works. No API integration. Depends on S3 presigned URL pattern (D-LAB-002).
🟡 GAP — Stub server filter params unverified
viewType values BEST_SELLERS, GENRE, CONFIGURATION, EXCLUSIVE and the category sidebar filter param were added in the route restructure but not yet verified against the stub server resolver. Some may fall through to a default.
🟡 GAP — No page-level tests
Only pricing.spec.ts exists (frontend + server). No component or page tests yet. Visual comparison against crawl screenshots blocked by dev server issue above.
13

What Was NOT Ported (intentionally)

CoverFlow / Flash Browse
Required Flash/Java plugin. Dead feature. Not in scope.
Shadowbox.js product modals
Dead library. Product detail is now a full page route.
Admin / CSW module
Customer Service Workbench (VSR-CSW.dxl). Separate ticket required.
Email notifications (INT-2709)
Order confirmation, packlist/invoice emails, restock alerts. Backlog.
Bestsellers Soundscan feed (INT-2710)
Weekly Luminate/Soundscan ingest into Snowflake. Backlog.