Skip to content

fix(widgets): modal contrast, contact-support clickability, BUY/PAID status tiers - #6

Merged
gitchadd merged 6 commits into
mainfrom
feat/v1-a11y-vocab-pass
May 15, 2026
Merged

gitchadd merged 6 commits into
mainfrom
feat/v1-a11y-vocab-pass

Conversation

@gitchadd

Copy link
Copy Markdown
Collaborator

Follow-up fixes that landed after PR #5 was squash-merged. All on feat/v1-a11y-vocab-pass.

Summary

  • fix(modal): pipe themeStyle through 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 where themeToCssVars(theme) lives, so previously the card fell back to --p2p-color-bg = #ffffff while 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 no txSigner is 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 min plain "Paid · processing payment", 5–30 min gentle "taking longer than usual" (no countdown), ≥ 30 min countdown anchored on the 24h BUY_DISPUTE_CLOSE_MS resolution deadline.

Test plan

  • npm run typecheck clean
  • npm test 55/55 vitest
  • node --test src/core/*.test.ts 71/71
  • npm run build CJS + DTS clean
  • Verified live on demo merchant-app: dark-theme modal contrast restored, stale-PLACED label on day-old test orders, no console errors

🤖 Generated with Claude Code

gitchadd 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.
…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.
@gitchadd
gitchadd merged commit f66e826 into main May 15, 2026
0 of 2 checks passed
@gitchadd
gitchadd deleted the feat/v1-a11y-vocab-pass branch May 15, 2026 13:19
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant