UX: Bridge button stays enabled and receive quote still displays across invalid form states #119

Closed
opened 2026-04-24 05:54:34 +00:00 by Brouie · 12 comments
Brouie commented 2026-04-24 05:54:34 +00:00 (Migrated from gitlab.com)

Found during Bridge #116 E2E walkthrough — grouped findings for form validation and CTA-enable state.

Summary

The 'BRIDGE ...' CTA button remains visually active and clickable across multiple invalid form states. Additionally, 'YOU WILL RECEIVE' continues to calculate and display values during invalid states, creating a misleading impression that the transfer is ready to submit.

Findings

1. CTA stays active across invalid states

Observed in live mainnet walkthrough on https://bridge.cl8y.com/ (v0.1.82, 8efb987):

  • Amount exceeds balance → error 'Amount exceeds the maximum (X)' + CTA still active
  • Amount below minimum → error 'Amount is below the minimum transfer amount (X)' + CTA still active
  • Recipient invalid address (wrong format / no 0x prefix) → red outline + 'Invalid address' + CTA still active
  • Recipient too-short address → 'Invalid address' + CTA still active
  • Recipient empty (placeholder only) → no error + CTA still active

The CTA 'disabled' state appears absent or visually identical to enabled across all these paths.

2. YOU WILL RECEIVE calculates during invalid states

  • Over-balance amount → receive still computed and displayed (e.g. 3 → '2.985 TESTA')
  • Under-minimum amount → receive still computed (1 → '0.995 TESTA')
  • Invalid recipient → receive still computed
  • Zero/negative amount → receive correctly zeros (inconsistent vs above paths)

3. MAX button produces over-limit error

  • Click MAX → populates full balance (e.g. 2.063202)
  • Inline error fires: 'Amount exceeds the maximum (2.063 tokena-cb)'
  • Clicking MAX should always produce a valid amount. Likely a rounding mismatch between the displayed balance, MAX button value, and validation threshold (fee-deduction math?).

4. Over-precision decimal input unclear visual state

  • Paste a value with more decimals than the token supports (e.g. 1.847848572097299727878253263654 for 18-decimal token)
  • Field truncates with trailing chars highlighted/selected as if overflow
  • No error message, no clear feedback to user about what was accepted vs dropped

5. MIN button value fails HTML5 step validation

  • MIN minimum value is e.g. 0.10050251256281407 (17 decimals)
  • Click MIN → input populated with the minimum value
  • Browser tooltip fires: 'Please enter a valid value. The two nearest valid values are 0.100502 and 0.100503'
  • The input's step attribute is too coarse for its own MIN value — form validates its own MIN value as invalid.

Why this matters

