fix(widgets): modal contrast, contact-support clickability, BUY/PAID status tiers - #6
Merged
Merged
Conversation
added 6 commits
May 15, 2026 18:48
Modal portals to document.body, escaping the calling widget's root where `themeToCssVars(theme)` is applied. The card's `background: var(--p2p-color-bg, #ffffff)` fell back to white, while inner content (which reaches the host's CSS vars via the inner themeStyle div) renders text in near-white — producing white-on-white text on dark integrators. Each Modal caller now passes its `themeStyle` to Modal, which spreads it on the backdrop. CSS cascade carries the vars to the card so the surface matches the integrator's palette. Also pins `color: color.text` on the card so descendants without an explicit color inherit a readable token. Verified on merchant-app demo: dialog bg now resolves to integrator dark surface, text near-white — proper contrast restored.
… recourse Previous behavior only rendered the Contact Support chip when the order was inside the dispute window or after a dispute was filed. PLACED / ACCEPTED / PAID / COMPLETED states showed status text with NO action — leaving users with no way to contact support when an order was stuck (e.g. PAID waiting on a merchant-confirm-completion watcher that hasn't fired). Now every row renders a Contact Support button. Variant depends on state: - In dispute window → chip with countdown, click opens report flow - Dispute open / resolved → plain button with dot, click opens chat - Everything else → plain button, click opens chat (or surfaces "Support not available yet" if the order isn't bound to an inbox yet) End users always have a way to reach support, even when no on-chain dispute path is currently valid.
…s have a recourse" This reverts commit f57f0bf.
…to chat when no txSigner
Previous behavior: the in-window report chip rendered with disabled={!txSigner}.
When the embedder didn't wire txSigner (or it was somehow undefined), the
chip appeared visibly greyed out and the HTML disabled attribute blocked
the click — leaving the user with a visible but non-functional button.
Now: the chip is always clickable. Click target depends on txSigner:
- txSigner present → opens the on-chain report flow (raiseDispute)
- txSigner missing → opens chat (will surface "Support not available yet"
if the bridge has no inbox bound, but the user is never left without a
response on click)
Two related smart-action machine improvements: 1. **PLACED past 5min → "still matching, taking longer than usual"** — the chain's status field stays PLACED until an `autoCancelExpiredOrders` keeper sweep runs, which lags the actual accept-window expiry. The widget now surfaces the staleness so users don't keep staring at "Placed · matching" forever when no merchant can pick the order up anymore. 2. **BUY/PAID renders a countdown** — "Paid · processing payment · completes within <X>m" while inside the 30-min processing window (matches contract's placedAt + orderExpiry envelope). Past the window we fall back to the plain "Paid · processing payment" label. Constants exported: - `PLACED_STALE_THRESHOLD_MS = 5 * MIN` - `BUY_PAID_PROCESSING_WINDOW_MS = 30 * MIN` 71 node:test cases pass, 55 vitest pass, typecheck + build clean.
… band) Revise the BUY/PAID label tiers per UX spec: < 5min → "Paid · processing payment" 5–30min → "Paid · processing payment · taking longer than usual" ≥ 30min → "Paid · processing payment · will resolve within <countdown>" The middle tier is a gentle warning without a countdown (drops the noise of a constantly-changing timer for orders that are merely a few minutes overdue). The outer tier anchors the countdown on BUY_DISPUTE_CLOSE_MS (24h) — the chain's outermost resolution deadline, so the user always sees a hard upper bound on the wait.
vvictor-dev
pushed a commit
to vvictor-dev/widgets
that referenced
this pull request
Aug 22, 2026
…status tiers (p2pdotme#6) * fix(modal): pipe themeStyle through portal so card respects host palette Modal portals to document.body, escaping the calling widget's root where `themeToCssVars(theme)` is applied. The card's `background: var(--p2p-color-bg, #ffffff)` fell back to white, while inner content (which reaches the host's CSS vars via the inner themeStyle div) renders text in near-white — producing white-on-white text on dark integrators. Each Modal caller now passes its `themeStyle` to Modal, which spreads it on the backdrop. CSS cascade carries the vars to the card so the surface matches the integrator's palette. Also pins `color: color.text` on the card so descendants without an explicit color inherit a readable token. Verified on merchant-app demo: dialog bg now resolves to integrator dark surface, text near-white — proper contrast restored. * fix(contact-support): always render the button so users always have a recourse Previous behavior only rendered the Contact Support chip when the order was inside the dispute window or after a dispute was filed. PLACED / ACCEPTED / PAID / COMPLETED states showed status text with NO action — leaving users with no way to contact support when an order was stuck (e.g. PAID waiting on a merchant-confirm-completion watcher that hasn't fired). Now every row renders a Contact Support button. Variant depends on state: - In dispute window → chip with countdown, click opens report flow - Dispute open / resolved → plain button with dot, click opens chat - Everything else → plain button, click opens chat (or surfaces "Support not available yet" if the order isn't bound to an inbox yet) End users always have a way to reach support, even when no on-chain dispute path is currently valid. * Revert "fix(contact-support): always render the button so users always have a recourse" This reverts commit f57f0bf. * fix(contact-support): in-window chip is always clickable, falls back to chat when no txSigner Previous behavior: the in-window report chip rendered with disabled={!txSigner}. When the embedder didn't wire txSigner (or it was somehow undefined), the chip appeared visibly greyed out and the HTML disabled attribute blocked the click — leaving the user with a visible but non-functional button. Now: the chip is always clickable. Click target depends on txSigner: - txSigner present → opens the on-chain report flow (raiseDispute) - txSigner missing → opens chat (will surface "Support not available yet" if the bridge has no inbox bound, but the user is never left without a response on click) * fix(order-action): surface stale-PLACED + processing-payment countdown Two related smart-action machine improvements: 1. **PLACED past 5min → "still matching, taking longer than usual"** — the chain's status field stays PLACED until an `autoCancelExpiredOrders` keeper sweep runs, which lags the actual accept-window expiry. The widget now surfaces the staleness so users don't keep staring at "Placed · matching" forever when no merchant can pick the order up anymore. 2. **BUY/PAID renders a countdown** — "Paid · processing payment · completes within <X>m" while inside the 30-min processing window (matches contract's placedAt + orderExpiry envelope). Past the window we fall back to the plain "Paid · processing payment" label. Constants exported: - `PLACED_STALE_THRESHOLD_MS = 5 * MIN` - `BUY_PAID_PROCESSING_WINDOW_MS = 30 * MIN` 71 node:test cases pass, 55 vitest pass, typecheck + build clean. * fix(order-action): three-tier BUY/PAID status (no countdown in middle band) Revise the BUY/PAID label tiers per UX spec: < 5min → "Paid · processing payment" 5–30min → "Paid · processing payment · taking longer than usual" ≥ 30min → "Paid · processing payment · will resolve within <countdown>" The middle tier is a gentle warning without a countdown (drops the noise of a constantly-changing timer for orders that are merely a few minutes overdue). The outer tier anchors the countdown on BUY_DISPUTE_CLOSE_MS (24h) — the chain's outermost resolution deadline, so the user always sees a hard upper bound on the wait. --------- Co-authored-by: gitchadd <gitchad@icloud.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up fixes that landed after PR #5 was squash-merged. All on
feat/v1-a11y-vocab-pass.Summary
fix(modal): pipethemeStylethrough the portal so the card respects the host palette — fixes white-on-white contrast when the integrator uses a dark theme. The portal escapes the widget root wherethemeToCssVars(theme)lives, so previously the card fell back to--p2p-color-bg = #ffffffwhile the inner text used the host's near-white--p2p-color-fg.fix(contact-support): in-window chip is always clickable; falls back to the chat flow when notxSigneris wired (instead of rendering disabled / cursor-not-allowed). One revert pair in the history — the always-render direction was wrong; ship clickability without changing render conditions.fix(order-action): PLACED orders past 5 min surface"Placed · still matching, this is taking longer than usual"so users don't stare at stale state when the chain auto-cancel keeper lags.fix(order-action): three-tier BUY/PAID status —< 5 minplain"Paid · processing payment",5–30 mingentle"taking longer than usual"(no countdown),≥ 30 mincountdown anchored on the 24hBUY_DISPUTE_CLOSE_MSresolution deadline.Test plan
npm run typecheckcleannpm test55/55 vitestnode --test src/core/*.test.ts71/71npm run buildCJS + DTS clean🤖 Generated with Claude Code