OE-3 UI: Limit Ladder- Rung count input snaps to minimum on backspace; cannot type a new value from scratch #295

Closed
opened 2026-06-03 16:10:50 +00:00 by totdking · 13 comments
totdking commented 2026-06-03 16:10:50 +00:00 (Migrated from gitlab.com)
No description provided.
totdking commented 2026-06-03 16:35:29 +00:00 (Migrated from gitlab.com)

Summary:

The rung count input in the limit ladder panel has an unusable editing experience. The default is 5. Backspacing to clear the field immediately snaps the value to 2 (the minimum), and the input cannot be cleared or retyped. Any value above the contract maximum (20) is silently clamped to 20 with no feedback. The user cannot freely type a new rung count.


Reproduction steps

  1. Navigate to /limits or /trade/:pairAddr and open the Limit Ladder section
  2. Observe: rung count field defaults to 5
  3. Click the rung count field and press Backspace to clear the 5
  4. Observe: field immediately snaps to 2 instead of allowing an empty field
  5. Try to backspace the 2 — it does not clear
  6. Try to type 3, 10, or any value less than 20 — the field does not accept it cleanly
  7. Type any value above 20 (e.g. 25) — field is clamped to 20 with no message

Expected behavior

  • The rung count field should allow the user to clear it and type any integer between 2 and the contract max (20)
  • Clamping to min/max should happen on blur or submit, not on every keystroke
  • If the value is out of range at submit, a validation message should surface
  • Typing 3 should result in 3, not 2

Actual behavior

  • Clearing the field snaps immediately to 2 on every keystroke
  • No intermediate empty state is possible — the user cannot delete the existing value to type a new one
  • Values above the max are silently clamped to 20

Verified root cause (code read and confirmed)

LimitOrderLadderPanel.tsx line 231:

onChange={(e) => setRungCount(Math.min(maxRungs, Math.max(2, Number(e.target.value) || 2)))}

The onChange handler coerces and clamps on every keystroke:

  1. When the user backspaces 5, e.target.value becomes "" (empty string)
  2. Number("") === 0, so Number(e.target.value) || 2 evaluates to 2
  3. Math.max(2, 2) → 2, Math.min(20, 2) → 2 — state is set to 2 immediately
  4. React re-renders with value={rungCount} = 2, overwriting the input
  5. The user now sees 2 and cannot clear it — every backspace triggers the same snap

Values above maxRungs (20) hit Math.min(maxRungs, ...) and are silently capped with no user message.

Relevant state:

  • Default: useState(5) at line 54
  • maxRungs: configQuery.data?.max_batch_rungs ?? 20 at line 61 (fetched from contract config)
  • Input element: type="number", min={2}, max={maxRungs}, data-testid="ladder-rung-count" at lines 226–232

Fix direction: Decouple the input's display value from the numeric state. Use a local string state (e.g. rungCountInput) for the controlled input value, and only parse/clamp into rungCount on blur or when the value is a valid integer within range. This is the standard pattern for numeric inputs that need mid-edit empty states.


Impact assessment

  • User-facing: Users cannot reliably set a custom rung count. The field is effectively locked to 2 (minimum), 5 (default), or 20 (maximum) via the stepper arrows — free typing is broken.
  • OE-3 result: The ladder can still be placed with 5 rungs (default), so the placement mechanic itself is untested. Rung count configurability fails.

Environment

  • Chain: localterra
  • LCD: http://localhost:1317
  • Wallet: Keplr (Terra Classic)
  • Browser: Chrome
  • Page: /limits Limit Ladder section
  • Network throttle applied: No

Severity: ~bug : the core ladder placement flow still works at the default rung count, but the rung count field is functionally broken for any non-default value. Affects configurability, not availability.

Related checklist items: OE-3

cc: @PlasticDigits