Users can click Bridge with invalid amounts/recipients and:

  • Waste wallet signing + gas attempting a transfer that the contract will revert
  • In the case of invalid recipient, silent-accept of format-valid typos (see launch-blocker #117) combined with always-enabled CTA could compound user loss risk

Proposed fix scope

  • Gate CTA enable state on aggregate form validity (all fields valid + non-empty)
  • Zero out (or dim) YOU WILL RECEIVE during any invalid-input state
  • Recompute MAX to subtract bridge fee and validation-round-trip (MAX result must pass validation)
  • Constrain HTML5 step attribute to token's actual decimals, or drop it and rely on JS validation
  • Add explicit feedback when input exceeds token decimals (round / truncate / reject with message)

Screenshots

Attached via web UI — states covered: over-balance, under-min, invalid EVM address, short EVM address, empty recipient, MAX-overshoot, over-precision input, MIN+step-conflict.

cc @PlasticDigits

Found during Bridge #116 E2E walkthrough — grouped findings for form validation and CTA-enable state. ## Summary The 'BRIDGE ...' CTA button remains visually active and clickable across multiple invalid form states. Additionally, 'YOU WILL RECEIVE' continues to calculate and display values during invalid states, creating a misleading impression that the transfer is ready to submit. ## Findings ### 1. CTA stays active across invalid states Observed in live mainnet walkthrough on https://bridge.cl8y.com/ (v0.1.82, 8efb987): - Amount exceeds balance → error 'Amount exceeds the maximum (X)' + CTA still active - Amount below minimum → error 'Amount is below the minimum transfer amount (X)' + CTA still active - Recipient invalid address (wrong format / no 0x prefix) → red outline + 'Invalid address' + CTA still active - Recipient too-short address → 'Invalid address' + CTA still active - Recipient empty (placeholder only) → no error + CTA still active The CTA 'disabled' state appears absent or visually identical to enabled across all these paths. ### 2. YOU WILL RECEIVE calculates during invalid states - Over-balance amount → receive still computed and displayed (e.g. 3 → '2.985 TESTA') - Under-minimum amount → receive still computed (1 → '0.995 TESTA') - Invalid recipient → receive still computed - Zero/negative amount → receive correctly zeros (inconsistent vs above paths) ### 3. MAX button produces over-limit error - Click MAX → populates full balance (e.g. 2.063202) - Inline error fires: 'Amount exceeds the maximum (2.063 tokena-cb)' - Clicking MAX should always produce a valid amount. Likely a rounding mismatch between the displayed balance, MAX button value, and validation threshold (fee-deduction math?). ### 4. Over-precision decimal input unclear visual state - Paste a value with more decimals than the token supports (e.g. 1.847848572097299727878253263654 for 18-decimal token) - Field truncates with trailing chars highlighted/selected as if overflow - No error message, no clear feedback to user about what was accepted vs dropped ### 5. MIN button value fails HTML5 step validation - MIN minimum value is e.g. 0.10050251256281407 (17 decimals) - Click MIN → input populated with the minimum value - Browser tooltip fires: 'Please enter a valid value. The two nearest valid values are 0.100502 and 0.100503' - The input's `step` attribute is too coarse for its own MIN value — form validates its own MIN value as invalid. ## Why this matters Users can click Bridge with invalid amounts/recipients and: - Waste wallet signing + gas attempting a transfer that the contract will revert - In the case of invalid recipient, silent-accept of format-valid typos (see launch-blocker #117) combined with always-enabled CTA could compound user loss risk ## Proposed fix scope - Gate CTA enable state on aggregate form validity (all fields valid + non-empty) - Zero out (or dim) YOU WILL RECEIVE during any invalid-input state - Recompute MAX to subtract bridge fee and validation-round-trip (MAX result must pass validation) - Constrain HTML5 `step` attribute to token's actual decimals, or drop it and rely on JS validation - Add explicit feedback when input exceeds token decimals (round / truncate / reject with message) ## Screenshots Attached via web UI — states covered: over-balance, under-min, invalid EVM address, short EVM address, empty recipient, MAX-overshoot, over-precision input, MIN+step-conflict. cc @PlasticDigits
Brouie commented 2026-04-24 06:04:24 +00:00 (Migrated from gitlab.com)

image.png{width=640 height=593}

![image.png](/uploads/c71369b9baae8288eec078e40f989e10/image.png){width=640 height=593}
Brouie commented 2026-04-24 06:06:18 +00:00 (Migrated from gitlab.com)

mentioned in issue #116

mentioned in issue #116
PlasticDigits commented 2026-04-24 06:12:24 +00:00 (Migrated from gitlab.com)

mentioned in commit 11cb57a4de

mentioned in commit 11cb57a4de3ad5dbfd8e4b2bac62516cb86cb18b
PlasticDigits commented 2026-04-24 06:12:24 +00:00 (Migrated from gitlab.com)

mentioned in commit 8e0c040ee2

mentioned in commit 8e0c040ee2e186401fbc9ae7c7441de2c9db9d86
PlasticDigits commented 2026-04-24 06:12:49 +00:00 (Migrated from gitlab.com)

GL-119 fix merged to main (8e0c040)

@brouie Please verify on staging / production when convenient — issue left open per request.

What changed

  • Explicit recipient: Bridge CTA and submit guards use recipient.trim() only. Empty field + connected wallet no longer enables submit; users must type an address or Autofill.
  • Receive quote: You will receive shows a net estimate only when recipient + amount pass the same aggregate checks (valid gross, within min/max, route valid). Otherwise the row shows — (muted).
  • MAX: Gross string is formatted at full token precision and clamped in base units so parsed amount never exceeds balance / bridge caps (fixes display-rounding overshoot).
  • Amount step: step=\any\ on the amount input so HTML5 stepping does not reject token-accurate MIN values.
  • Precision: Inline message when the user enters more fractional digits than the source token supports (extra digits are ignored in parseAmount).

Verification checklist

  • Empty recipient + connected wallet: CTA disabled; tooltip asks to enter address or autofill.
  • After Autofill, CTA follows amount min/max and route validity as before.
  • Invalid / short / bad-checksum recipient: CTA disabled; You will receive stays — (not a computed net).
  • Amount above max or below min: CTA disabled; You will receive stays —.
  • MAX with full balance: no "exceeds maximum" error; parsed amount ≤ balance / caps.
  • MIN: no browser step validation popup on the amount field.
  • Paste amount with more decimals than token: rose hint about ignored digits; transfer still uses floored value.
  • npm run test:unit in packages/frontend passes (610 tests on the merge machine).

Thanks!

## GL-119 fix merged to `main` (8e0c040) @brouie Please verify on staging / production when convenient — issue left **open** per request. ### What changed - **Explicit recipient**: Bridge CTA and submit guards use `recipient.trim()` only. Empty field + connected wallet no longer enables submit; users must type an address or **Autofill**. - **Receive quote**: **You will receive** shows a net estimate only when recipient + amount pass the same aggregate checks (valid gross, within min/max, route valid). Otherwise the row shows **—** (muted). - **MAX**: Gross string is formatted at full token precision and clamped in base units so parsed amount never exceeds balance / bridge caps (fixes display-rounding overshoot). - **Amount `step`**: `step=\any\` on the amount input so HTML5 stepping does not reject token-accurate MIN values. - **Precision**: Inline message when the user enters more fractional digits than the source token supports (extra digits are ignored in `parseAmount`). ### Docs / agent cross-links - **INV-UX1** in [`docs/FRONTEND_BRIDGE_INVARIANTS.md`](https://gitlab.com/PlasticDigits/cl8y-bridge-monorepo/-/blob/main/docs/FRONTEND_BRIDGE_INVARIANTS.md) (merged with GL-118 cross-links). - [`skills/agent-bridge-recipient-validation.md`](https://gitlab.com/PlasticDigits/cl8y-bridge-monorepo/-/blob/main/skills/agent-bridge-recipient-validation.md) updated for INV-UX1 + GL-119. - Helpers: `packages/frontend/src/utils/amountInputLimits.ts`; tests in `amountInputLimits.test.ts` + `TransferForm.test.tsx`. ### Verification checklist - [ ] Empty recipient + connected wallet: CTA disabled; tooltip asks to enter address or autofill. - [ ] After **Autofill**, CTA follows amount min/max and route validity as before. - [ ] Invalid / short / bad-checksum recipient: CTA disabled; **You will receive** stays **—** (not a computed net). - [ ] Amount above max or below min: CTA disabled; **You will receive** stays **—**. - [ ] **MAX** with full balance: no "exceeds maximum" error; parsed amount ≤ balance / caps. - [ ] **MIN**: no browser `step` validation popup on the amount field. - [ ] Paste amount with more decimals than token: rose hint about ignored digits; transfer still uses floored value. - [ ] `npm run test:unit` in `packages/frontend` passes (610 tests on the merge machine). Thanks!
Brouie commented 2026-04-24 06:22:05 +00:00 (Migrated from gitlab.com)

@PlasticDigits verified on the live fix — 4 of 5 findings confirmed resolved:

  • ✅ Over-balance: amount '11' with balance 10.96 → 'Amount exceeds the maximum' fires, YOU WILL RECEIVE shows '— TESTA' (no longer misleading calc), CTA 'BRIDGE FROM EVM' visually disabled.
  • ✅ Under-min: amount '1' below 1.005... → 'Amount is below the minimum transfer amount' fires, receive zeroed, CTA disabled.
  • ✅ MAX button: click MAX populates 10.963201982 (smart deduction below balance 10.963202), no over-limit error, receive computes cleanly to 10.908386 TESTA, CTA active. The rounding mismatch is gone.
  • ✅ Invalid recipient: red outline + 'Invalid address' + improved helper text ('check for typos — bech32 checksum' on Terra side, 'check EIP-55 checksum if using mixed case' on EVM side), receive zeroed, CTA disabled.

Bonus: the chain-specific helper text on invalid recipients is a strong touch — users get actionable guidance instead of generic error.

Remaining items from the original filing:

  • Over-precision decimal input visual clarity (not retested this round — lower priority, UX polish)
  • MIN button HTML5 step attribute conflict (not retested this round — minor)

Closing as substantially verified. The core CTA-stays-enabled-across-invalid-states concern is resolved — the form now behaves as a coherent gated flow. If you want the two remaining minor items addressed, happy to file them as a separate minor-UX follow-up, otherwise this can close.

@PlasticDigits verified on the live fix — 4 of 5 findings confirmed resolved: - ✅ **Over-balance:** amount '11' with balance 10.96 → 'Amount exceeds the maximum' fires, YOU WILL RECEIVE shows '— TESTA' (no longer misleading calc), CTA 'BRIDGE FROM EVM' visually disabled. - ✅ **Under-min:** amount '1' below 1.005... → 'Amount is below the minimum transfer amount' fires, receive zeroed, CTA disabled. - ✅ **MAX button:** click MAX populates 10.963201982 (smart deduction below balance 10.963202), no over-limit error, receive computes cleanly to 10.908386 TESTA, CTA active. The rounding mismatch is gone. - ✅ **Invalid recipient:** red outline + 'Invalid address' + improved helper text ('check for typos — bech32 checksum' on Terra side, 'check EIP-55 checksum if using mixed case' on EVM side), receive zeroed, CTA disabled. Bonus: the chain-specific helper text on invalid recipients is a strong touch — users get actionable guidance instead of generic error. Remaining items from the original filing: - Over-precision decimal input visual clarity (not retested this round — lower priority, UX polish) - MIN button HTML5 step attribute conflict (not retested this round — minor) Closing as substantially verified. The core CTA-stays-enabled-across-invalid-states concern is resolved — the form now behaves as a coherent gated flow. If you want the two remaining minor items addressed, happy to file them as a separate minor-UX follow-up, otherwise this can close.
PlasticDigits commented 2026-04-24 06:50:05 +00:00 (Migrated from gitlab.com)

mentioned in commit 417b6bd208

mentioned in commit 417b6bd208c63f449df4897f4ac3ffe06e0b0388
PlasticDigits commented 2026-04-24 06:50:05 +00:00 (Migrated from gitlab.com)

mentioned in commit 496f371494

mentioned in commit 496f371494ca02e501eafb66ca995cb0629f62bf
PlasticDigits commented 2026-04-24 06:50:24 +00:00 (Migrated from gitlab.com)

GL-119 follow-up merged to main (417b6bd)

@brouie Please verify when convenient — issue left open per workflow.

Focus (latest comments)

  • Over-precision decimal input: Amount is now type="text" + inputMode=decimal. When fractional digits exceed the token, the field gets an amber ring, aria-invalid, a Transfer uses <exact floored value> SYMBOL — floored to token precision line (via formatBaseUnitsAsExactDecimalString), plus the existing rose copy about ignored digits.
  • MIN vs HTML5 step: Native number validation is no longer used for the amount field, so the browser should not show "Please enter a valid value. The two nearest valid values are …" when MIN fills a long exact decimal.

Docs / invariants

  • INV-UX1 updated in docs/FRONTEND_BRIDGE_INVARIANTS.md (text amount field + precision line).
  • skills/agent-bridge-recipient-validation.md cross-linked to INV-UX1 / GL-119.
  • Code: AmountInput.tsx, TransferForm.tsx, amountInputLimits.ts; tests: TransferForm.test.tsx, amountInputLimits.test.ts.

Checklist for verification

  • MIN: Click MIN with a long minimum; no HTML5 nearest valid value popup on the amount field.
  • Excess precision: Paste or type more fractional digits than the token allows; see amber field outline, Transfer uses … line with full floored amount, and rose explanation.
  • Keyboard mobile: inputMode=decimal still opens a numeric-style keyboard where supported.
  • npm run test:unit in packages/frontend — 612 tests green on the merge box.

Thanks!

## GL-119 follow-up merged to `main` (417b6bd) @brouie Please verify when convenient — **issue left open** per workflow. ### Focus (latest comments) - **Over-precision decimal input:** Amount is now `type="text"` + `inputMode=decimal`. When fractional digits exceed the token, the field gets an amber ring, `aria-invalid`, a **Transfer uses &lt;exact floored value&gt; SYMBOL — floored to token precision** line (via `formatBaseUnitsAsExactDecimalString`), plus the existing rose copy about ignored digits. - **MIN vs HTML5 step:** Native `number` validation is no longer used for the amount field, so the browser should not show *"Please enter a valid value. The two nearest valid values are …"* when MIN fills a long exact decimal. ### Docs / invariants - **INV-UX1** updated in `docs/FRONTEND_BRIDGE_INVARIANTS.md` (text amount field + precision line). - `skills/agent-bridge-recipient-validation.md` cross-linked to INV-UX1 / GL-119. - Code: `AmountInput.tsx`, `TransferForm.tsx`, `amountInputLimits.ts`; tests: `TransferForm.test.tsx`, `amountInputLimits.test.ts`. ### Checklist for verification - [ ] **MIN:** Click MIN with a long minimum; no HTML5 *nearest valid value* popup on the amount field. - [ ] **Excess precision:** Paste or type more fractional digits than the token allows; see amber field outline, *Transfer uses …* line with full floored amount, and rose explanation. - [ ] **Keyboard mobile:** `inputMode=decimal` still opens a numeric-style keyboard where supported. - [ ] `npm run test:unit` in `packages/frontend` — 612 tests green on the merge box. Thanks!
Brouie commented 2026-04-26 04:35:53 +00:00 (Migrated from gitlab.com)

@PlasticDigits closing on 417b6bd. The two minor items I left as polish on the previous round are now addressed.

AmountInput.tsx walkthrough:

  • L113-114: type='text' inputMode='decimal' (kills HTML5 number validation, preserves mobile numeric keyboard)
  • L122: aria-invalid={excessFractionDigits || undefined}
  • L128: amber ring border-amber-400/80 ring-2 ring-amber-500/30 on excess precision
  • L174-182: 'Transfer uses ...' line renders when excessFractionDigits true, with the floored amount span via formatBaseUnitsAsExactDecimalString

formatBaseUnitsAsExactDecimalString helper at amountInputLimits.ts:37, imported at TransferForm.tsx:76, used at TransferForm.tsx:1899.

Docs:

  • docs/FRONTEND_BRIDGE_INVARIANTS.md L5: 'INV-UX1 - Transfer form: CTA, receive quote, and amount field (GL-119)'
  • skills/agent-bridge-recipient-validation.md L3, L7, L26: cross-linked to INV-UX1 / GL-119 with the text + inputMode=decimal note

Tests: cd packages/frontend && npm run test:unit -> 618/618 passing (you cited 612, we have 618 - small drift from address.test.ts that landed alongside cb7f871 for #117).

Mobile inputMode=decimal numeric-keyboard behavior is the W3C-standard attribute, deferred to the next live mobile session for a visual confirmation but no concern at the source level.

Closing.

@PlasticDigits closing on 417b6bd. The two minor items I left as polish on the previous round are now addressed. AmountInput.tsx walkthrough: - L113-114: type='text' inputMode='decimal' (kills HTML5 number validation, preserves mobile numeric keyboard) - L122: aria-invalid={excessFractionDigits || undefined} - L128: amber ring border-amber-400/80 ring-2 ring-amber-500/30 on excess precision - L174-182: 'Transfer uses ...' line renders when excessFractionDigits true, with the floored amount span via formatBaseUnitsAsExactDecimalString formatBaseUnitsAsExactDecimalString helper at amountInputLimits.ts:37, imported at TransferForm.tsx:76, used at TransferForm.tsx:1899. Docs: - docs/FRONTEND_BRIDGE_INVARIANTS.md L5: 'INV-UX1 - Transfer form: CTA, receive quote, and amount field (GL-119)' - skills/agent-bridge-recipient-validation.md L3, L7, L26: cross-linked to INV-UX1 / GL-119 with the text + inputMode=decimal note Tests: cd packages/frontend && npm run test:unit -> 618/618 passing (you cited 612, we have 618 - small drift from address.test.ts that landed alongside cb7f871 for #117). Mobile inputMode=decimal numeric-keyboard behavior is the W3C-standard attribute, deferred to the next live mobile session for a visual confirmation but no concern at the source level. Closing.
Brouie (Migrated from gitlab.com) closed this issue 2026-04-26 04:35:54 +00:00
PlasticDigits commented 2026-05-19 08:10:26 +00:00 (Migrated from gitlab.com)

mentioned in issue #132

mentioned in issue #132
PlasticDigits commented 2026-08-11 03:53:03 +00:00 (Migrated from gitlab.com)

mentioned in issue #134

mentioned in issue #134
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-bridge-monorepo#119
No description provided.