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
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.
| 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 |
- Params:
mediaType, viewType, category, page, pageSize
- Returns full product rows including
dealInfo, stockStatus, thumbnailUrl
- Used by: CatalogPage, DashboardPage (new releases)
- Params:
query (string), page, pageSize
- Returns same shape as GET_PRODUCTS
- Used by: CatalogPage search sidebar form
- Single product by
id
- Includes
merchandiseRollup, returnsAllowed, alternateCatalogId
- Used by: ProductDetailPage
- Params:
genreName, configurationName, excludeId, limit
- Used by: ProductDetailPage bottom section
- Returns all active deals with
productCount, waiveLooseBulk, waiveMinOrder
- Used by: DashboardPage deals table, CatalogPage on-deal sidebar
- Fragment-based:
CartItemFields + CartFields
- Returns computed totals:
totalProductAmount, totalLooseCharge, totalMinOrderCharge, grandTotal
- Paginated. Params:
userId, page, pageSize, status, fromDate, toDate
- Returns
totalPages, totalCount
GET_ORDER: full order with item-level surcharges
GET_ME: user profile + shipToAccounts[] + packlistEmailFlag
- All four cart mutation operations defined
- REMOVE + CLEAR use
refetchQueries to keep cache fresh
- Params:
cartId, poNumber, shipToAccountNo
- Returns:
orderId, status, grandTotal
- Wires CdsAdapter.submitOrder() server-side (stub in lab)
- UPDATE_PROFILE:
fullName, packlistEmailFlag
- CHANGE_PASSWORD:
currentPassword, newPassword
⚖️ 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.
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
- 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
- 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)
- 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)
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.
Tables: users, accounts, user_accounts, ship_to_accounts
Tables: carts, cart_items. Cart is per-user persistent (survives sessions).
Tables: orders, order_items. Tracks status, CDS reference, tracking number, surcharges per line.
Table: restock_subscriptions. Supports INT-2709 email notifications.
- 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)
DXL field → Snowflake column → GraphQL field mapping table.
snake_case columns → camelCase GQL.
YN flags → Boolean (e.g. loose_charge_in='Y' → looseChargeFlag=true).
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)
🔴 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.
Required Flash/Java plugin. Dead feature. Not in scope.
Dead library. Product detail is now a full page route.
Customer Service Workbench (VSR-CSW.dxl). Separate ticket required.
Order confirmation, packlist/invoice emails, restock alerts. Backlog.
Weekly Luminate/Soundscan ingest into Snowflake. Backlog.