## **Summary:** The rung count input in the limit ladder panel has an unusable editing experience. The default is 5. Backspacing to clear the field immediately snaps the value to 2 (the minimum), and the input cannot be cleared or retyped. Any value above the contract maximum (20) is silently clamped to 20 with no feedback. The user cannot freely type a new rung count. --- ### Reproduction steps 1. Navigate to `/limits` or `/trade/:pairAddr` and open the Limit Ladder section 2. Observe: rung count field defaults to `5` 3. Click the rung count field and press Backspace to clear the `5` 4. Observe: field immediately snaps to `2` instead of allowing an empty field 5. Try to backspace the `2` — it does not clear 6. Try to type `3`, `10`, or any value less than 20 — the field does not accept it cleanly 7. Type any value above `20` (e.g. `25`) — field is clamped to `20` with no message --- ### Expected behavior - The rung count field should allow the user to clear it and type any integer between 2 and the contract max (20) - Clamping to min/max should happen on blur or submit, not on every keystroke - If the value is out of range at submit, a validation message should surface - Typing `3` should result in `3`, not `2` --- ### Actual behavior - Clearing the field snaps immediately to `2` on every keystroke - No intermediate empty state is possible — the user cannot delete the existing value to type a new one - Values above the max are silently clamped to 20 --- ### Verified root cause (code read and confirmed) `LimitOrderLadderPanel.tsx` line 231: ```js onChange={(e) => setRungCount(Math.min(maxRungs, Math.max(2, Number(e.target.value) || 2)))} ``` The `onChange` handler coerces and clamps on every keystroke: 1. When the user backspaces `5`, `e.target.value` becomes `""` (empty string) 2. `Number("") === 0`, so `Number(e.target.value) || 2` evaluates to `2` 3. `Math.max(2, 2)` → `2`, `Math.min(20, 2)` → `2` — state is set to `2` immediately 4. React re-renders with `value={rungCount}` = `2`, overwriting the input 5. The user now sees `2` and cannot clear it — every backspace triggers the same snap Values above `maxRungs` (20) hit `Math.min(maxRungs, ...)` and are silently capped with no user message. **Relevant state:** - Default: `useState(5)` at line 54 - `maxRungs`: `configQuery.data?.max_batch_rungs ?? 20` at line 61 (fetched from contract config) - Input element: `type="number"`, `min={2}`, `max={maxRungs}`, `data-testid="ladder-rung-count"` at lines 226–232 **Fix direction:** Decouple the input's display value from the numeric state. Use a local string state (e.g. `rungCountInput`) for the controlled input value, and only parse/clamp into `rungCount` on blur or when the value is a valid integer within range. This is the standard pattern for numeric inputs that need mid-edit empty states. --- ### Impact assessment - **User-facing:** Users cannot reliably set a custom rung count. The field is effectively locked to 2 (minimum), 5 (default), or 20 (maximum) via the stepper arrows — free typing is broken. - **OE-3 result:** The ladder can still be placed with 5 rungs (default), so the placement mechanic itself is untested. Rung count configurability fails. --- ### Environment - Chain: localterra - LCD: [http://localhost:1317](http://localhost:1317) - Wallet: Keplr (Terra Classic) - Browser: Chrome - Page: `/limits` Limit Ladder section - Network throttle applied: No --- **Severity:** ~bug : the core ladder placement flow still works at the default rung count, but the rung count field is functionally broken for any non-default value. Affects configurability, not availability. **Related checklist items:** OE-3 cc: @PlasticDigits
totdking commented 2026-06-03 17:28:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #291

mentioned in issue #291
Brouie commented 2026-06-04 07:08:51 +00:00 (Migrated from gitlab.com)

Confirmed against current source (frontend-only, your layer to fix/verify in browser). LimitOrderLadderPanel.tsx:231 clamps on every keystroke: setRungCount(Math.min(maxRungs, Math.max(2, Number(e.target.value) || 2))) on a controlled value={rungCount} input — so backspacing to empty makes Number("")===0 → 2, and React immediately re-renders 2, blocking a clear/retype. Standard fix: decouple a local string input state from the numeric rungCount, parse+clamp only on blur/submit (allow an intermediate empty value mid-edit). Real, low — placement still works at the default 5. @totdking

Confirmed against current source (frontend-only, your layer to fix/verify in browser). `LimitOrderLadderPanel.tsx:231` clamps on every keystroke: `setRungCount(Math.min(maxRungs, Math.max(2, Number(e.target.value) || 2)))` on a controlled `value={rungCount}` input — so backspacing to empty makes `Number("")===0 → 2`, and React immediately re-renders `2`, blocking a clear/retype. Standard fix: decouple a local string input state from the numeric `rungCount`, parse+clamp only on blur/submit (allow an intermediate empty value mid-edit). Real, low — placement still works at the default 5. @totdking
Brouie commented 2026-06-05 03:21:48 +00:00 (Migrated from gitlab.com)

mentioned in merge request !757

mentioned in merge request !757
Brouie commented 2026-06-05 03:23:12 +00:00 (Migrated from gitlab.com)

