TimeCurve frontend: sale-phase detection mis-reports 'pre-start' when contract is live #48

Closed
opened 2026-04-23 04:06:37 +00:00 by Brouie · 18 comments
Brouie commented 2026-04-23 04:06:37 +00:00 (Migrated from gitlab.com)

Filed per dev request from #39 section C1 findings.

Summary

Frontend phase detection reports pre-start/waiting-on-chain state even when the contract is live (saleStart > 0 && !ended). This gates Buy CHARM interaction and prevents manual UI-driven testing despite the chain being fully active.

Observed

  • Arena view: Countdown 24:36:43 renders correctly, but banner text says 'Pre-start / WAITING ON CHAIN / The sale has not started yet'
  • Simple view: 'Loading sale state…' displays indefinitely; Buy CHARM card stuck on 'COMING SOON'
  • Urgency styling: Present in design but not triggered because phase detection reports pre-start

Contradicting evidence (same session)

  • Contract state: saleStart > 0 confirmed on-chain (section B verification)
  • Contract is NOT ended
  • Recent Buys, Live BP leader cards, Battle Feed all populate correctly — so indexer + contract reads work for some data, just not the phase
  • Bot swarm is successfully buying (verified via indexer /v1/timecurve/buys)

Suspected cause

Either:

  • 'Adaptive e2e launch state' from fed6cd2 didn't fully land
  • LaunchGate guard is conservatively blocking when it doesn't recognize state

