feat(merchant): orders inbox rebuild, home dashboard, and first-class order flows - #175
Conversation
…pers Reframe the merchant orders page around order status, matching Market /orders: - Left panel labelled Orders (was Conversations). - Selected order uses a subtle orange tint from the shared secondary token (bg-secondary-500/10 + border-secondary-500/40). - Each row shows a StatusPill; the selected order detail shows a StatusStepper of the order lifecycle -- the same shared UI the Market uses. Steps carry subtitles so the stepper's connector line renders (it is a flex-1 bar that needs row height, which title-only rows collapsed). Shared logic: new @conduit/core order-status module (getOrderStatusDisplay, buildOrderStatusTimeline) maps an order status string to pill tone + stepper rows for both apps. Market's buildOrderTimeline/deriveOrderHeaderStatus stay app-local -- they need the buyer-side OrderViewModel the merchant lacks -- so only the status-keyed presentation is shared. Core exposes structurally StatusStepperRow-compatible types to avoid a @conduit/ui dependency. Unit-tested.
…ges) Selected order card layout: - StatusStepper is a fixed header, always visible regardless of the active tab. - Details/Actions use the market's segmented pill selector (restyled Radix TabsList/TabsTrigger: rounded-full, self-start so it sizes to its options rather than stretching the flex column), matching the /products source control. - Messages is a bottom-right icon button that sets the controlled active tab. Order-row status: pill is shrink-0 and the order id is min-w-0/break-all so the StatusPill no longer clips when the id is long.
…ut shift Add smart action buttons right of the Details/Actions selector, driven by a shared core helper getMerchantOrderActions(status): - pending/paid: Decline order (destructive, left) + Accept order (primary) - accepted: Cancel order (destructive, left) + Mark as shipped (primary) - shipped/delivered/terminal: none (buyer confirms delivery) Each publishes the mapped status via a param-based advanceStatusMutation and refetches; the clicked button shows a sending state. Also remove the standalone 'Checking latest order messages…' block: it inserted/ removed a flow element on every poll, displacing the list below it. Refresh state is already conveyed by the refresh button (Refreshing…/Updated). Core helper unit-tested.
Order items rendered the raw product id (30402:pubkey:d) with no image. Resolve each item against the merchant's own listings (getMerchantStorefront, keyed by address/id) to fill name + thumbnail, and show a 40px image, name, quantity, and unit price per line plus the subtotal. OrderDetailCard gains an optional imageUrl and drops the product-id hash line (falls back to 'Product' if a title is missing). Order messages carry no image, so the merchant catalog is the source.
- port market orders layout: hero, two-column grid, order-progress stepper, items/shipping/order-details cards, and list-card styling - add order search box, market-parity list spacing, and mobile horizontal scroller + search sheet - resolve order-item name/image via a single batched product read (getProductsByIds) instead of per-item fanouts - share the order message bubble via @conduit/ui OrderConversationMessage (used by market messages + merchant), drop app-local copies - merge actions/messages under a segmented toggle; centered inline reply input with paper-plane send - link buyer to their market storefront; fix order date (ms) rendering
- move buyer info to a bottom-right card (Message button only); move the smart status actions into the Actions card - Actions: dropdown-selected single form (invoice/status/shipping), grouped under Respond + Manual action, drop nested form cards - extract floating chat to @conduit/ui OrderMessagesWidget (shareable); full-screen on mobile, anchored card on desktop - compact order-item bubbles (thumbnail + qty + price, no address hash) via resolveItem; drop verbose product-id line - let the detail panel flow with the page (sticky list rail) instead of an independent scroll pane
Model payment and acceptance as two independent gates instead of a
linear status, so the zap-out (pay-first) and order-first (invoice)
flows are both first-class:
- order-status: add MerchantOrderState + deriveOrderFlow; timeline
orders payment↔acceptance by flow; shipping is gated on payment;
unify status vocabulary (invoiced/processing/complete) with
forward-compat; string args still accepted
- merchant: derive {status, paid, invoiceSent} from the order summary
and drive actions + timeline from it; warn that cancelling a paid
order needs a manual (non-custodial) Lightning refund
- spec: document the two flows, the paid⟂accepted gates, and the
refund-as-recorded-payout convention in order-lifecycle.md
- tests: flow ordering, payment-gated shipping, deriveOrderFlow
…ilter - order-status: add orderFlowFromCheckoutMode (authoritative flow from the buyer's checkout mode) alongside deriveOrderFlow - market order-view: expose `flow` on OrderViewModel and relabel the invoice timeline row as direct payment for prepaid (zap-out) orders - merchant: add All/Pending/In Progress/Completed phase pills (map order status → phase; cancelled shows only under All) under the search box, in both the desktop list rail and the mobile search sheet
Order gift wraps are self-copied into the sender's inbox, so a pubkey that both buys and sells received both sides. Gate each conversation list by the `order` message direction (buyer = sender, merchant = recipient): - merchant list drops orders the principal placed as a buyer - buyer list drops orders the principal received as the merchant - also derive buyer/merchant pubkey from the order message Plus item search over product name/description/tags via one batched listing read across recent orders.
Reuse the shared OrderMessagesWidget on the buyer orders page so messaging the merchant happens in a floating window (full-screen on mobile) without navigating away. The reply publishes an order `message` gift-wrapped to merchant + buyer self-copy and refreshes the buyer conversation queries. Message-merchant and shipping "Edit" links now open the widget.
…lter Buyer phase tabs used vm.phase, which marks any *sent* order as in_progress, so "waiting for invoice" never showed under Pending. Add getOrderFilterPhase (unpaid → pending, paid/fulfilling → in_progress) and filter on it. Show the phase pills under the search box on the desktop rail too, and drop the now-obsolete desktop tab-collapse.
- drop the bordered card around the mobile nav user menu (keep the network badge above it) - match the market header: user-menu trigger uses bg-primary-500 / white text / hover:bg-primary-600 (applies to the sidebar too)
…st item - extract BuyerAvatar + OrderListItem to components/OrderListItem and reuse in the orders route - home page now lists recent orders from the merchant conversation list (buyer avatar, name, preview, total, status pill) with buyer profile hydration, instead of a plain text row — role-filtered and status-aware
Add four order-analytics charts derived from the merchant conversation list (role-filtered), rendered as self-contained inline SVG using theme tokens: - Orders over time (hero): 30-day daily bar chart of order volume - Order status: donut of pending/in-progress/completed/cancelled - Revenue (paid): 30-day bars, amounts normalized to sats via btc/usd - Top products: horizontal bars by ordered quantity Extract a shared order-phase helper (used by orders + charts) and a pure buildDashboardChartData aggregator with unit tests.
…oduct card - add shared @conduit/ui DoubleSideStatusPill (primary-filled left half + faint primary-tint bordered right half) - product card title-aside now shows the Active status pill and a DoubleSideStatusPill for the zap policy (e.g. "Public zap" / "generic") - drop the separate safety row for active listings (info moved into card; the warning row stays for non-active listings)
Deploying conduit-market with
|
| Latest commit: |
a7ccbf4
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://bf066643.conduit-market-coo.pages.dev |
| Branch Preview URL: | https://feat-merchant-orders-status.conduit-market-coo.pages.dev |
Deploying conduit-merchant with
|
| Latest commit: |
a7ccbf4
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://9608d4a1.conduit-merchant-33n.pages.dev |
| Branch Preview URL: | https://feat-merchant-orders-status.conduit-merchant-33n.pages.dev |
Preview Deploys
Mainnet Branch URLs
Commit-level preview links are hidden by PR setting ( Mainnet preview links for latest commit a7ccbf4 |
There was a problem hiding this comment.
P1 [packages/core/src/protocol/commerce.ts:2269, 2342]: the new role gate still lets a bucket through when the initial order message is missing. Both the live inbox read and the cached helpers are explicitly partial/limited, so older order wraps can fall out while later status/proof messages remain. In that case a dual-role pubkey will see the conversation in both buyer and merchant views again, which reintroduces the mixup this PR is trying to eliminate.
P1 [apps/market/src/lib/order-view.ts:276]: Merchant can now emit accepted, but Market still drops that status before it reaches the buyer view-model. For invoice-first orders, acceptance regresses to merchantStatus = null, so the buyer header/timeline/filtering never reflect the acceptance gate.
P2 [packages/ui/package.json:18]: @conduit/ui now depends on @conduit/core, which conflicts with the documented shared-package boundary in AGENTS.md and docs/ARCHITECTURE.md. If this inversion is intentional, the repo contract should be updated first; otherwise the protocol helpers/types need to stay out of @conduit/ui.
Require non-empty tracking and carrier values at the form and publish boundary. Preserve item fulfillment format, skip shipment only for merchant-verified digital-only orders, and keep unresolved or mixed orders on the physical fulfillment path.\n\nValidated with typecheck, lint, telemetry policy, 838 passing tests, and production builds.
Require both the order snapshot and current merchant listing to be digital before skipping shipment. A physical snapshot remains authoritative for preserving fulfillment on historical orders.\n\nValidated with focused fulfillment tests, full test suite, typecheck, lint, and telemetry policy.
Make fulfillment timeline copy reflect completed, active, and waiting states. Present the recommended next action before destructive alternatives and cover the hierarchy with regression tests. Validation: bun run typecheck; bun run lint; bun run telemetry:check; bun test; bun run build
Keep terminal and payment-evidence timelines aligned with available actions, guard concurrent order publications, and preserve guest out-of-band payment confirmation. Add accessible confirmation semantics and behavior-level state coverage. Validation: bun run typecheck; bun run lint; bun run telemetry:check; bun test; bun run build; React Doctor exact delta
Keep orderless partial reads distinct from known out-of-band guests so only explicit guest orders can record manual payment confirmation. Validation: focused order/action/phase tests; typecheck; lint; telemetry policy
ericfj2140
left a comment
There was a problem hiding this comment.
I reviewed the hardening work from 110a4b6 through the current PR head, a7ccbf4. This is now in a merge-ready state from my side.
The biggest change is that order handling no longer treats payment, acceptance, fulfillment, and communication as one unreliable linear status. Buyer payment reports and proofs remain evidence that the merchant must verify; merchant-confirmed settlement implies acceptance and moves the order directly into fulfillment. This removes the redundant accept step for paid orders, suppresses ordinary invoice actions after payment, and gives the Merchant inbox operational queues for paid fulfillment, payment verification, unpaid review, shipped, and closed orders.
Partial relay/cache histories were hardened throughout the stack. Buyer and merchant roles are recovered from fixed message direction when enough evidence survives, while ambiguous histories remain conservative. Participant hints now preserve payment evidence and merchant state consistently across the list, selected-order pane, dashboard charts, status pills, filters, and buyer timeline. Buyer-side paid projection also remains consistent when only a merchant status survives a partial read, without incorrectly inferring a prepaid flow.
Fulfillment was completed as a real workflow rather than a manual status console. Physical shipments require both a non-empty tracking code and carrier at the form and mutation boundaries; tracking URL and notes remain optional, and rendered tracking links are restricted to HTTP(S). One shipping update advances the order to shipped, and shipped orders expose delivery completion. Digital-only orders skip shipment only when both the durable order snapshot and resolved merchant-authored listings agree every item is digital. Physical, mixed, unresolved, deleted, and legacy listings conservatively retain the shipping gate, and historical shipping events backfill the earlier paid/accepted gates.
Guest handling now matches the actual communication boundary. Explicit guests use phone/email out of band while Merchant records decisions, settlement confirmation, shipping, and completion as encrypted self-copies. Their order history is readable without presenting a fictional reply composer. Orderless partial histories remain a distinct unknown state and cannot use the guest-only manual payment shortcut. Legacy signed-in orders without the newer identity marker still retain buyer-and-self delivery.
The Merchant progress and action UI was reorganized around the next decision. Completed, active, and waiting timeline rows now use state-appropriate language; active shipping reads “Shipping in progress” and explains how to advance it. Cancelled and refund-requested timelines stop at the terminal state. The recommended next step appears first, with cancellation moved into a destructive “Other actions” section and explicit paid/payment-evidence refund warnings. Destructive actions use confirmation dialogs with alert-dialog semantics, publication actions share an exclusive lock, and success/error feedback uses live status/alert semantics. The horizontally scrolling queue pills were also replaced with the shared accessible dropdown in both the desktop rail and mobile orders sheet.
Protocol construction for Merchant order actions was moved behind @conduit/core. The shared publisher makes buyer-and-self versus self-only delivery explicit while preserving the order-linked inner p tag, NIP-17/NIP-59 wrapping, and the current kind-16 encoding. The known kind collision is intentionally not partially migrated here; that remains tracked behind OpenMarketsFoundation/specification#12 and CND-191.
The repo contracts were updated in the same PR to match the implemented settlement, decision, fulfillment, communication, shipment, digital-order, and guest-order behavior. The contribution/review workflow now allows required contract updates to land with implementation and reserves separate decision PRs for broad architecture or external consensus.
The review feedback was handled comprehensively: partial role separation, accepted-status propagation, the documented UI-to-Core package edge, snapshot-aware shipping readiness, proof-only queue/dashboard consistency, selected-pane participant hints, buyer paid projection, conservative flow inference, legacy buyer delivery, historical physical fulfillment, and the guest/unknown communication distinction are all covered. All 13 Sudden Agent threads are resolved, and the latest Agent review is green.
Validation is green at the current head: 847 tests pass with 1 skip, and CI passes format, lint, typecheck, telemetry and color policies, E2E smoke, mainnet build, dependency/security scans, both preview deployments, and Agent review. The final dropdown commit has green CI and deployed previews but did not receive a fresh signed-in visual pass; because both layouts use the same shared accessible control and the earlier signed-in preview covered the surrounding fulfillment hierarchy, I consider that a low-risk visual spot-check item rather than a merge blocker.
Remaining limitations are understood and are not blockers for this PR: refunds are still manual/out of band; guest self-copies record Merchant intent but do not prove buyer notification; product revisions are not historically pinned; the private-commerce kind migration remains separate; and the large Merchant/Market order routes remain future refactor candidates.
Approved.
Preview Settings
Summary
Rebuilds the Merchant orders inbox and dashboard around one shared order-state model, keeps buyer and merchant order roles separated, and makes prepaid and invoice-first flows explicit.
This iteration also finishes the merchant fulfillment path:
Other actionssectionThe Open Markets kind 16/17 collision is intentionally not changed here. The future migration is tracked in CND-191 and gated on OpenMarketsFoundation/specification#12.
Tracking
Contract Changes
docs/specs/order-lifecycle.mddocs/specs/merchant.mdtypeformat and digital-products-skip-shipping behaviorImplementation Changes
Merchant orders
shipping_updateitself projects to shipped.Shipping in progresswith a tracking-details prompt.Respondgroup with aNext stepsection and moves cancellation to a redOther actionssection after the primary workflow.alertdialogsemantics.Shared order behavior
@conduit/core.ptag.Existing PR scope retained
PR Checks
@conduit/coreValidation
bun run typecheckbun run lintbun test— 847 pass, 1 skipbun run buildbun run telemetry:check60b3686versus3ae9dd1reports no new warnings60b3686Runtime / Visual Evidence
60b3686; the prior signed-ine240819preview confirmed the paid physical hierarchy and copy, while the later commits harden edge states and confirmation behavior.tseepdirect eval and large Market/Merchant chunks.Risks and Follow-ups
Contract Review
Reviewer decision:
Review Focus
apps/merchant/src/lib/order-phase.tsandapps/merchant/src/routes/orders.tsxapps/merchant/src/lib/shipping-update.tspackages/core/src/protocol/order-status.tspackages/core/src/protocol/merchant-order-publish.tsapps/merchant/src/routes/orders.tsxpackages/core/src/protocol/order-status.tsapps/merchant/src/routes/orders.tsx