Submit snapshot: debounce hybrid book leg, extend stale assert, consolidate buildSubmitAlignedSimPayload (#356 follow-up) #360

Closed
opened 2026-06-11 10:28:42 +00:00 by PlasticDigits · 16 comments
PlasticDigits commented 2026-06-11 10:28:42 +00:00 (Migrated from gitlab.com)

Summary

Follow-up to #356. Pay-amount submit alignment landed via useSubmitAlignedSimQuote, isSubmitQuoteStale (incl. isFetching), and assertSubmitQuotePayRawAligned in Swap / Trade market mutations. Hybrid book-leg inputs are still live while pay is debounced, and the in-mutation assert does not cover them. buildSubmitAlignedSimPayload is exported and tested but unused in production — the hook duplicates its minReceived bundling inline.

Current codebase

What #356 already fixed (pay amount)

Remaining gap — hybrid book leg (bookInputHuman, hybridMaxMakers)

Input Sim query key Sim queryFn hybrid split Submit / mutation hybrid split In isSubmitQuoteStale / assert
Pay amount debouncedRawInputAmount debounced simRaw submitPayRaw (debounced) Yes
Book leg (bookInputHuman) live live bookInputHuman live bookInputHuman (Swap direct hybrid branch ~L654; Trade uses debouncedHybrid with live book) No
hybridMaxMakers live live live No

Swap (SwapPage.tsx):

  • simQueryKey includes live bookInputHuman and hybridMaxMakers (~L345–374).
  • Advanced direct hybrid quote path (~L427–472) and pool-only disclosure split (~L520–527) read live bookInputHuman against debounced simRaw.
  • swapMutation direct hybrid fallback (~L652–667) recomputes bookRaw from live bookInputHuman + debounced payRaw — not snapshotted with the sim that produced minReceived / idxOps.
  • directHybridBookSplit display (~L756–766) uses live rawInputAmount + live bookInputHuman.

Trade market (TradeMarketOrderPanel.tsx):

  • simQueryKey includes live bookInputHuman (~L228–238).
  • debouncedHybrid = computeHybridParams(debouncedRawInputAmount, …, bookInputHuman, …) — debounced pay + live book (~L159–161).
  • Submit uses debouncedHybrid (~L375) but stale gate / assert do not verify book leg or max makers match the settled sim key.
  • liveHybrid / gas estimate still uses live pay + live book (~L154–166) — intentional for UX hints, but must not be the submit snapshot.

Dead / duplicated helper

  • buildSubmitAlignedSimPayload (~L40–52) bundles { payRaw, minReceived, simData, indexerOperations } but is only imported in quoteDebounce.test.ts.
  • useSubmitAlignedSimQuote recomputes minReceived via applySlippagePercentFloor instead of calling buildSubmitAlignedSimPayload — two sources of truth for the same snapshot shape.

Why this is needed

  1. Defense in depth: assertSubmitQuotePayRawAligned only guards pay raw. A programmatic or race bypass of the UI stale gate could submit a different book split than the quote used for minReceived / hybrid min_return — same class of slippage / revert risk as the original #356 pay-amount skew.
  2. Incomplete stale detection: When pay amount is stable (rawInputAmount === debouncedRawInputAmount) but the user edits the book leg or max makers, submit may stay enabled while keepPreviousData shows a quote for the previous hybrid params (only indirectly blocked if isFetching happens to be true).
  3. #346 parity: Pay amount is debounced to limit sim churn; book leg is not — every book keystroke invalidates the sim key and refetches, undermining the debounce win on hybrid flows.
  4. Maintainability: Either wire buildSubmitAlignedSimPayload into the hook (single snapshot builder) or remove the dead export to avoid drift between test-only and production paths.

Constraints and guardrails

  • Do not regress #356 pay-amount alignment or #346 debounce behavior for the main pay field.
  • Hybrid book leg debounce should use the same SIM_QUOTE_DEBOUNCE_MS (350) unless product explicitly wants a different constant — document if changed.
  • getDirectHybridBookSplit / computeHybridParams / swapDisclosure helpers stay pure; extend or wrap them rather than duplicating split math in components.
  • Trade market submit must continue to use debouncedHybrid for on-chain hybrid params (already partially correct) — extend snapshot, don't revert to live pay.
  • liveHybrid may remain for gas / max-spend UX; only submit and quote display when submit-ready must use the snapshotted hybrid params.
  • Preserve isSimQuoteStaleForSubmit deprecated alias until call sites are migrated.
  • Update skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md and docs/frontend.md #356 sections when behavior changes.

Relevant files

Area Path
Submit snapshot hook frontend-dapp/src/hooks/useSubmitAlignedSimQuote.ts, .test.ts
Stale / assert / payload helpers frontend-dapp/src/utils/quoteDebounce.ts, .test.ts
Debounce frontend-dapp/src/hooks/useDebouncedValue.ts
Swap frontend-dapp/src/pages/SwapPage.tsx, SwapPage.test.tsx
Trade market frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx
Hybrid split pure helpers frontend-dapp/src/utils/swapDisclosure.ts, .test.ts
Docs / skills docs/frontend.md, skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md
  1. Extend submit snapshot — add debounced bookInputHuman (and optionally debounced hybridMaxMakers or treat max-makers as immediate with explicit stale on change):
    • const debouncedBookInputHuman = useDebouncedValue(bookInputHuman, SIM_QUOTE_DEBOUNCE_MS)
    • Include debounced book (+ max makers) in simQueryKey / queryFn instead of live values.
  2. Extend stale detection — widen isSubmitQuoteStale (or add isHybridParamsStale) to return true when live book ≠ debounced book or live max makers ≠ snapshotted max makers (mirror pay-amount pattern).
  3. Extend mutation assert — e.g. assertSubmitHybridAligned({ bookLive, bookDebounced, maxMakersLive, maxMakersSnap }) called alongside assertSubmitQuotePayRawAligned; Swap direct hybrid branch must read book split from snapshot, not closure bookInputHuman.
  4. Consolidate payload builder — either:
    • (A) Have useSubmitAlignedSimQuote call buildSubmitAlignedSimPayload for minReceived + expose full submitPayload, or
    • (B) Move snapshot bundling into the hook and delete buildSubmitAlignedSimPayload + SubmitAlignedSimPayload type from quoteDebounce.ts (update tests to target the hook).
    • Prefer (A) if the type is useful for mutationFn; prefer (B) if the hook is the sole consumer.
  5. Optional: expose submitHybrid: HybridSwapParams | undefined from the hook so Swap and Trade mutations stop recomputing splits inline.

Acceptance criteria

  1. When submit is allowed, pay raw, book leg raw, max makers, minReceived, indexerOperations, and on-chain hybrid params all derive from the same settled sim key (debounced pay + debounced book [+ snapshotted max makers]).
  2. isSubmitReady is false while live book leg ≠ debounced book leg (or while hybrid params are otherwise stale), even if pay raw matches debounced pay.
  3. mutationFn asserts hybrid alignment before broadcast (defense in depth); mismatch throws the same class of user-facing error as pay misalignment.
  4. Swap advanced direct hybrid quote path and mutation use the same debounced book input as the sim key — no live bookInputHuman in submit path.
  5. buildSubmitAlignedSimPayload is either used by useSubmitAlignedSimQuote (single builder) or removed with tests migrated — no orphaned export.
  6. Docs/skills updated to state book-leg debounce and hybrid snapshot invariant.

Test plan (all paths)

Unit — quoteDebounce.ts

  • Stale when live book ≠ debounced book (pay stable).
  • Stale when live max makers ≠ snapshotted (if snapshotted).
  • Hybrid assert throws on book mismatch; passes when aligned.
  • buildSubmitAlignedSimPayload wired or removed per chosen direction.

Unit — useSubmitAlignedSimQuote

  • isSubmitReady false when book leg stale.
  • submitPayRaw + exposed hybrid snapshot match debounced inputs.

Unit — swapDisclosure.ts / computeHybridParams

  • No regression on split math; add cases with debounced vs live book if helpers gain debounced entry points.

Component — SwapPage.test.tsx

  • Hybrid enabled, stable pay, change book leg → Swap disabled / Calculating… until debounced book quote settles.
  • Submit mutation receives snapshotted hybrid (mock swap / postRouteSolve args).

Component — Trade market panel (add or extend tests)

  • Same book-leg stale gate as Swap.
  • Submit uses debouncedHybrid matching sim key.

Integration / manual (LocalTerra + make dev)

  1. Swap → Settings → enable limit book leg → pay 10, book 2 → wait for quote → change book to 5 → submit disabled until refresh → submit sends book_input matching displayed quote.
  2. Trade market → hybrid on → repeat (1).
  3. Change max maker fills with stable pay/book → submit disabled during refetch.
  4. Pool-only fallback disclosure still correct when book leg configured but indexer unavailable.

Automated commands

cd frontend-dapp && npx vitest run src/utils/quoteDebounce.test.ts src/hooks/useSubmitAlignedSimQuote.test.ts src/utils/swapDisclosure.test.ts
cd frontend-dapp && npx vitest run src/pages/SwapPage.test.tsx
make test-frontend

Test plan — attack, hack, and abuse vectors

Vector Scenario Expected
Quote–execution skew Stable pay, rapid book edits, click submit during keepPreviousData Submit blocked; mutation assert throws if UI bypassed
Inflated min_return exploit Quote for small book leg, submit with large book leg (more pool) Blocked by stale gate / assert; on-chain min_return matches quoted out
Deflated min_return (user harm) Quote for large book leg, submit with small book leg Submit blocked or uses fresh quote; user not worse than displayed
Programmatic mutation trigger DevTools / double-click / extension triggers swapMutation while hybrid stale assertSubmitHybridAligned throws before broadcast
Max makers manipulation Lower max makers after quote to change fill behavior Stale detection blocks until new sim; assert catches mismatch
Indexer vs LCD hybrid path postRouteSolve vs simulateHybridSwap fallback Submit uses same hybrid snapshot as winning quote branch
Gas griefing (Trade) Book leg max spend UI uses live hybrid but submit uses snapshot User cannot submit with live gas estimate that assumes different hybrid than execution

Verification criteria

  • All acceptance criteria met on main branch after merge.
  • make test-frontend green.
  • Manual checklist (4 scenarios above) recorded on MR or issue.
  • docs/frontend.md and skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md #356 rows mention debounced book leg.
  • No remaining production imports of live bookInputHuman inside swapMutation hybrid branches (Swap + Trade).
  • rg buildSubmitAlignedSimPayload frontend-dapp/src shows only intentional usages (hook or tests), not dead export.
  • #356 — pay-amount submit alignment (parent; partial)
  • #346 — sim query debounce (pay field)
  • skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md — submit–quote alignment table
## Summary Follow-up to [#356](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/356). Pay-amount submit alignment landed via `useSubmitAlignedSimQuote`, `isSubmitQuoteStale` (incl. `isFetching`), and `assertSubmitQuotePayRawAligned` in Swap / Trade market mutations. **Hybrid book-leg inputs are still live** while pay is debounced, and the in-mutation assert does not cover them. **`buildSubmitAlignedSimPayload` is exported and tested but unused in production** — the hook duplicates its `minReceived` bundling inline. ## Current codebase ### What #356 already fixed (pay amount) - [`frontend-dapp/src/hooks/useSubmitAlignedSimQuote.ts`](../frontend-dapp/src/hooks/useSubmitAlignedSimQuote.ts) — exposes `submitPayRaw` (= debounced pay raw), `simData`, `minReceived`, `isQuoteStale`, `isSubmitReady`. - [`frontend-dapp/src/utils/quoteDebounce.ts`](../frontend-dapp/src/utils/quoteDebounce.ts) — `isSubmitQuoteStale(raw, debouncedRaw, isPlaceholderData, isFetching)`; `assertSubmitQuotePayRawAligned` checks **pay raw only**. - [`frontend-dapp/src/pages/SwapPage.tsx`](../frontend-dapp/src/pages/SwapPage.tsx) and [`frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx`](../frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx) — `swapMutation` uses `submitPayRaw` / `minReceived` / `simData`; calls `assertSubmitQuotePayRawAligned(rawInputAmount, debouncedRawInputAmount)` at top of `mutationFn`. ### Remaining gap — hybrid book leg (`bookInputHuman`, `hybridMaxMakers`) | Input | Sim query key | Sim `queryFn` hybrid split | Submit / mutation hybrid split | In `isSubmitQuoteStale` / assert | |-------|---------------|----------------------------|--------------------------------|----------------------------------| | Pay amount | `debouncedRawInputAmount` | debounced `simRaw` | `submitPayRaw` (debounced) | **Yes** | | Book leg (`bookInputHuman`) | **live** | **live** `bookInputHuman` | **live** `bookInputHuman` (Swap direct hybrid branch ~L654; Trade uses `debouncedHybrid` with **live** book) | **No** | | `hybridMaxMakers` | **live** | **live** | **live** | **No** | **Swap (`SwapPage.tsx`):** - `simQueryKey` includes live `bookInputHuman` and `hybridMaxMakers` (~L345–374). - Advanced direct hybrid quote path (~L427–472) and pool-only disclosure split (~L520–527) read **live** `bookInputHuman` against **debounced** `simRaw`. - `swapMutation` direct hybrid fallback (~L652–667) recomputes `bookRaw` from **live** `bookInputHuman` + debounced `payRaw` — not snapshotted with the sim that produced `minReceived` / `idxOps`. - `directHybridBookSplit` display (~L756–766) uses **live** `rawInputAmount` + live `bookInputHuman`. **Trade market (`TradeMarketOrderPanel.tsx`):** - `simQueryKey` includes live `bookInputHuman` (~L228–238). - `debouncedHybrid` = `computeHybridParams(debouncedRawInputAmount, …, bookInputHuman, …)` — debounced pay + **live** book (~L159–161). - Submit uses `debouncedHybrid` (~L375) but stale gate / assert do not verify book leg or max makers match the settled sim key. - `liveHybrid` / gas estimate still uses live pay + live book (~L154–166) — intentional for UX hints, but must not be the submit snapshot. ### Dead / duplicated helper - [`buildSubmitAlignedSimPayload`](../frontend-dapp/src/utils/quoteDebounce.ts) (~L40–52) bundles `{ payRaw, minReceived, simData, indexerOperations }` but is **only imported in** [`quoteDebounce.test.ts`](../frontend-dapp/src/utils/quoteDebounce.test.ts). - `useSubmitAlignedSimQuote` recomputes `minReceived` via `applySlippagePercentFloor` instead of calling `buildSubmitAlignedSimPayload` — two sources of truth for the same snapshot shape. ## Why this is needed 1. **Defense in depth:** `assertSubmitQuotePayRawAligned` only guards pay raw. A programmatic or race bypass of the UI stale gate could submit a **different book split** than the quote used for `minReceived` / hybrid `min_return` — same class of slippage / revert risk as the original #356 pay-amount skew. 2. **Incomplete stale detection:** When pay amount is stable (`rawInputAmount === debouncedRawInputAmount`) but the user edits the book leg or max makers, submit may stay enabled while `keepPreviousData` shows a quote for the **previous** hybrid params (only indirectly blocked if `isFetching` happens to be true). 3. **#346 parity:** Pay amount is debounced to limit sim churn; book leg is not — every book keystroke invalidates the sim key and refetches, undermining the debounce win on hybrid flows. 4. **Maintainability:** Either wire `buildSubmitAlignedSimPayload` into the hook (single snapshot builder) or remove the dead export to avoid drift between test-only and production paths. ## Constraints and guardrails - Do **not** regress #356 pay-amount alignment or #346 debounce behavior for the main pay field. - Hybrid book leg debounce should use the same `SIM_QUOTE_DEBOUNCE_MS` (350) unless product explicitly wants a different constant — document if changed. - `getDirectHybridBookSplit` / `computeHybridParams` / `swapDisclosure` helpers stay pure; extend or wrap them rather than duplicating split math in components. - Trade market submit must continue to use `debouncedHybrid` for on-chain `hybrid` params (already partially correct) — extend snapshot, don't revert to live pay. - `liveHybrid` may remain for gas / max-spend UX; only **submit** and **quote display when submit-ready** must use the snapshotted hybrid params. - Preserve `isSimQuoteStaleForSubmit` deprecated alias until call sites are migrated. - Update [`skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md`](../skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md) and [`docs/frontend.md`](../docs/frontend.md) #356 sections when behavior changes. ## Relevant files | Area | Path | |------|------| | Submit snapshot hook | `frontend-dapp/src/hooks/useSubmitAlignedSimQuote.ts`, `.test.ts` | | Stale / assert / payload helpers | `frontend-dapp/src/utils/quoteDebounce.ts`, `.test.ts` | | Debounce | `frontend-dapp/src/hooks/useDebouncedValue.ts` | | Swap | `frontend-dapp/src/pages/SwapPage.tsx`, `SwapPage.test.tsx` | | Trade market | `frontend-dapp/src/components/trade/TradeMarketOrderPanel.tsx` | | Hybrid split pure helpers | `frontend-dapp/src/utils/swapDisclosure.ts`, `.test.ts` | | Docs / skills | `docs/frontend.md`, `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md` | ## Recommended direction 1. **Extend submit snapshot** — add debounced `bookInputHuman` (and optionally debounced `hybridMaxMakers` or treat max-makers as immediate with explicit stale on change): - `const debouncedBookInputHuman = useDebouncedValue(bookInputHuman, SIM_QUOTE_DEBOUNCE_MS)` - Include debounced book (+ max makers) in `simQueryKey` / `queryFn` instead of live values. 2. **Extend stale detection** — widen `isSubmitQuoteStale` (or add `isHybridParamsStale`) to return true when live book ≠ debounced book or live max makers ≠ snapshotted max makers (mirror pay-amount pattern). 3. **Extend mutation assert** — e.g. `assertSubmitHybridAligned({ bookLive, bookDebounced, maxMakersLive, maxMakersSnap })` called alongside `assertSubmitQuotePayRawAligned`; Swap direct hybrid branch must read book split from snapshot, not closure `bookInputHuman`. 4. **Consolidate payload builder** — either: - (A) Have `useSubmitAlignedSimQuote` call `buildSubmitAlignedSimPayload` for `minReceived` + expose full `submitPayload`, **or** - (B) Move snapshot bundling into the hook and **delete** `buildSubmitAlignedSimPayload` + `SubmitAlignedSimPayload` type from `quoteDebounce.ts` (update tests to target the hook). - Prefer (A) if the type is useful for mutationFn; prefer (B) if the hook is the sole consumer. 5. **Optional:** expose `submitHybrid: HybridSwapParams | undefined` from the hook so Swap and Trade mutations stop recomputing splits inline. ## Acceptance criteria 1. When submit is allowed, **pay raw, book leg raw, max makers, `minReceived`, `indexerOperations`, and on-chain `hybrid` params** all derive from the same settled sim key (debounced pay + debounced book [+ snapshotted max makers]). 2. `isSubmitReady` is false while live book leg ≠ debounced book leg (or while hybrid params are otherwise stale), even if pay raw matches debounced pay. 3. `mutationFn` asserts hybrid alignment before broadcast (defense in depth); mismatch throws the same class of user-facing error as pay misalignment. 4. Swap advanced direct hybrid quote path and mutation use the **same** debounced book input as the sim key — no live `bookInputHuman` in submit path. 5. `buildSubmitAlignedSimPayload` is either used by `useSubmitAlignedSimQuote` (single builder) or removed with tests migrated — no orphaned export. 6. Docs/skills updated to state book-leg debounce and hybrid snapshot invariant. ## Test plan (all paths) ### Unit — `quoteDebounce.ts` - Stale when live book ≠ debounced book (pay stable). - Stale when live max makers ≠ snapshotted (if snapshotted). - Hybrid assert throws on book mismatch; passes when aligned. - `buildSubmitAlignedSimPayload` wired or removed per chosen direction. ### Unit — `useSubmitAlignedSimQuote` - `isSubmitReady` false when book leg stale. - `submitPayRaw` + exposed hybrid snapshot match debounced inputs. ### Unit — `swapDisclosure.ts` / `computeHybridParams` - No regression on split math; add cases with debounced vs live book if helpers gain debounced entry points. ### Component — `SwapPage.test.tsx` - Hybrid enabled, stable pay, change book leg → Swap disabled / **Calculating…** until debounced book quote settles. - Submit mutation receives snapshotted hybrid (mock `swap` / `postRouteSolve` args). ### Component — Trade market panel (add or extend tests) - Same book-leg stale gate as Swap. - Submit uses `debouncedHybrid` matching sim key. ### Integration / manual (LocalTerra + `make dev`) 1. Swap → Settings → enable limit book leg → pay `10`, book `2` → wait for quote → change book to `5` → submit disabled until refresh → submit sends `book_input` matching displayed quote. 2. Trade market → hybrid on → repeat (1). 3. Change `max maker fills` with stable pay/book → submit disabled during refetch. 4. Pool-only fallback disclosure still correct when book leg configured but indexer unavailable. ### Automated commands ```bash cd frontend-dapp && npx vitest run src/utils/quoteDebounce.test.ts src/hooks/useSubmitAlignedSimQuote.test.ts src/utils/swapDisclosure.test.ts cd frontend-dapp && npx vitest run src/pages/SwapPage.test.tsx make test-frontend ``` ## Test plan — attack, hack, and abuse vectors | Vector | Scenario | Expected | |--------|----------|----------| | Quote–execution skew | Stable pay, rapid book edits, click submit during `keepPreviousData` | Submit blocked; mutation assert throws if UI bypassed | | Inflated min_return exploit | Quote for small book leg, submit with large book leg (more pool) | Blocked by stale gate / assert; on-chain `min_return` matches quoted out | | Deflated min_return (user harm) | Quote for large book leg, submit with small book leg | Submit blocked or uses fresh quote; user not worse than displayed | | Programmatic mutation trigger | DevTools / double-click / extension triggers `swapMutation` while hybrid stale | `assertSubmitHybridAligned` throws before broadcast | | Max makers manipulation | Lower max makers after quote to change fill behavior | Stale detection blocks until new sim; assert catches mismatch | | Indexer vs LCD hybrid path | `postRouteSolve` vs `simulateHybridSwap` fallback | Submit uses same hybrid snapshot as winning quote branch | | Gas griefing (Trade) | Book leg max spend UI uses live hybrid but submit uses snapshot | User cannot submit with live gas estimate that assumes different hybrid than execution | ## Verification criteria - [ ] All acceptance criteria met on `main` branch after merge. - [ ] `make test-frontend` green. - [ ] Manual checklist (4 scenarios above) recorded on MR or issue. - [ ] `docs/frontend.md` and `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md` #356 rows mention debounced book leg. - [ ] No remaining production imports of live `bookInputHuman` inside `swapMutation` hybrid branches (Swap + Trade). - [ ] `rg buildSubmitAlignedSimPayload frontend-dapp/src` shows only intentional usages (hook or tests), not dead export. ## Related - #356 — pay-amount submit alignment (parent; partial) - #346 — sim query debounce (pay field) - `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md` — submit–quote alignment table
PlasticDigits commented 2026-06-11 10:28:43 +00:00 (Migrated from gitlab.com)

marked as related to #356

marked as related to #356
PlasticDigits commented 2026-06-11 15:31:25 +00:00 (Migrated from gitlab.com)

mentioned in commit c1bea72d0c

mentioned in commit c1bea72d0ca073739a682effb7aa8fbe92122781
PlasticDigits commented 2026-06-11 15:31:35 +00:00 (Migrated from gitlab.com)

mentioned in merge request !866

mentioned in merge request !866
PlasticDigits commented 2026-06-11 15:34:18 +00:00 (Migrated from gitlab.com)

mentioned in commit 16e0656c37

mentioned in commit 16e0656c376eb60f2b327823847aca37e02c5dce
Brouie commented 2026-06-12 03:05:56 +00:00 (Migrated from gitlab.com)

mentioned in merge request !868

mentioned in merge request !868
Brouie commented 2026-06-12 03:11:48 +00:00 (Migrated from gitlab.com)

Heads up — #360 (!866) merges clean but left npm run build (tsc -b) red on main at 16e0656.

buildSubmitAlignedSimPayload types its applySlippageFloor param as => string, but the fn useSubmitAlignedSimQuote actually passes is applySlippagePercentFloor, which returns string | null. The minReceived field it feeds is already string | null and every consumer (SwapPage, TradeMarketOrderPanel) null-checks it — so the param type was just too narrow.

Fix is a one-line widen to => string | null in qa/360-build-tsc-minreceived-null -> MR !868. Build green after it, full vitest 935/937 (the 2 fails are the known terraExplorer localhost-vs-127.0.0.1 env artifact, unrelated).

Same CI gap as before: the pipeline runs vitest + eslint but not tsc -b, so type-only breaks keep landing on main unseen. Might be worth a tsc -b step.

Heads up — #360 (!866) merges clean but left `npm run build` (tsc -b) red on main at 16e0656. `buildSubmitAlignedSimPayload` types its `applySlippageFloor` param as `=> string`, but the fn `useSubmitAlignedSimQuote` actually passes is `applySlippagePercentFloor`, which returns `string | null`. The `minReceived` field it feeds is already `string | null` and every consumer (SwapPage, TradeMarketOrderPanel) null-checks it — so the param type was just too narrow. Fix is a one-line widen to `=> string | null` in `qa/360-build-tsc-minreceived-null` -> MR !868. Build green after it, full vitest 935/937 (the 2 fails are the known terraExplorer localhost-vs-127.0.0.1 env artifact, unrelated). Same CI gap as before: the pipeline runs vitest + eslint but not tsc -b, so type-only breaks keep landing on main unseen. Might be worth a tsc -b step.
PlasticDigits commented 2026-06-12 03:35:36 +00:00 (Migrated from gitlab.com)

mentioned in commit f0a1a65bb7

mentioned in commit f0a1a65bb742de783916f034af21098d01a6e44b
PlasticDigits commented 2026-06-12 03:35:36 +00:00 (Migrated from gitlab.com)

mentioned in commit 8408689ee5

mentioned in commit 8408689ee51ccc8efb7692ae716c7f33a0facf5c
Brouie commented 2026-06-12 03:37:00 +00:00 (Migrated from gitlab.com)

Verified the implementation (c1bea72 / !866) at the source + unit layer on 16e0656 — all six acceptance criteria map:

  • sim key + queryFn use debounced book leg + debounced max makers (Swap and Trade market)
  • submit builds hybrid params from the debounced snapshot (submitDirectHybrid / debouncedHybrid), not live bookInputHuman — live values survive only in the stale-comparison + assert, never the tx
  • isSubmitQuoteStale returns stale on live!=snapshot book or max makers (pay stable)
  • both mutationFns call assertSubmitHybridAligned before broadcast (defense in depth)
  • buildSubmitAlignedSimPayload is now wired into useSubmitAlignedSimQuote — no orphaned export
  • docs/frontend.md + AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md updated with the snapshot invariant

37 targeted tests green (quoteDebounce, useSubmitAlignedSimQuote, swapDisclosure, SwapPage, TradeMarketOrderPanel.submitSnapshot). The new snapshot test fails against the pre-fix panel (book 2->5 leaves submit enabled) and passes at HEAD, so it genuinely guards the AC2 behavior.

Two things before this closes: (1) the tsc -b build break flagged above needs !868 merged first; (2) the 4 live LocalTerra hybrid scenarios + the on-chain min_return match are browser/laptop — on my queue. Good to close from my side on the src+unit layer once !868 lands and the laptop hybrid pass rides along.

Verified the implementation (c1bea72 / !866) at the source + unit layer on 16e0656 — all six acceptance criteria map: - sim key + queryFn use debounced book leg + debounced max makers (Swap and Trade market) - submit builds hybrid params from the debounced snapshot (submitDirectHybrid / debouncedHybrid), not live bookInputHuman — live values survive only in the stale-comparison + assert, never the tx - isSubmitQuoteStale returns stale on live!=snapshot book or max makers (pay stable) - both mutationFns call assertSubmitHybridAligned before broadcast (defense in depth) - buildSubmitAlignedSimPayload is now wired into useSubmitAlignedSimQuote — no orphaned export - docs/frontend.md + AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md updated with the snapshot invariant 37 targeted tests green (quoteDebounce, useSubmitAlignedSimQuote, swapDisclosure, SwapPage, TradeMarketOrderPanel.submitSnapshot). The new snapshot test fails against the pre-fix panel (book 2->5 leaves submit enabled) and passes at HEAD, so it genuinely guards the AC2 behavior. Two things before this closes: (1) the tsc -b build break flagged above needs !868 merged first; (2) the 4 live LocalTerra hybrid scenarios + the on-chain min_return match are browser/laptop — on my queue. Good to close from my side on the src+unit layer once !868 lands and the laptop hybrid pass rides along.
Brouie commented 2026-06-12 03:50:52 +00:00 (Migrated from gitlab.com)

mentioned in issue #337

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

Verify #360 — PASS (main @ 8408689)

Verified on origin/main after fast-forward to 8408689 (includes c1bea72 #360 implementation + f0a1a65 tsc minReceived null widen from !868).

Acceptance criteria

# Criterion Result Evidence
1 Submit snapshot: pay raw, book leg, max makers, minReceived, indexer ops, hybrid params from one debounced sim key PASS debouncedBookInputHuman + debouncedHybridMaxMakers in sim keys; submitDirectHybrid / debouncedHybrid for mutations (SwapPage.tsx, TradeMarketOrderPanel.tsx)
2 isSubmitReady false when live book ≠ debounced (pay stable) PASS isSubmitQuoteStale hybrid branch; hook + component tests
3 mutationFn asserts hybrid alignment before broadcast PASS assertSubmitHybridAligned in Swap + Trade swapMutation
4 Swap submit path uses debounced book (no live bookInputHuman in hybrid tx build) PASS submitDirectHybrid from debounced inputs; live book only in stale assert
5 buildSubmitAlignedSimPayload wired (no orphan export) PASS useSubmitAlignedSimQuote calls builder; rg → hook + tests only
6 Docs/skills updated PASS docs/frontend.md, skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md #356/#360 rows

Automated tests

vitest (targeted #360): 37/37 passed
  quoteDebounce.test.ts, useSubmitAlignedSimQuote.test.ts, swapDisclosure.test.ts,
  SwapPage.test.tsx (#360 book-leg stale), TradeMarketOrderPanel.submitSnapshot.test.tsx

make test-frontend: 946/946 passed (160 files)

Manual / LocalTerra

Provisioned stack via make setup-cloud-localterra (chain block ~5351, indexer :3001 healthy).

Scenario Result Notes
Swap book 2→5 stale gate + submit alignment PASS (unit/component) SwapPage.test.tsx "book leg differs from debounced hybrid quote (#360)"
Trade market same stale gate PASS (unit/component) TradeMarketOrderPanel.submitSnapshot.test.tsx
Max makers change → submit disabled PASS (unit) quoteDebounce.test.ts max-makers stale case
Pool-only fallback disclosure PASS (unit) Existing SwapPage outage/fallback tests unchanged

Ad-hoc Playwright on LocalTerra failed to locate Simulated Wallet connect control (harness/fixture gap in one-off script, not stale-gate regression). Full hybrid on-chain E2E (hybrid-swap.spec.ts) not re-run in this verify session due to time; component tests explicitly guard AC2.

Verification checklist

  • Acceptance criteria on main
  • make test-frontend green
  • Manual scenarios covered at unit/component layer; live browser walkthrough deferred (same as Brouie note on !866)
  • Docs mention debounced book leg
  • No live bookInputHuman in submit hybrid branches (assert-only for defense in depth)
  • buildSubmitAlignedSimPayload intentionally used

Note (out of #360 scope)

npm run build (tsc -b) still fails on main with errors in terraWalletSignTxRaw.ts / terraTxHash.ts (#359 broadcast recovery) — unrelated to the #360 string | null slippage-floor fix. CI does not run tsc -b today.

Follow-up ideas

  • Add a dedicated Playwright spec for book-leg debounce stale gate using connectWallet fixture (mirror component tests).
  • Add tsc -b to frontend CI to catch type-only breaks.
## Verify #360 — PASS (main @ 8408689) Verified on `origin/main` after fast-forward to `8408689` (includes `c1bea72` #360 implementation + `f0a1a65` tsc `minReceived` null widen from !868). ### Acceptance criteria | # | Criterion | Result | Evidence | |---|-----------|--------|----------| | 1 | Submit snapshot: pay raw, book leg, max makers, minReceived, indexer ops, hybrid params from one debounced sim key | **PASS** | `debouncedBookInputHuman` + `debouncedHybridMaxMakers` in sim keys; `submitDirectHybrid` / `debouncedHybrid` for mutations (`SwapPage.tsx`, `TradeMarketOrderPanel.tsx`) | | 2 | `isSubmitReady` false when live book ≠ debounced (pay stable) | **PASS** | `isSubmitQuoteStale` hybrid branch; hook + component tests | | 3 | `mutationFn` asserts hybrid alignment before broadcast | **PASS** | `assertSubmitHybridAligned` in Swap + Trade `swapMutation` | | 4 | Swap submit path uses debounced book (no live `bookInputHuman` in hybrid tx build) | **PASS** | `submitDirectHybrid` from debounced inputs; live book only in stale assert | | 5 | `buildSubmitAlignedSimPayload` wired (no orphan export) | **PASS** | `useSubmitAlignedSimQuote` calls builder; `rg` → hook + tests only | | 6 | Docs/skills updated | **PASS** | `docs/frontend.md`, `skills/AGENTS_FRONTEND_SWAP_ROUTE_DISPLAY.md` #356/#360 rows | ### Automated tests ```text vitest (targeted #360): 37/37 passed quoteDebounce.test.ts, useSubmitAlignedSimQuote.test.ts, swapDisclosure.test.ts, SwapPage.test.tsx (#360 book-leg stale), TradeMarketOrderPanel.submitSnapshot.test.tsx make test-frontend: 946/946 passed (160 files) ``` ### Manual / LocalTerra Provisioned stack via `make setup-cloud-localterra` (chain block ~5351, indexer :3001 healthy). | Scenario | Result | Notes | |----------|--------|-------| | Swap book 2→5 stale gate + submit alignment | **PASS (unit/component)** | `SwapPage.test.tsx` "book leg differs from debounced hybrid quote (#360)" | | Trade market same stale gate | **PASS (unit/component)** | `TradeMarketOrderPanel.submitSnapshot.test.tsx` | | Max makers change → submit disabled | **PASS (unit)** | `quoteDebounce.test.ts` max-makers stale case | | Pool-only fallback disclosure | **PASS (unit)** | Existing `SwapPage` outage/fallback tests unchanged | Ad-hoc Playwright on LocalTerra failed to locate Simulated Wallet connect control (harness/fixture gap in one-off script, not stale-gate regression). Full hybrid on-chain E2E (`hybrid-swap.spec.ts`) not re-run in this verify session due to time; component tests explicitly guard AC2. ### Verification checklist - [x] Acceptance criteria on main - [x] `make test-frontend` green - [x] Manual scenarios covered at unit/component layer; live browser walkthrough deferred (same as Brouie note on !866) - [x] Docs mention debounced book leg - [x] No live `bookInputHuman` in submit hybrid branches (assert-only for defense in depth) - [x] `buildSubmitAlignedSimPayload` intentionally used ### Note (out of #360 scope) `npm run build` (`tsc -b`) still fails on main with errors in `terraWalletSignTxRaw.ts` / `terraTxHash.ts` (#359 broadcast recovery) — unrelated to the #360 `string | null` slippage-floor fix. CI does not run `tsc -b` today. ### Follow-up ideas - Add a dedicated Playwright spec for book-leg debounce stale gate using `connectWallet` fixture (mirror component tests). - Add `tsc -b` to frontend CI to catch type-only breaks.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-12 04:21:21 +00:00
PlasticDigits commented 2026-06-12 04:46:02 +00:00 (Migrated from gitlab.com)

mentioned in issue #361

mentioned in issue #361
ghost1 commented 2026-06-13 03:43:21 +00:00 (Migrated from gitlab.com)

mentioned in commit 26a4b7b6a2

mentioned in commit 26a4b7b6a251168b233bcad384baf307db79a9b2
ghost1 commented 2026-06-13 03:43:37 +00:00 (Migrated from gitlab.com)

mentioned in merge request !894

mentioned in merge request !894
Brouie commented 2026-06-29 15:30:19 +00:00 (Migrated from gitlab.com)

mentioned in issue #428

mentioned in issue #428
PlasticDigits commented 2026-08-05 01:48:28 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1041

mentioned in merge request !1041
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#360
No description provided.