Skip to content

feat: added quote metadata#7035

Open
fairlighteth wants to merge 11 commits intodevelopfrom
feat/quote-id-uidisplay
Open

feat: added quote metadata#7035
fairlighteth wants to merge 11 commits intodevelopfrom
feat/quote-id-uidisplay

Conversation

@fairlighteth
Copy link
Contributor

@fairlighteth fairlighteth commented Feb 18, 2026

Summary

This PR surfaces quote metadata already returned by the backend in the Swap widget UI, so users/support can inspect quote identity and verification state without DevTools.

It adds a new Quote ID row in expanded details and a compact verification signal in collapsed details, with correct swap-only behavior and error/no-quote guards.

Key changes:

  • Added Quote ID row to expanded details (TradeRateDetails) using a new RowQuoteId component.
  • Added short reference formatting for display: Q-XXXXXXXX (first 8 chars, uppercase, no #), while copy action uses full quote ID.
  • Added copy affordance with inline Copied! feedback.
  • Added info tooltip content with support guidance + live refresh countdown from quote expiration.
  • Added verification status UI:
    • Collapsed details: shield icon next to fee summary (QuoteVerificationIcon) in swap flow.
    • Expanded details: Verified / Unverified badge on Quote ID row (QuoteVerificationBadge).
  • Reused quote metadata from existing quote response:
    • quoteResults.quoteResponse.id
    • quoteResults.quoteResponse.verified
    • quoteResults.quoteResponse.expiration
  • Ensured row/icon are hidden when quote is missing or quote request is in error state.
  • Added swap+bridge support on the swap leg details (QuoteSwapContent / useQuoteSwapContext).
  • Added modules/accountProxy/index.ts public exports and switched call sites to avoid import/no-internal-modules warnings.

UI refinements from review feedback:

  • Switched shield icon to react-icons/bi (BiCheckShield).
  • Updated tooltip copy to factual wording: “verified by CoW Protocol simulation”.
  • Tightened icon alignment/spacing in both collapsed and expanded contexts.
  • Cropped icon dead space via custom viewBox override for better visual centering.

Screenshots:

Screen.Recording.2026-02-18.at.12.57.30.mov
Screen.Recording.2026-02-18.at.12.56.13.mov

To Test

  1. Swap widget: verified quote state
  • Open Swap and create a valid quote (e.g. ETH/WETH -> USDC with amount).
  • In collapsed details (rate + fee row), verify a shield icon appears next to fee.
  • Hover the shield and verify tooltip text: Quote verified by CoW Protocol simulation.
  • Expand details and verify a Quote ID row appears as the last row.
  • Verify displayed ID format is Q-XXXXXXXX (8 uppercase chars, no #).
  • Click copy icon and verify Copied! feedback appears.
  • Paste clipboard value and verify it is the full quote ID (not truncated).
  1. Unverified quote state
  • Reproduce a quote where verified=false (if available in env).
  • Verify collapsed shield is grey/muted.
  • Verify expanded badge shows Unverified (grey).
  • Hover icon/badge and verify tooltip text: Quote not yet verified by CoW Protocol simulation.
  1. Quote lifecycle / edge cases
  • Change sell amount/token and verify Quote ID + badge update with each new quote.
  • Hover Quote ID info icon and verify Refresh in: countdown updates live (no stale static value).
  • Clear input or force quote error/offline and verify collapsed shield and Quote ID row are hidden.
  • Confirm no regressions in fee/slippage/deadline rows.
  1. Swap + Bridge flow
  • Open Swap + Bridge quote details.
  • Verify Quote ID row (with badge + copy + tooltip) appears on the swap details section.
  • Verify behavior matches swap flow for refresh/copy/verified states.
  1. Validation commands
  • pnpm exec eslint apps/cowswap-frontend/src/modules/swap/containers/SwapRateDetails/index.tsx apps/cowswap-frontend/src/modules/tradeWidgetAddons/
    containers/RowQuoteId/index.tsx apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/QuoteVerificationIndicator.tsx apps/cowswap-
    frontend/src/modules/bridge/hooks/useQuoteSwapContext.ts apps/cowswap-frontend/src/modules/tradeFormValidation/hooks/useTradeFormValidationContext.ts apps/
    cowswap-frontend/src/modules/account/containers/CowShedInfo/index.tsx apps/cowswap-frontend/src/modules/accountProxy/index.ts
  • pnpm exec tsc -p apps/cowswap-frontend/tsconfig.app.json --noEmit
  • pnpm exec jest apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.test.ts --config apps/cowswap-frontend/jest.config.ts
    --watchman=false

Background

This is frontend-only. No backend/API changes were required.

The backend has long returned quote metadata on each quote response; we now surface it in UI:

  • id: support reference/correlation
  • verified: simulation-backed quote validity signal
  • expiration: quote refresh timing context

Data path:

  • Swap flow: useTradeQuote() -> SwapRateDetails -> TradeRateDetails -> RowQuoteId
  • Swap+Bridge swap leg: useTradeQuote() -> useQuoteSwapContext() -> QuoteSwapContent

This implementation intentionally treats missing verification as unverified and hides metadata when quote state is unavailable/error to avoid stale or misleading UI signals.

Summary by CodeRabbit

  • New Features
    • Quote verification badge/icon, Quote ID display with copy-to-clipboard, and expiration/refresh info surfaced in tooltips and rate/fee details.
  • Localization
    • Added/updated EN/ES/RU strings for quote verification, statuses, Quote ID, "Copied!" and related tooltips.
  • Refactor
    • Public module reorganizations and a new consolidated account-proxy entry point.
  • Tests
    • Unit tests for quote ID formatting and expiration labeling.

@vercel
Copy link

vercel bot commented Feb 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Feb 18, 2026 6:27pm
explorer-dev Ready Ready Preview Feb 18, 2026 6:27pm
swap-dev Ready Ready Preview Feb 18, 2026 6:27pm
widget-configurator Ready Ready Preview Feb 18, 2026 6:27pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Feb 18, 2026 6:27pm
sdk-tools Ignored Ignored Preview Feb 18, 2026 6:27pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

No actionable comments were generated in the recent review. 🎉


Walkthrough

Adds quote metadata (quoteId, quoteVerified, quoteExpiration) through bridge context and exposes UI: Quote ID display with copy, verification badge/icon, tooltip with refresh/expiration, helpers/tests, barrel exports, and locale entries; also minor import/barrel reorganizations.

Changes

Cohort / File(s) Summary
Quote ID utilities
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/quoteId.ts
New types and helpers to normalize/format quote IDs and compute human-readable expiration labels (getQuoteIdString, formatQuoteIdReference, getQuoteExpiresInLabel).
RowQuoteId UI & exports
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/RowQuoteId.container.tsx, .../RowQuoteId.styled.ts, .../index.tsx
New RowQuoteId and QuoteIdValue components, styled blocks, copy-to-clipboard UX (“Copied!”), and re-export.
RowQuoteId tests
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.test.ts
Unit tests for quote helpers: formatting, normalization, and expiration-label logic.
Tooltip & verification UI
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/QuoteIdTooltipContent.tsx, .../QuoteVerificationIndicator.tsx
New tooltip component showing expiration/refresh info and verification components (QuoteVerificationIcon, QuoteVerificationBadge) with localized copy.
Trade widget integration
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TradeRateDetails/index.tsx, apps/cowswap-frontend/src/modules/tradeWidgetAddons/index.ts
TradeRateDetails props extended (quoteId, quoteVerified, quoteExpiration); RowQuoteId and verification exports added to tradeWidgetAddons public API.
Bridge context & consumer
apps/cowswap-frontend/src/modules/bridge/types.ts, .../hooks/useQuoteSwapContext.ts, .../pure/contents/QuoteSwapContent/index.tsx
Added optional quote metadata fields to QuoteSwapContext; hook normalizes/memoizes quoteMeta (guarded by quoteError); QuoteSwapContent renders Quote ID block when present.
Swap UI updates
apps/cowswap-frontend/src/modules/swap/containers/SwapRateDetails/index.tsx
Shows verification icon beside fee when quote is present/verified; injects quote props into TradeRateDetails.
Barrel & imports
apps/cowswap-frontend/src/modules/accountProxy/index.ts, apps/cowswap-frontend/src/modules/account/containers/CowShedInfo/index.tsx, .../useTradeFormValidationContext.ts
New accountProxy barrel re-exports; imports updated to consume barrel.
Localization
apps/cowswap-frontend/src/locales/en-US.po, .../es-ES.po, .../ru-RU.po
Added and relocated translation keys for quote ID and verification UI (e.g., "Quote ID", "Copy full quote ID", "Copied!", "Quote verified by CoW Protocol simulation", "Verified", "Unverified") and updated public id paths to match moved modules.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

preview-widget-cfg, Bridge, Design

Suggested reviewers

  • elena-zh
  • shoom3301
  • alfetopito

Poem

🐇 I found a Q‑ID in a hop and a bound,
A tiny badge that says the quote is sound,
I copied, I shared, with a whisker‑quick skip,
The expiry ticks down on my fluffy tip,
Hooray — a little trace, a rabbit‑happy round! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: added quote metadata' directly corresponds to the main objective of surfacing backend quote metadata in the Swap widget UI. It accurately summarizes the primary change.
Description check ✅ Passed The PR description follows the template structure with Summary, To Test, and Background sections. It provides detailed explanations of changes, testing steps, validation commands, and includes screenshots as suggested by the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/quote-id-uidisplay

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (8)
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TradeRateDetails/index.tsx (1)

33-35: Consider reusing QuoteIdInput / QuoteExpirationInput type aliases instead of inline types.

The types string | number | null and string | null duplicate the domain aliases already defined in quoteId.ts.

♻️ Proposed type reuse
+import { type QuoteExpirationInput, type QuoteIdInput } from '../RowQuoteId/quoteId'

 interface TradeRateDetailsProps {
   ...
-  quoteId?: string | number | null
-  quoteVerified?: boolean | null
-  quoteExpiration?: string | null
+  quoteId?: QuoteIdInput
+  quoteVerified?: boolean | null
+  quoteExpiration?: QuoteExpirationInput
   ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TradeRateDetails/index.tsx`
around lines 33 - 35, Replace the inline union types for quoteId and
quoteExpiration with the existing aliases: import and use QuoteIdInput for
quoteId (instead of string | number | null) and QuoteExpirationInput for
quoteExpiration (instead of string | null) in the TradeRateDetails component;
update the props/type declaration referencing quoteId?: QuoteIdInput and
quoteExpiration?: QuoteExpirationInput and ensure the import of these types from
the module that defines them (e.g., quoteId.ts) is added at the top of the file.
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/QuoteVerificationIndicator.tsx (1)

15-59: CompactIconWrapper duplicates styles already applied by CompactIconTooltip > div.

Both CompactIconTooltip > div (lines 16–21) and CompactIconWrapper (lines 53–59) apply display: inline-flex, align-items: center, justify-content: center, and line-height: 0. Only vertical-align: middle is unique to CompactIconWrapper. Consider keeping only the wrapper and removing the redundant targeting on > div, or eliminating the wrapper and moving vertical-align: middle into the > div selector.

♻️ Consolidate styles into a single layer
 const CompactIconTooltip = styled(HoverTooltip)`
   > div {
-    display: inline-flex;
-    align-items: center;
-    justify-content: center;
-    line-height: 0;
+    vertical-align: middle;
   }
 `

Then update CompactIconWrapper to keep the full flex layout (or vice-versa).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/QuoteVerificationIndicator.tsx`
around lines 15 - 59, The styles for centering are duplicated: remove the
redundant rules from CompactIconTooltip's "> div" selector and keep them on
CompactIconWrapper; specifically delete display:inline-flex, align-items:center,
justify-content:center and line-height:0 from CompactIconTooltip > div, and
ensure CompactIconWrapper (the styled span) retains display:inline-flex,
align-items:center, justify-content:center, line-height:0 and
vertical-align:middle so all icon instances use CompactIconWrapper for layout.
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.test.ts (1)

35-39: getQuoteExpiresInLabel test suite is missing coverage for the hours and exact-minutes branches.

The following reachable code paths in quoteId.ts have no tests:

  • hours > 0"{h}h {m}m" (e.g., "1h 5m")
  • seconds === 0"{m}m" (exact minutes, e.g., "3m")
  • Already-expired quote → "0s"
✅ Proposed additional test cases
+    it('formats remaining hours', () => {
+      const now = Date.parse('2026-02-18T12:00:00.000Z')
+      expect(getQuoteExpiresInLabel('2026-02-18T13:05:00.000Z', now)).toBe('1h 5m')
+    })
+
+    it('formats exact minutes with no seconds', () => {
+      const now = Date.parse('2026-02-18T12:00:00.000Z')
+      expect(getQuoteExpiresInLabel('2026-02-18T12:03:00.000Z', now)).toBe('3m')
+    })
+
+    it('returns 0s for an already-expired quote', () => {
+      const now = Date.parse('2026-02-18T12:01:00.000Z')
+      expect(getQuoteExpiresInLabel('2026-02-18T12:00:00.000Z', now)).toBe('0s')
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.test.ts`
around lines 35 - 39, Add new unit tests to
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.test.ts
that call getQuoteExpiresInLabel with inputs exercising the missing branches:
(1) an expiry >1 hour to assert the "Hh Mm" format (e.g., expect "1h 5m" for a
timestamp 1h5m in the future), (2) an expiry with seconds===0 to assert exact
minute formatting (e.g., expect "3m" for exactly 3 minutes), and (3) an
already-expired timestamp (expiry <= now) to assert it returns "0s"; use the
existing pattern of passing a fixed now value like the other tests to keep them
deterministic.
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/quoteId.ts (1)

33-33: Hours branch silently drops seconds — document the intentional truncation.

"1h 5m 30s" is displayed as "1h 5m" once hours are present. This is a reasonable design simplification for long countdowns, but it is undocumented and untested (see test file note below).

♻️ Add a brief comment
-  if (hours > 0) return `${hours}h ${minutes}m`
+  // Seconds precision is intentionally omitted for hour-scale countdowns
+  if (hours > 0) return `${hours}h ${minutes}m`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/quoteId.ts`
at line 33, Add a brief inline comment above the hours branch that states
seconds are intentionally omitted when hours > 0 (i.e., the line "if (hours > 0)
return `${hours}h ${minutes}m`") so the truncation is documented and clear to
reviewers and tests; keep the implementation unchanged, just document the design
choice mentioning "seconds intentionally dropped for long countdowns" and link
it to the hours/minutes branch.
apps/cowswap-frontend/src/modules/swap/containers/SwapRateDetails/index.tsx (1)

41-44: Duplicates quote-metadata derivation already handled by normalizeQuoteMeta.

Lines 41–44 manually re-implement the same quoteError → quoteResponse → id/verified/expiration derivation that normalizeQuoteMeta in useQuoteSwapContext.ts already encapsulates. Extracting a shared useQuoteMetadata() hook would remove this duplication and keep normalization (including String(id)) in one place.

♻️ Sketch of a shared hook
// modules/tradeQuote/hooks/useQuoteMetadata.ts
export function useQuoteMetadata() {
  const { quote, error: quoteError } = useTradeQuote()
  const quoteResponse = quoteError ? undefined : quote?.quoteResults.quoteResponse
  return {
    quoteId: quoteResponse?.id != null ? String(quoteResponse.id) : undefined,
    quoteVerified: !!quoteResponse?.verified,
    quoteExpiration: quoteResponse?.expiration ?? null,
    showVerificationIcon: !!quoteResponse,
  }
}

Both SwapRateDetails and useQuoteSwapContext can then call useQuoteMetadata().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cowswap-frontend/src/modules/swap/containers/SwapRateDetails/index.tsx`
around lines 41 - 44, SwapRateDetails duplicates the quote metadata derivation
already handled by normalizeQuoteMeta in useQuoteSwapContext; extract a shared
hook (e.g., useQuoteMetadata) that centralizes quote, quoteError → quoteResponse
mapping and normalization (ensure id is cast via String(id) when present,
quoteVerified boolean, quoteExpiration defaulting to null, and a
showVerificationIcon flag), then replace the manual derivation in
SwapRateDetails and call useQuoteMetadata from useQuoteSwapContext so both
consume the single source of truth (refer to SwapRateDetails,
useQuoteSwapContext, and normalizeQuoteMeta when implementing).
apps/cowswap-frontend/src/modules/bridge/hooks/useQuoteSwapContext.ts (1)

44-48: Prefer primitive memo deps over the full quote object.

quoteMeta only reads three leaf primitives (id, verified, expiration) from quote?.quoteResults.quoteResponse. Using the whole quote object as a dep means every poll cycle (when tradeQuotes atom updates the quote reference) recreates quoteMeta even when those three fields haven't changed, cascading unnecessarily into the outer useMemo.

♻️ Proposed refactor
+  const quoteResponse = quoteError ? undefined : quote?.quoteResults.quoteResponse
   const quoteMeta = useMemo(() => {
-    const quoteResponse = quoteError ? undefined : quote?.quoteResults.quoteResponse
-    return normalizeQuoteMeta(quoteResponse?.id, quoteResponse?.verified, quoteResponse?.expiration)
-  }, [quote, quoteError])
+    return normalizeQuoteMeta(quoteResponse?.id, quoteResponse?.verified, quoteResponse?.expiration)
+  }, [quoteResponse?.id, quoteResponse?.verified, quoteResponse?.expiration])

As per coding guidelines: "Derive primitive memo deps (e.g., balance strings, amount hashes) instead of spreading whole objects/arrays into dependency lists."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cowswap-frontend/src/modules/bridge/hooks/useQuoteSwapContext.ts` around
lines 44 - 48, The memo is depending on the whole `quote` object causing
needless recomputations; change the dependency list of the `useMemo` that
computes `quoteMeta` to use the three primitive fields it actually reads instead
of `quote`—capture `const id = quote?.quoteResults.quoteResponse?.id`, `const
verified = quote?.quoteResults.quoteResponse?.verified`, and `const expiration =
quote?.quoteResults.quoteResponse?.expiration` (or derive them inline) and then
call `normalizeQuoteMeta(id, verified, expiration)` with dependencies `[id,
verified, expiration, quoteError]`; keep the `useMemo` and `normalizeQuoteMeta`
usage unchanged.
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.tsx (2)

55-84: Extract 1500 as a named constant.

The clipboard timeout 1500 is a magic number. Hoisting it makes intent clear and centralises any future tweak.

♻️ Proposed refactor
+const COPY_FEEDBACK_DURATION_MS = 1500
+
 export function QuoteIdValue({ quoteId }: QuoteIdValueProps): ReactNode {
   const { t } = useLingui()
-  const [isCopied, setCopied] = useCopyClipboard(1500)
+  const [isCopied, setCopied] = useCopyClipboard(COPY_FEEDBACK_DURATION_MS)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.tsx`
around lines 55 - 84, Extract the magic number 1500 used in useCopyClipboard
inside QuoteIdValue into a clearly named constant (e.g.,
COPY_FEEDBACK_DURATION_MS) at the top of the module, then replace the literal in
the useCopyClipboard call with that constant; update any related references in
QuoteIdValue (and ensure handleCopy / setCopied behavior remains unchanged) so
the timeout value is centralized and self-documenting.

86-91: expiration? is redundant — QuoteExpirationInput already includes undefined.

QuoteExpirationInput is typed as string | null | undefined, so the ? modifier adds no new information and creates a subtle inconsistency with the explicit union.

♻️ Proposed refactor
 interface RowQuoteIdProps {
   quoteId: QuoteIdInput
   isVerified?: boolean | null
-  expiration?: QuoteExpirationInput
+  expiration: QuoteExpirationInput
   styleProps?: RowStyleProps
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.tsx`
around lines 86 - 91, The expiration property on the RowQuoteIdProps interface
is declared as optional (expiration?) but QuoteExpirationInput is already string
| null | undefined, so remove the optional modifier and declare it as
expiration: QuoteExpirationInput to avoid redundancy and keep typings
consistent; update the RowQuoteIdProps interface (and any places constructing
RowQuoteIdProps if needed) to use expiration: QuoteExpirationInput instead of
expiration?: QuoteExpirationInput.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/cowswap-frontend/src/locales/es-ES.po`:
- Around line 162-165: Several msgstr entries in the ES PO file are empty (e.g.,
the msgid "You don't have any orders at the moment." referenced from
OrdersTableNoOrdersContent.utils.tsx); update each empty msgstr to the correct
Spanish translation (or copy the English text deliberately if intended) for that
msgid and likewise fill the other empty msgstr entries listed (lines: 483-485,
794-796, 1252-1254, 1518-1521, 1782-1785, 1876-1879, 2307-2310, 2717-2721,
2994-2997, 3154-3156, 3335-3338, 3564-3567, 3768-3771, 3923-3926, 4295-4298,
4442-4445, 4662-4665, 4873-4876, 4965-4968, 5137-5141, 6658-6661) ensuring each
msgid’s Spanish translation matches the UI context (use the same
phrasing/terminology as the rest of the locale); save the file and run any
lint/po validation to confirm no empty msgstr remain.

In `@apps/cowswap-frontend/src/locales/ru-RU.po`:
- Around line 162-165: The ru-RU .po contains empty msgstr values for new
empty-state strings (e.g. msgid "You don't have any orders at the moment."
referenced from OrdersTableNoOrdersContent.utils.tsx) causing fallback to
English; update the corresponding msgstr for that msgid with the proper Russian
translation (or copy the English text as a temporary placeholder) and likewise
fill in the other empty msgstr entries noted in the review (the ranges listed)
so all new empty-state msgid strings in
apps/cowswap-frontend/src/locales/ru-RU.po have non-empty Russian translations.
- Around line 5376-5379: The msgstr for the msgid "Confirm Swap and Bridge" in
apps/cowswap-frontend/src/locales/ru-RU.po is still English; update the msgstr
value for that msgid to the Russian translation (e.g., "Подтвердить обмен и
перевод через мост") so the locale file returns a proper ru-RU string for
useGetConfirmButtonLabel.

In `@apps/cowswap-frontend/src/modules/swap/containers/SwapRateDetails/index.tsx`:
- Line 45: The expression for showQuoteVerificationIcon is redundant because
quoteResponse is undefined whenever quoteError is truthy; replace the current
compound guard with a single existence check by using only quoteResponse (i.e.,
set showQuoteVerificationIcon based on !!quoteResponse) so the unnecessary &&
!quoteError term is removed; update the declaration of showQuoteVerificationIcon
and run tests to confirm no behavioral changes.

---

Nitpick comments:
In `@apps/cowswap-frontend/src/modules/bridge/hooks/useQuoteSwapContext.ts`:
- Around line 44-48: The memo is depending on the whole `quote` object causing
needless recomputations; change the dependency list of the `useMemo` that
computes `quoteMeta` to use the three primitive fields it actually reads instead
of `quote`—capture `const id = quote?.quoteResults.quoteResponse?.id`, `const
verified = quote?.quoteResults.quoteResponse?.verified`, and `const expiration =
quote?.quoteResults.quoteResponse?.expiration` (or derive them inline) and then
call `normalizeQuoteMeta(id, verified, expiration)` with dependencies `[id,
verified, expiration, quoteError]`; keep the `useMemo` and `normalizeQuoteMeta`
usage unchanged.

In `@apps/cowswap-frontend/src/modules/swap/containers/SwapRateDetails/index.tsx`:
- Around line 41-44: SwapRateDetails duplicates the quote metadata derivation
already handled by normalizeQuoteMeta in useQuoteSwapContext; extract a shared
hook (e.g., useQuoteMetadata) that centralizes quote, quoteError → quoteResponse
mapping and normalization (ensure id is cast via String(id) when present,
quoteVerified boolean, quoteExpiration defaulting to null, and a
showVerificationIcon flag), then replace the manual derivation in
SwapRateDetails and call useQuoteMetadata from useQuoteSwapContext so both
consume the single source of truth (refer to SwapRateDetails,
useQuoteSwapContext, and normalizeQuoteMeta when implementing).

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.test.ts`:
- Around line 35-39: Add new unit tests to
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.test.ts
that call getQuoteExpiresInLabel with inputs exercising the missing branches:
(1) an expiry >1 hour to assert the "Hh Mm" format (e.g., expect "1h 5m" for a
timestamp 1h5m in the future), (2) an expiry with seconds===0 to assert exact
minute formatting (e.g., expect "3m" for exactly 3 minutes), and (3) an
already-expired timestamp (expiry <= now) to assert it returns "0s"; use the
existing pattern of passing a fixed now value like the other tests to keep them
deterministic.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/index.tsx`:
- Around line 55-84: Extract the magic number 1500 used in useCopyClipboard
inside QuoteIdValue into a clearly named constant (e.g.,
COPY_FEEDBACK_DURATION_MS) at the top of the module, then replace the literal in
the useCopyClipboard call with that constant; update any related references in
QuoteIdValue (and ensure handleCopy / setCopied behavior remains unchanged) so
the timeout value is centralized and self-documenting.
- Around line 86-91: The expiration property on the RowQuoteIdProps interface is
declared as optional (expiration?) but QuoteExpirationInput is already string |
null | undefined, so remove the optional modifier and declare it as expiration:
QuoteExpirationInput to avoid redundancy and keep typings consistent; update the
RowQuoteIdProps interface (and any places constructing RowQuoteIdProps if
needed) to use expiration: QuoteExpirationInput instead of expiration?:
QuoteExpirationInput.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/quoteId.ts`:
- Line 33: Add a brief inline comment above the hours branch that states seconds
are intentionally omitted when hours > 0 (i.e., the line "if (hours > 0) return
`${hours}h ${minutes}m`") so the truncation is documented and clear to reviewers
and tests; keep the implementation unchanged, just document the design choice
mentioning "seconds intentionally dropped for long countdowns" and link it to
the hours/minutes branch.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/QuoteVerificationIndicator.tsx`:
- Around line 15-59: The styles for centering are duplicated: remove the
redundant rules from CompactIconTooltip's "> div" selector and keep them on
CompactIconWrapper; specifically delete display:inline-flex, align-items:center,
justify-content:center and line-height:0 from CompactIconTooltip > div, and
ensure CompactIconWrapper (the styled span) retains display:inline-flex,
align-items:center, justify-content:center, line-height:0 and
vertical-align:middle so all icon instances use CompactIconWrapper for layout.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/TradeRateDetails/index.tsx`:
- Around line 33-35: Replace the inline union types for quoteId and
quoteExpiration with the existing aliases: import and use QuoteIdInput for
quoteId (instead of string | number | null) and QuoteExpirationInput for
quoteExpiration (instead of string | null) in the TradeRateDetails component;
update the props/type declaration referencing quoteId?: QuoteIdInput and
quoteExpiration?: QuoteExpirationInput and ensure the import of these types from
the module that defines them (e.g., quoteId.ts) is added at the top of the file.

Comment on lines +162 to 165
#: apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTable/Content/NoOrders/OrdersTableNoOrdersContent.utils.tsx
msgid "You don't have any orders at the moment."
msgstr ""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fill missing Spanish translations for new order/quote strings.
Several newly added entries still have empty msgstr values, so they’ll render in English/blank in the ES locale. Please translate these or explicitly copy the English text if that’s intended.

Also applies to: 483-485, 794-796, 1252-1254, 1518-1521, 1782-1785, 1876-1879, 2307-2310, 2717-2721, 2994-2997, 3154-3156, 3335-3338, 3564-3567, 3768-3771, 3923-3926, 4295-4298, 4442-4445, 4662-4665, 4873-4876, 4965-4968, 5137-5141, 6658-6661

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cowswap-frontend/src/locales/es-ES.po` around lines 162 - 165, Several
msgstr entries in the ES PO file are empty (e.g., the msgid "You don't have any
orders at the moment." referenced from OrdersTableNoOrdersContent.utils.tsx);
update each empty msgstr to the correct Spanish translation (or copy the English
text deliberately if intended) for that msgid and likewise fill the other empty
msgstr entries listed (lines: 483-485, 794-796, 1252-1254, 1518-1521, 1782-1785,
1876-1879, 2307-2310, 2717-2721, 2994-2997, 3154-3156, 3335-3338, 3564-3567,
3768-3771, 3923-3926, 4295-4298, 4442-4445, 4662-4665, 4873-4876, 4965-4968,
5137-5141, 6658-6661) ensuring each msgid’s Spanish translation matches the UI
context (use the same phrasing/terminology as the rest of the locale); save the
file and run any lint/po validation to confirm no empty msgstr remain.

Comment on lines +162 to 165
#: apps/cowswap-frontend/src/modules/ordersTable/pure/OrdersTable/Content/NoOrders/OrdersTableNoOrdersContent.utils.tsx
msgid "You don't have any orders at the moment."
msgstr ""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing Russian translations for new empty‑state strings.

These msgstr entries are empty, so ru-RU will fall back to English/blank. Please provide translations (or copy the English msgid if you want a temporary fallback).

Also applies to: 483-485, 794-796, 1252-1254, 1518-1520, 1782-1784, 1876-1878, 2307-2310, 3155-3156, 3335-3338, 3564-3567, 3768-3771, 3923-3925, 3963-3965, 4151-4153, 4295-4297, 4442-4444, 4662-4665, 4873-4876, 4905-4907, 4965-4967, 6658-6661

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cowswap-frontend/src/locales/ru-RU.po` around lines 162 - 165, The ru-RU
.po contains empty msgstr values for new empty-state strings (e.g. msgid "You
don't have any orders at the moment." referenced from
OrdersTableNoOrdersContent.utils.tsx) causing fallback to English; update the
corresponding msgstr for that msgid with the proper Russian translation (or copy
the English text as a temporary placeholder) and likewise fill in the other
empty msgstr entries noted in the review (the ranges listed) so all new
empty-state msgid strings in apps/cowswap-frontend/src/locales/ru-RU.po have
non-empty Russian translations.

Comment on lines +5376 to 5379
#: apps/cowswap-frontend/src/modules/trade/hooks/useGetConfirmButtonLabel.ts
msgid "Confirm Swap and Bridge"
msgstr "Confirm Swap and Bridge"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Localize “Confirm Swap and Bridge”.

msgstr is still English; please translate it for ru-RU consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cowswap-frontend/src/locales/ru-RU.po` around lines 5376 - 5379, The
msgstr for the msgid "Confirm Swap and Bridge" in
apps/cowswap-frontend/src/locales/ru-RU.po is still English; update the msgstr
value for that msgid to the Russian translation (e.g., "Подтвердить обмен и
перевод через мост") so the locale file returns a proper ru-RU string for
useGetConfirmButtonLabel.

@Danziger
Copy link
Contributor

@fairlighteth I would show a shield icon without a check mark for non-verified quotes. Something like this:
image

@Danziger thanks for reviewing. I had considered it, but find those icons too alarming, as if something is wrong with the quote, which I think is the wrong signal. I settled on a 'grey' verified badge as a contrast to the 'green' verified badge. Would want more signal in case this is undesired.

Out of curiosity, what does Matcha show when the quote is not verified?

The thing with the check is that, specially for new users that haven't seen/noticed there's 2 versions, it looks as good as the green one.

In any case, the specific icons are just suggestions. There's also an empty shield icon which might be less alarming.

@fairlighteth
Copy link
Contributor Author

@fairlighteth I would show a shield icon without a check mark for non-verified quotes. Something like this:
image

@Danziger thanks for reviewing. I had considered it, but find those icons too alarming, as if something is wrong with the quote, which I think is the wrong signal. I settled on a 'grey' verified badge as a contrast to the 'green' verified badge. Would want more signal in case this is undesired.

Out of curiosity, what does Matcha show when the quote is not verified?

The thing with the check is that, specially for new users that haven't seen/noticed there's 2 versions, it looks as good as the green one.

In any case, the specific icons are just suggestions. There's also an empty shield icon which might be less alarming.

Did some experimentation and now use an outlined shield when it's verified:
Screenshot 2026-02-18 at 14 10 55
Screenshot 2026-02-18 at 14 10 49

pushed in 55b0c9e

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/QuoteIdTooltipContent.tsx (1)

38-40: expiration prop is used only as a boolean gate — consider narrowing the API.

The component ignores the actual expiration string; only its truthiness (expiration && isQuoteUpdatePossible) is tested. Passing QuoteExpirationInput (a full string union) when only presence matters is slightly over-specified. Using hasExpiration?: boolean would make the intent explicit and reduce coupling to the upstream quote type.

♻️ Optional simplification
 interface QuoteIdTooltipContentProps {
-  expiration?: QuoteExpirationInput
+  hasExpiration?: boolean
 }

-export function QuoteIdTooltipContent({ expiration }: QuoteIdTooltipContentProps): ReactNode {
+export function QuoteIdTooltipContent({ hasExpiration }: QuoteIdTooltipContentProps): ReactNode {
   ...
-  {expiration && isQuoteUpdatePossible ? (
+  {hasExpiration && isQuoteUpdatePossible ? (

Callsites would pass hasExpiration={!!expiration}.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/QuoteIdTooltipContent.tsx`
around lines 38 - 40, The prop expiration on QuoteIdTooltipContentProps is only
used as a boolean gate (checked via expiration && isQuoteUpdatePossible), so
change the prop to hasExpiration?: boolean to make the intent explicit: update
the QuoteIdTooltipContentProps interface, rename usages of the prop inside the
QuoteIdTooltipContent component to hasExpiration, and update callsites to pass
hasExpiration={!!expiration}; ensure logic that used "expiration &&
isQuoteUpdatePossible" now uses "hasExpiration && isQuoteUpdatePossible".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/cowswap-frontend/src/modules/swap/containers/SwapRateDetails/index.tsx`:
- Around line 22-32: The child selector in FeeSummaryWithVerification currently
targets any direct div child and may unintentionally style feeElement if its
root is a div; update the JSX to wrap QuoteVerificationIcon in a dedicated
wrapper element (e.g., a span/div with a specific className or data attribute)
and change the styled-component rule to target that wrapper (e.g.,
.quoteVerificationWrapper) instead of the generic `> div`, ensuring feeElement
is not affected while keeping the icon layout consistent.

---

Nitpick comments:
In
`@apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/RowQuoteId/QuoteIdTooltipContent.tsx`:
- Around line 38-40: The prop expiration on QuoteIdTooltipContentProps is only
used as a boolean gate (checked via expiration && isQuoteUpdatePossible), so
change the prop to hasExpiration?: boolean to make the intent explicit: update
the QuoteIdTooltipContentProps interface, rename usages of the prop inside the
QuoteIdTooltipContent component to hasExpiration, and update callsites to pass
hasExpiration={!!expiration}; ensure logic that used "expiration &&
isQuoteUpdatePossible" now uses "hasExpiration && isQuoteUpdatePossible".

@elena-zh
Copy link
Contributor

Hey @fairlighteth , nice!

Some discrepancies:

  1. Qupteid field is present on the Swap and bridge confirm modal while it is absent on the Confirm swap modal:
image image
  1. WDYT about moving the quoteID to the top on the confirm modal, so it will not be lost among among calculations/amounts there?
image
  1. Should we add the same field to the TWAP page form and Confirm modal?
  2. Lastly, can we add this qupteiD into the Explorer appdata? I know, it should be addressed in another task, just asking about the possibility ;)
image

Thanks!

@fairlighteth
Copy link
Contributor Author

fairlighteth commented Feb 18, 2026

@elena-zh thank you for reviewing

  1. Fixed
  2. I think showing the Quote ID at the bottom chronologically makes more sense. Moving it to the top gives it too much of a priority IMO.
  3. Would keep out of scope (similar to LIMIT in a way)
  4. This seems to have implications to the hashed appData. Let's keep out of scope.

Copy link
Contributor

@elena-zh elena-zh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you

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.

4 participants

Comments