Fixed in MR !757 (bundled the four ladder-panel fixes — they're one component). For #295: decoupled the rung-count input from the numeric state — a raw string draft drives the field so backspacing no longer snaps to 2, and it clamps/normalizes on blur. Over-max now shows an inline message instead of silently capping. The numeric rungCount stays the source of truth for the preview/plan/gas/mutation. Frontend-only — the browser walkthrough (backspace to empty, type 3, type 25 to trigger the message, blur to clamp) is yours. @PlasticDigits

Fixed in MR !757 (bundled the four ladder-panel fixes — they're one component). For #295: decoupled the rung-count input from the numeric state — a raw string draft drives the field so backspacing no longer snaps to 2, and it clamps/normalizes on blur. Over-max now shows an inline message instead of silently capping. The numeric rungCount stays the source of truth for the preview/plan/gas/mutation. Frontend-only — the browser walkthrough (backspace to empty, type 3, type 25 to trigger the message, blur to clamp) is yours. @PlasticDigits
PlasticDigits commented 2026-06-05 03:24:32 +00:00 (Migrated from gitlab.com)

mentioned in commit 6e42a5b6f6

mentioned in commit 6e42a5b6f6a8a5b91735b5ad60d68168450254b0
PlasticDigits commented 2026-06-05 04:10:22 +00:00 (Migrated from gitlab.com)

Verification — GitLab #295 (OE-3: limit ladder rung count input)

Verified on main @ 9f0babe (includes MR !757 / 6e42a5b).

Acceptance criteria

Item Result How verified
Fix merged: decoupled string draft (rungCountInput), parse on valid keystrokes, clamp on blur PASS LimitOrderLadderPanel.tsx — handleRungCountChange, handleRungCountBlur, rungCountError; old onChange clamp removed
Backspace clears field without snapping to 2 PASS Ephemeral vitest + @testing-library/user-event: clear() → value === '', no error banner
Type 3 → field shows 3 (not 2) PASS Same test: type('3') → value === '3'
Type 25 → inline message (not silent cap while typing) PASS Same test: error ladder-rung-count-error contains “at most 20”
Blur clamps out-of-range to max PASS Same test: blur after 25 → value === '20', error cleared
Blur on empty restores last valid count PASS Same test: blur after clear → restores 5
Old root-cause pattern gone PASS rg 'Number(e.target.value) || 2' — no matches in frontend-dapp
make lint-frontend PASS 0 errors (6 pre-existing warnings)
make test-frontend PASS 136 files / 822 tests
Chrome walkthrough on /limits (human QA note in MR !757) SKIP No frontend-dapp/.env.local / LocalTerra stack running in this session; UI behavior covered by Testing Library user-event reproduction above

Summary

The reported bug is fixed on main. MR !757 landed the string-draft + blur-clamp pattern and inline over-max validation. No additional repo changes were required for this verification pass.

Closing as verified.

## Verification — GitLab #295 (OE-3: limit ladder rung count input) Verified on `main` @ `9f0babe` (includes MR !757 / `6e42a5b`). ### Acceptance criteria | Item | Result | How verified | |------|--------|----------------| | Fix merged: decoupled string draft (`rungCountInput`), parse on valid keystrokes, clamp on blur | **PASS** | `LimitOrderLadderPanel.tsx` — `handleRungCountChange`, `handleRungCountBlur`, `rungCountError`; old `onChange` clamp removed | | Backspace clears field without snapping to `2` | **PASS** | Ephemeral vitest + `@testing-library/user-event`: `clear()` → `value === ''`, no error banner | | Type `3` → field shows `3` (not `2`) | **PASS** | Same test: `type('3')` → `value === '3'` | | Type `25` → inline message (not silent cap while typing) | **PASS** | Same test: error `ladder-rung-count-error` contains “at most 20” | | Blur clamps out-of-range to max | **PASS** | Same test: blur after `25` → `value === '20'`, error cleared | | Blur on empty restores last valid count | **PASS** | Same test: blur after clear → restores `5` | | Old root-cause pattern gone | **PASS** | `rg 'Number(e.target.value) \|\| 2'` — no matches in `frontend-dapp` | | `make lint-frontend` | **PASS** | 0 errors (6 pre-existing warnings) | | `make test-frontend` | **PASS** | 136 files / 822 tests | | Chrome walkthrough on `/limits` (human QA note in MR !757) | **SKIP** | No `frontend-dapp/.env.local` / LocalTerra stack running in this session; UI behavior covered by Testing Library user-event reproduction above | ### Summary The reported bug is **fixed on `main`**. MR !757 landed the string-draft + blur-clamp pattern and inline over-max validation. No additional repo changes were required for this verification pass. Closing as verified.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-05 04:10:27 +00:00
ghost1 commented 2026-06-05 05:24:02 +00:00 (Migrated from gitlab.com)

mentioned in commit b4432f658f

mentioned in commit b4432f658f0b8a3b83ba1057298a0d2acce4d185
PlasticDigits commented 2026-06-05 05:39:47 +00:00 (Migrated from gitlab.com)

mentioned in merge request !769

mentioned in merge request !769
ghost1 commented 2026-06-05 07:13:17 +00:00 (Migrated from gitlab.com)

mentioned in commit 0f2d3c2d34

mentioned in commit 0f2d3c2d34c478a3f108beb1b32dd11c34d6472f
PlasticDigits commented 2026-06-05 10:00:07 +00:00 (Migrated from gitlab.com)

mentioned in commit 816fec5d92

mentioned in commit 816fec5d925dd8401056472a1cea5adf5df010b5
PlasticDigits commented 2026-06-07 12:14:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

mentioned in issue #337
PlasticDigits commented 2026-06-16 15:58:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #385

mentioned in issue #385
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-dex-terraclassic#295
No description provided.