Play-route transition state model and UX #343

Closed
opened 2026-06-25 20:07:10 +00:00 by PlasticDigits · 5 comments
PlasticDigits commented 2026-06-25 20:07:10 +00:00 (Migrated from gitlab.com)

Parent

Transition Gap Analysis (#342) — P0 #1, P1 #7, P3 #16. Bundles H-F1, H-F2, H-F3.

Current codebase

  • Play route (/) uses indexer-first polling via useArenaHeroTimer, usePodiumReads, useArenaPlayerLevel, etc. React Query hooks use placeholderData: (previousData) => previousData (e.g. usePodiumReads.ts), latching stale epoch/standings during refresh.
  • Hero countdown uses wall-clock skew (chainNowSec from polled_at_ms + block_timestamp_sec) in useArenaHeroTimer.ts.
  • Side podium chips in ArenaTimerChips.tsx use static block_timestamp_sec at last poll (now = data ? Number(data.block_timestamp_sec) : …) — can disagree with hero between polls.
  • podiumCopy.tsx documents mechanics but there is no explicit UI state for expired-pending-roll, settling, or epoch-advanced.
  • docs/frontend/arena-views.md covers level-up celebration (#335) and wallet profile (#336) but has no dedicated transition-state UX spec.

Why needed

Users see frozen 00:00 timers, old epoch numbers, and stale leaderboards during rollovers because transition boundaries are poll-driven with latched placeholders. There is no settling indicator or permissionless-roll CTA. Timer clock sources split between hero and chips adds confusion during transitions.

Constraints / guardrails

  • Indexer-first display (#301): no browser RPC mirrors for podiums/timers/sale head when VITE_INDEXER_URL is set.
  • Reuse existing indexer connectivity backoff (#96).
  • AGPL; keep transition logic documented, not hidden in ad-hoc conditionals.
  • Coordinate with sibling issues for ingest-lag fields and transition activity feed (do not duplicate API design).

Relevant files

  • frontend/src/pages/arena/useArenaHeroTimer.ts
  • frontend/src/pages/arena/ArenaTimerChips.tsx
  • frontend/src/pages/arena/useTimerPodiumSlideMeta.ts
  • frontend/src/pages/arena/usePodiumReads.ts
  • frontend/src/pages/arena/podiumCopy.tsx
  • frontend/src/pages/arena/ArenaSimplePage.tsx
  • frontend/src/pages/arena/arenaPodiumTimerDisplay.ts
  • docs/frontend/arena-views.md
  1. Define a transition state model mapping on-chain/indexer signals to UX states: live, expired-pending-roll, settling, epoch-advanced, claimable. Derive from podium_epochs, podium_deadlines_sec, podium_timer_armed, claimable_cred_epoch (wallet stats), and (when available) ingest-lag fields from sibling issue.
  2. Implement play-route UI for expired/settling states: subtle spinner or “waiting for settlement” copy; optional permissionless rollPodiumEpoch CTA when timer expired and armed (with chain-mismatch barrier).
  3. Unify chainNowSec — export shared skew helper from hero timer hook; use in ArenaTimerChips and useTimerPodiumSlideMeta so all countdowns share the same wall-vs-chain anchor.
  4. Document the state machine in docs/frontend/arena-views.md (“Transition UX” section): on-chain events → indexer fields → UI states.

Acceptance criteria

  • Play route shows distinct UX for timer-at-zero vs live countdown (not just frozen 00:00).
  • Hero and side-chip countdowns use the same chainNowSec skew within a poll cycle.
  • Transition state model documented in arena-views.md with event/field mapping.
  • No new browser RPC reads for display when indexer URL is configured.
  • data-testid hooks for settling/pending-roll states for E2E (sibling issue).

Test plan

Path Command
Frontend unit cd frontend && npm run typecheck && npm run lint && npm test
Timer skew Extend useArenaHeroTimer / ArenaTimerChips tests for unified chainNowSec
Static indexer-first guard indexerFirstDisplay.test.ts still passes
E2E (after sibling) YIELDOMEGA_E2E_INDEXER=1 bash scripts/e2e-anvil.sh + transition spec

Attack / abuse test plan

  • Malicious indexer returning future deadlines must not crash UI; states should degrade to “unknown/syncing”.
  • Do not expose roll CTA that bypasses ChainMismatchWriteBarrier.
  • Skew helper must not allow negative countdowns to display as large positive values.

Verification criteria

  • Manual: warp Anvil past deadline → play route shows pending-roll/settling (not silent freeze).
  • Unit tests cover state derivation for armed/unarmed, expired, and post-roll epoch bump.
  • Docs link from arena-views.md to time-arena.md transition events.
## Parent [Transition Gap Analysis (#342)](https://gitlab.com/PlasticDigits/yieldomega/-/issues/342) — P0 #1, P1 #7, P3 #16. Bundles **H-F1**, **H-F2**, **H-F3**. ## Current codebase - Play route (`/`) uses indexer-first polling via `useArenaHeroTimer`, `usePodiumReads`, `useArenaPlayerLevel`, etc. React Query hooks use `placeholderData: (previousData) => previousData` (e.g. `usePodiumReads.ts`), latching stale epoch/standings during refresh. - Hero countdown uses wall-clock skew (`chainNowSec` from `polled_at_ms` + `block_timestamp_sec`) in `useArenaHeroTimer.ts`. - Side podium chips in `ArenaTimerChips.tsx` use static `block_timestamp_sec` at last poll (`now = data ? Number(data.block_timestamp_sec) : …`) — can disagree with hero between polls. - `podiumCopy.tsx` documents mechanics but there is no explicit UI state for *expired-pending-roll*, *settling*, or *epoch-advanced*. - `docs/frontend/arena-views.md` covers level-up celebration (#335) and wallet profile (#336) but has no dedicated transition-state UX spec. ## Why needed Users see frozen `00:00` timers, old epoch numbers, and stale leaderboards during rollovers because transition boundaries are poll-driven with latched placeholders. There is no settling indicator or permissionless-roll CTA. Timer clock sources split between hero and chips adds confusion during transitions. ## Constraints / guardrails - **Indexer-first display** ([#301](https://gitlab.com/PlasticDigits/yieldomega/-/issues/301)): no browser RPC mirrors for podiums/timers/sale head when `VITE_INDEXER_URL` is set. - Reuse existing indexer connectivity backoff ([#96](https://gitlab.com/PlasticDigits/yieldomega/-/issues/96)). - AGPL; keep transition logic documented, not hidden in ad-hoc conditionals. - Coordinate with sibling issues for ingest-lag fields and transition activity feed (do not duplicate API design). ## Relevant files - `frontend/src/pages/arena/useArenaHeroTimer.ts` - `frontend/src/pages/arena/ArenaTimerChips.tsx` - `frontend/src/pages/arena/useTimerPodiumSlideMeta.ts` - `frontend/src/pages/arena/usePodiumReads.ts` - `frontend/src/pages/arena/podiumCopy.tsx` - `frontend/src/pages/arena/ArenaSimplePage.tsx` - `frontend/src/pages/arena/arenaPodiumTimerDisplay.ts` - `docs/frontend/arena-views.md` ## Recommended direction 1. **Define a transition state model** mapping on-chain/indexer signals to UX states: `live`, `expired-pending-roll`, `settling`, `epoch-advanced`, `claimable`. Derive from `podium_epochs`, `podium_deadlines_sec`, `podium_timer_armed`, `claimable_cred_epoch` (wallet stats), and (when available) ingest-lag fields from sibling issue. 2. **Implement play-route UI** for expired/settling states: subtle spinner or “waiting for settlement” copy; optional permissionless `rollPodiumEpoch` CTA when timer expired and armed (with chain-mismatch barrier). 3. **Unify `chainNowSec`** — export shared skew helper from hero timer hook; use in `ArenaTimerChips` and `useTimerPodiumSlideMeta` so all countdowns share the same wall-vs-chain anchor. 4. **Document** the state machine in `docs/frontend/arena-views.md` (“Transition UX” section): on-chain events → indexer fields → UI states. ## Acceptance criteria - [ ] Play route shows distinct UX for timer-at-zero vs live countdown (not just frozen `00:00`). - [ ] Hero and side-chip countdowns use the same `chainNowSec` skew within a poll cycle. - [ ] Transition state model documented in `arena-views.md` with event/field mapping. - [ ] No new browser RPC reads for display when indexer URL is configured. - [ ] `data-testid` hooks for settling/pending-roll states for E2E (sibling issue). ## Test plan | Path | Command | |------|---------| | Frontend unit | `cd frontend && npm run typecheck && npm run lint && npm test` | | Timer skew | Extend `useArenaHeroTimer` / `ArenaTimerChips` tests for unified `chainNowSec` | | Static indexer-first guard | `indexerFirstDisplay.test.ts` still passes | | E2E (after sibling) | `YIELDOMEGA_E2E_INDEXER=1 bash scripts/e2e-anvil.sh` + transition spec | ## Attack / abuse test plan - Malicious indexer returning future deadlines must not crash UI; states should degrade to “unknown/syncing”. - Do not expose roll CTA that bypasses `ChainMismatchWriteBarrier`. - Skew helper must not allow negative countdowns to display as large positive values. ## Verification criteria - Manual: warp Anvil past deadline → play route shows pending-roll/settling (not silent freeze). - Unit tests cover state derivation for armed/unarmed, expired, and post-roll epoch bump. - Docs link from `arena-views.md` to `time-arena.md` transition events.
PlasticDigits commented 2026-06-25 20:07:47 +00:00 (Migrated from gitlab.com)

mentioned in issue #342

mentioned in issue #342
PlasticDigits commented 2026-06-25 22:01:22 +00:00 (Migrated from gitlab.com)

mentioned in commit e05dae569d

mentioned in commit e05dae569dcc9e0c4f67fcf44966a371854fe7e4
PlasticDigits commented 2026-06-25 22:01:36 +00:00 (Migrated from gitlab.com)

mentioned in merge request !519

mentioned in merge request !519
PlasticDigits commented 2026-06-25 22:23:21 +00:00 (Migrated from gitlab.com)

mentioned in commit 304744cae2

mentioned in commit 304744cae27cb05a9b12f2ab202b6ddc3c6b752c
PlasticDigits commented 2026-06-26 06:47:15 +00:00 (Migrated from gitlab.com)

Verification — #343 Play-route transition state model and UX

Verified on main (2026-06-26). All acceptance criteria PASS; no repo changes required.

Acceptance criteria

Criterion Result How verified
Distinct UX for timer-at-zero vs live countdown PASS arenaTransitionState.test.ts; manual Anvil warp → Playwright on http://127.0.0.1:5175/ found data-testid="arena-timer-expired-pending-roll" (×2) + arena-timer-transition-foot with copy "Waiting for settlement — roll the epoch or wait for the next qualifying buy." and arena-podium-roll-cta
Hero + side chips share chainNowSec skew PASS indexerFirstDisplay.test.ts (#343 skew guard); ArenaTimerChips.test.tsx; arenaChainNow.test.ts
Transition state model documented in arena-views.md PASS arena-views §343 — state table, shared skew, E2E hooks; links to time-arena.md § Podium epoch roll
No new browser RPC display mirrors when indexer URL set PASS indexerFirstDisplay.test.ts (static guard for podium/timer hooks)
data-testid hooks for settling/pending-roll PASS Documented in arena-views §343; transitionStateTestId() in arenaTransitionState.ts; manual Playwright confirmed arena-timer-expired-pending-roll, arena-timer-transition-foot, arena-podium-roll-cta

Test plan

Path Command Result
Frontend unit cd frontend && npm run typecheck && npm run lint && npm test PASS (760 tests)
Timer skew / transition derivation arenaTransitionState.test.ts, arenaChainNow.test.ts, ArenaTimerChips.test.tsx PASS
Indexer-first guard indexerFirstDisplay.test.ts PASS
Manual: warp past deadline Anvil :8547 + indexer :3102 + npm run dev :5175; anvil_setNextBlockTimestamp past last_buy_deadline_sec; Playwright headless PASS (expired-pending-roll)
E2E transition spec YIELDOMEGA_E2E_INDEXER=1 bash scripts/e2e-anvil.sh SKIP — deferred to sibling issue per test plan

Attack / abuse checks

Check Result
Malicious future deadlines degrade to syncing PASS — isSuspiciousDeadline + arenaTransitionState.test.ts
Roll CTA behind ChainMismatchWriteBarrier PASS — ArenaPodiumRollCta.tsx
Skew must not show huge positive countdowns on expiry PASS — podiumCountdownSec clamps with Math.max(0, …)

Invariant

INV-FRONTEND-343-TRANSITION-UX documented in docs/testing/invariants-and-business-logic.md and docs/frontend/arena-views.md.

## Verification — #343 Play-route transition state model and UX Verified on `main` (2026-06-26). All acceptance criteria **PASS**; no repo changes required. ### Acceptance criteria | Criterion | Result | How verified | |-----------|--------|--------------| | Distinct UX for timer-at-zero vs live countdown | **PASS** | `arenaTransitionState.test.ts`; manual Anvil warp → Playwright on `http://127.0.0.1:5175/` found `data-testid="arena-timer-expired-pending-roll"` (×2) + `arena-timer-transition-foot` with copy *"Waiting for settlement — roll the epoch or wait for the next qualifying buy."* and `arena-podium-roll-cta` | | Hero + side chips share `chainNowSec` skew | **PASS** | `indexerFirstDisplay.test.ts` (#343 skew guard); `ArenaTimerChips.test.tsx`; `arenaChainNow.test.ts` | | Transition state model documented in `arena-views.md` | **PASS** | [arena-views §343](docs/frontend/arena-views.md#play-route-transition-ux-gitlab-343) — state table, shared skew, E2E hooks; links to [time-arena.md § Podium epoch roll](docs/product/time-arena.md) | | No new browser RPC display mirrors when indexer URL set | **PASS** | `indexerFirstDisplay.test.ts` (static guard for podium/timer hooks) | | `data-testid` hooks for settling/pending-roll | **PASS** | Documented in arena-views §343; `transitionStateTestId()` in `arenaTransitionState.ts`; manual Playwright confirmed `arena-timer-expired-pending-roll`, `arena-timer-transition-foot`, `arena-podium-roll-cta` | ### Test plan | Path | Command | Result | |------|---------|--------| | Frontend unit | `cd frontend && npm run typecheck && npm run lint && npm test` | **PASS** (760 tests) | | Timer skew / transition derivation | `arenaTransitionState.test.ts`, `arenaChainNow.test.ts`, `ArenaTimerChips.test.tsx` | **PASS** | | Indexer-first guard | `indexerFirstDisplay.test.ts` | **PASS** | | Manual: warp past deadline | Anvil `:8547` + indexer `:3102` + `npm run dev :5175`; `anvil_setNextBlockTimestamp` past `last_buy_deadline_sec`; Playwright headless | **PASS** (`expired-pending-roll`) | | E2E transition spec | `YIELDOMEGA_E2E_INDEXER=1 bash scripts/e2e-anvil.sh` | **SKIP** — deferred to sibling issue per test plan | ### Attack / abuse checks | Check | Result | |-------|--------| | Malicious future deadlines degrade to `syncing` | **PASS** — `isSuspiciousDeadline` + `arenaTransitionState.test.ts` | | Roll CTA behind `ChainMismatchWriteBarrier` | **PASS** — `ArenaPodiumRollCta.tsx` | | Skew must not show huge positive countdowns on expiry | **PASS** — `podiumCountdownSec` clamps with `Math.max(0, …)` | ### Invariant **`INV-FRONTEND-343-TRANSITION-UX`** documented in `docs/testing/invariants-and-business-logic.md` and `docs/frontend/arena-views.md`.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-26 06:47:16 +00:00
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#343
No description provided.