Environment

  • Anvil 31337 local stack
  • SKIP_ANVIL_RICH_STATE=1 (active sale + 18-bot swarm)
  • Frontend localhost:5173 (laptop)
  • MetaMask connected (0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650)
  • Repo at fed6cd2..eac3d5f range (three-view split #40 included)

Impact

  • Blocks C1/C2 manual verification of Buy CHARM UI flow
  • Blocks C6 redeem path testing unless state resolves

Screenshot

C1-phase-mismatch

cc @PlasticDigits

**Filed per dev request from #39 section C1 findings.** ## Summary Frontend phase detection reports pre-start/waiting-on-chain state even when the contract is live (`saleStart > 0 && !ended`). This gates Buy CHARM interaction and prevents manual UI-driven testing despite the chain being fully active. ## Observed - **Arena view**: Countdown `24:36:43` renders correctly, but banner text says *'Pre-start / WAITING ON CHAIN / The sale has not started yet'* - **Simple view**: 'Loading sale state…' displays indefinitely; Buy CHARM card stuck on 'COMING SOON' - **Urgency styling**: Present in design but not triggered because phase detection reports pre-start ## Contradicting evidence (same session) - Contract state: `saleStart > 0` confirmed on-chain (section B verification) - Contract is NOT `ended` - Recent Buys, Live BP leader cards, Battle Feed all populate correctly — so indexer + contract reads work for some data, just not the phase - Bot swarm is successfully buying (verified via indexer `/v1/timecurve/buys`) ## Suspected cause Either: - 'Adaptive e2e launch state' from `fed6cd2` didn't fully land - `LaunchGate` guard is conservatively blocking when it doesn't recognize state ## Environment - Anvil 31337 local stack - `SKIP_ANVIL_RICH_STATE=1` (active sale + 18-bot swarm) - Frontend localhost:5173 (laptop) - MetaMask connected (`0xc46b15f4B56489a16F561c22D5F0BA8bdCa80650`) - Repo at `fed6cd2..eac3d5f` range (three-view split #40 included) ## Impact - Blocks C1/C2 manual verification of Buy CHARM UI flow - Blocks C6 redeem path testing unless state resolves ## Screenshot ![C1-phase-mismatch](/uploads/aeb18e2607831b03e9fff80aac19f379/image.png) cc @PlasticDigits
Brouie commented 2026-04-23 04:08:19 +00:00 (Migrated from gitlab.com)

mentioned in issue #49

mentioned in issue #49
Brouie commented 2026-04-23 04:09:25 +00:00 (Migrated from gitlab.com)

mentioned in issue #39

mentioned in issue #39
PlasticDigits commented 2026-04-23 05:33:54 +00:00 (Migrated from gitlab.com)

mentioned in commit 41aaa92b7e

mentioned in commit 41aaa92b7e25c2ac1ba341af57644f6d3dfb93ac
PlasticDigits commented 2026-04-23 05:33:54 +00:00 (Migrated from gitlab.com)

mentioned in commit 2b7dd2b8e3

mentioned in commit 2b7dd2b8e338b0912c679f09601924f6b55e6289
PlasticDigits commented 2026-04-23 05:34:07 +00:00 (Migrated from gitlab.com)

Fix merged (main @ 41aaa92)

Cause: derivePhase used wallet latestBlock.timestamp (and wall fallback) for “chain now,” while the hero countdown and related UX use useTimecurveHeroTimer, which is indexer-anchored (/v1/timecurve/chain-timer) with a fixed wall–chain skew. If the JSON-RPC head lags (common on local Anvil / multi-rail setups), the UI could show a correct live countdown to the round deadline but still classify the session as pre-start (Buy CHARM stuck on COMING SOON, Loading sale state…, Waiting on chain).

Change: Added ledgerSecIntForPhase() in timeCurveSimplePhase.ts and use Math.floor(hero chainNowSec) when the hero timer has loaded; otherwise keep the previous block-based time. Applied in useTimeCurveSaleSession (simple + shared phase) and TimeCurvePage (Arena). preStartCountdownSec in the session hook now uses the same phase clock so it cannot disagree with the phase strip.

Tests: Vitest — timeCurveSimplePhase tests (including new cases for ledgerSecIntForPhase).


@brouie — can you re-verify on your stack?

Checklist for verification

  • Local or dev: VITE_INDEXER_URL set so the hero timer can load (same as for Recent Buys / feed).
  • With an active sale (saleStart > 0, !ended, before deadline): Simple /timecurve shows Live sale (or equivalent) and Buy CHARM is not stuck on COMING SOON / Loading sale state….
  • Arena /timecurve/arena (or segment route): state badge is not Starts soon / Waiting on chain when the round is live; banner matches “live” semantics.
  • Pre-start (if you can reset time to before saleStart): phase still shows pre-launch and countdown to open behaves sensibly.
  • Post-deadline, pre-endSale: Timer expired / settlement messaging still correct.

If anything still mismatches, note whether the wallet RPC and indexer point at the same chain ID and base URL. Thanks.

## Fix merged (main @ `41aaa92`) **Cause:** `derivePhase` used **wallet `latestBlock.timestamp`** (and wall fallback) for “chain now,” while the **hero countdown** and related UX use **`useTimecurveHeroTimer`**, which is **indexer-anchored** (`/v1/timecurve/chain-timer`) with a fixed wall–chain skew. If the JSON-RPC head lags (common on local Anvil / multi-rail setups), the UI could show a **correct live countdown** to the round deadline but still classify the session as **pre-start** (Buy CHARM stuck on *COMING SOON*, *Loading sale state…*, *Waiting on chain*). **Change:** Added `ledgerSecIntForPhase()` in `timeCurveSimplePhase.ts` and use **`Math.floor(hero chainNowSec)`** when the hero timer has loaded; otherwise keep the previous block-based time. Applied in `useTimeCurveSaleSession` (simple + shared phase) and `TimeCurvePage` (Arena). `preStartCountdownSec` in the session hook now uses the same **phase** clock so it cannot disagree with the phase strip. **Tests:** Vitest — `timeCurveSimplePhase` tests (including new cases for `ledgerSecIntForPhase`). --- @brouie — can you re-verify on your stack? **Checklist for verification** - [ ] Local or dev: `VITE_INDEXER_URL` set so the hero timer can load (same as for Recent Buys / feed). - [ ] With an **active** sale (`saleStart > 0`, `!ended`, before deadline): **Simple** `/timecurve` shows **Live sale** (or equivalent) and Buy CHARM is **not** stuck on *COMING SOON* / *Loading sale state…*. - [ ] **Arena** `/timecurve/arena` (or segment route): state badge is **not** *Starts soon* / *Waiting on chain* when the round is live; banner matches “live” semantics. - [ ] **Pre-start** (if you can reset time to before `saleStart`): phase still shows pre-launch and countdown to open behaves sensibly. - [ ] **Post-deadline, pre-`endSale`**: *Timer expired* / settlement messaging still correct. If anything still mismatches, note whether the wallet RPC and indexer point at the **same** chain ID and base URL. Thanks.
Brouie commented 2026-04-23 06:17:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #40

mentioned in issue #40
Brouie commented 2026-04-23 06:24:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #42

mentioned in issue #42
Brouie commented 2026-04-24 02:34:17 +00:00 (Migrated from gitlab.com)

@PlasticDigits verified on 41aaa92.

Root cause resolution makes sense — derivePhase was using wallet-RPC block timestamp while the hero countdown uses indexer-anchored chainNowSec. On lagging RPC heads these diverged and phase reported pre-start despite the hero countdown correctly showing live remaining seconds.

Verification

Code-level:

  • New ledgerSecIntForPhase helper in timeCurveSimplePhase.ts — prefers Math.floor(heroChainNowSec) when available, falls back to block-based time
  • Wired in both surfaces:
    • useTimeCurveSaleSession.ts L293 (Simple path + shared phase)
    • TimeCurvePage.tsx L275 (Arena) with identical pattern at L277 passing heroChainNowSec
  • preStartCountdownSec in session hook also uses the same phase clock now so display and phase can't disagree

Tests:

  • timeCurveSimplePhase.test.ts: 16/16 passing (up from 13 yesterday — +3 new for ledgerSecIntForPhase covering floor math, undefined fallback, equal-value case)

Consistent fix across both Simple + Arena — matches the #40 invariant that these two share phase state via the same pure helper module.

Closing as verified.

@PlasticDigits verified on `41aaa92`. Root cause resolution makes sense — `derivePhase` was using wallet-RPC block timestamp while the hero countdown uses indexer-anchored chainNowSec. On lagging RPC heads these diverged and phase reported pre-start despite the hero countdown correctly showing live remaining seconds. ## Verification Code-level: - New `ledgerSecIntForPhase` helper in `timeCurveSimplePhase.ts` — prefers `Math.floor(heroChainNowSec)` when available, falls back to block-based time - Wired in both surfaces: - `useTimeCurveSaleSession.ts` L293 (Simple path + shared phase) - `TimeCurvePage.tsx` L275 (Arena) with identical pattern at L277 passing `heroChainNowSec` - `preStartCountdownSec` in session hook also uses the same phase clock now so display and phase can't disagree Tests: - `timeCurveSimplePhase.test.ts`: 16/16 passing (up from 13 yesterday — +3 new for `ledgerSecIntForPhase` covering floor math, undefined fallback, equal-value case) Consistent fix across both Simple + Arena — matches the #40 invariant that these two share phase state via the same pure helper module. Closing as verified.
Brouie (Migrated from gitlab.com) closed this issue 2026-04-24 02:34:18 +00:00
PlasticDigits commented 2026-04-24 05:13:28 +00:00 (Migrated from gitlab.com)

mentioned in issue #44

mentioned in issue #44
PlasticDigits commented 2026-05-03 07:10:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #113

mentioned in issue #113
PlasticDigits commented 2026-05-03 07:23:41 +00:00 (Migrated from gitlab.com)

mentioned in issue #114

mentioned in issue #114
PlasticDigits commented 2026-05-03 10:47:52 +00:00 (Migrated from gitlab.com)

mentioned in issue #115

mentioned in issue #115
PlasticDigits commented 2026-05-03 10:47:53 +00:00 (Migrated from gitlab.com)

marked this issue as related to #115

marked this issue as related to #115
PlasticDigits commented 2026-05-04 04:22:29 +00:00 (Migrated from gitlab.com)

mentioned in issue #124

mentioned in issue #124
PlasticDigits commented 2026-05-21 04:38:41 +00:00 (Migrated from gitlab.com)

mentioned in issue #227

mentioned in issue #227
PlasticDigits commented 2026-05-21 09:32:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #232

mentioned in issue #232
PlasticDigits commented 2026-05-22 00:14:56 +00:00 (Migrated from gitlab.com)

mentioned in issue #236

mentioned in issue #236
PlasticDigits commented 2026-05-22 06:51:19 +00:00 (Migrated from gitlab.com)

mentioned in issue #237

mentioned in issue #237
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/yieldomega#48
No description provided.