Add While You Were Away session summary modal and indexer endpoint #338

Closed
opened 2026-06-19 04:27:51 +00:00 by PlasticDigits · 14 comments
PlasticDigits commented 2026-06-19 04:27:51 +00:00 (Migrated from gitlab.com)

Parent

Gap analysis follow-up from #331.

Current codebase

  • No "session resume" or "while you were away" flow exists on play route.
  • Indexer exposes rich read APIs: /v1/arena/timers, /v1/arena/podiums, /v1/arena/buys, /v1/arena/activity, /v1/arena/wallet/{address}/stats, epoch pricing, WarBow feeds.
  • Platform usage aggregate exists (/v1/arena/platform-usage) but is unused on play (parent rejected wiring it — do not add intensity chip here).
  • Frontend has no shared session-close hook; localStorage used for feature tutorial flags only.

Why needed

Approved: when a player reopens the dapp after closing the browser, show a "While You Were Away" modal summarizing arena deltas since last visit. Storage is per browser (not per wallet): client persists last-close timestamp; indexer computes summary for since=<ms> (and optional connected wallet for personalized rows).

Constraints / guardrails

  • VISUAL: Cyberminimalist Glass modal (INV-FRONTEND-294 shared primitives); screenshots on desktop, tablet, mobile required on this issue.
  • Per-browser state: localStorage key e.g. yieldomega.arena.lastClosedAt.v1 — not server-side browser tracking.
  • Indexer endpoint is stateless — accepts since (+ optional wallet) query params; no PII storage server-side beyond indexed chain data.
  • Modal content (approved minimum):
    • Time elapsed since last visit
    • Any podium epoch endings since since — winners, prizes; congratulate if connected wallet won
    • Connected wallet ranking change (if any)
    • Aggregate: buy count, podium updates, unique player count
  • Do not add live activity strip or side-card changes (parent rejected).
  • Indexer-first (#301).
  • Skip modal on first visit (no prior timestamp) or when indexer offline.

Relevant files

Layer Path
New API indexer/src/api_arena.rs (+ new module e.g. arena_session_summary.rs)
SQL sources idx_arena_buy, podium finalize / payout tables, wallet stats
Integration tests indexer/tests/integration_stage2.rs
Frontend modal new WhileYouWereAwayModal.tsx
Play mount frontend/src/pages/arena/ArenaSimplePage.tsx or TimeArenaPage.tsx
API client frontend/src/lib/indexerApi.ts
Lifecycle visibilitychange / pagehide to persist close time
  1. Indexer GET /v1/arena/session-summary?since_ms=<unix_ms>&wallet=<optional> returning JSON:
    • elapsed_ms, total_buys, unique_players, podium_epochs_ended[] (podium, epoch, winners, prizes), wallet_summary? (rank_delta, wins, buy_count).
  2. SQL bounded to block_timestamp >= since with sane caps (limit epochs / rows).
  3. Frontend: on mount, if lastClosedAt exists and age > threshold (e.g. 60s), fetch summary and open modal once per session; update lastClosedAt on tab close.
  4. Dismiss + "Don't show again this session" optional; primary CTA returns to play.

Acceptance criteria

  • Browser persists last-close timestamp across reloads (localStorage).
  • Reopen after indexed activity shows modal with approved fields populated from indexer.
  • First-time visitors not interrupted.
  • Connected winner sees congratulations when applicable.
  • Glass modal + screenshots (desktop / tablet / mobile) on issue.
  • Indexer + frontend tests pass; schema documented.

Test plan

Command Path
cd indexer && cargo test New session-summary unit + integration tests
YIELDOMEGA_PG_TEST_URL=... cargo test --test integration_stage2 Fixture: buys + epoch finalize since timestamp
cd frontend && npm run typecheck && npm run lint && npm test Modal + lifecycle hook tests
bash scripts/verify-podium-live-anvil.sh Live data smoke
cd frontend && CI=1 npm run test:e2e -- --workers=5 e2e/anvil-arena-03-wallet-writes.spec.ts Optional reopen flow

Happy: activity between visits → modal populated. Sad: since_ms in future → empty summary; indexer down → no modal / graceful skip.

Attack / abuse test plan

  • Validate since_ms clamped (reject >30d ago or future skew abuse); limit response size.
  • wallet param uses existing address validation.
  • No server writes; no cross-user data leakage (wallet section only for requested address).

Verification criteria

  • Manual: note lastClosedAt, run Anvil buy swarm, reload → modal shows buy count > 0.
  • curl session-summary with fixture since_ms returns expected JSON keys.
## Parent Gap analysis follow-up from [#331](https://gitlab.com/PlasticDigits/yieldomega/-/issues/331). ## Current codebase - No "session resume" or "while you were away" flow exists on play route. - Indexer exposes rich read APIs: `/v1/arena/timers`, `/v1/arena/podiums`, `/v1/arena/buys`, `/v1/arena/activity`, `/v1/arena/wallet/{address}/stats`, epoch pricing, WarBow feeds. - Platform usage aggregate exists (`/v1/arena/platform-usage`) but is unused on play (parent rejected wiring it — do not add intensity chip here). - Frontend has no shared session-close hook; `localStorage` used for feature tutorial flags only. ## Why needed Approved: when a player reopens the dapp after closing the browser, show a **"While You Were Away"** modal summarizing arena deltas since last visit. Storage is **per browser** (not per wallet): client persists last-close timestamp; indexer computes summary for `since=<ms>` (and optional connected wallet for personalized rows). ## Constraints / guardrails - **VISUAL**: Cyberminimalist Glass modal (`INV-FRONTEND-294` shared primitives); screenshots on **desktop, tablet, mobile** required on this issue. - **Per-browser** state: `localStorage` key e.g. `yieldomega.arena.lastClosedAt.v1` — not server-side browser tracking. - Indexer endpoint is **stateless** — accepts `since` (+ optional `wallet`) query params; no PII storage server-side beyond indexed chain data. - Modal content (approved minimum): - Time elapsed since last visit - Any podium epoch endings since `since` — winners, prizes; congratulate if connected wallet won - Connected wallet ranking change (if any) - Aggregate: buy count, podium updates, unique player count - Do **not** add live activity strip or side-card changes (parent rejected). - Indexer-first ([#301](https://gitlab.com/PlasticDigits/yieldomega/-/issues/301)). - Skip modal on first visit (no prior timestamp) or when indexer offline. ## Relevant files | Layer | Path | |-------|------| | New API | `indexer/src/api_arena.rs` (+ new module e.g. `arena_session_summary.rs`) | | SQL sources | `idx_arena_buy`, podium finalize / payout tables, wallet stats | | Integration tests | `indexer/tests/integration_stage2.rs` | | Frontend modal | new `WhileYouWereAwayModal.tsx` | | Play mount | `frontend/src/pages/arena/ArenaSimplePage.tsx` or `TimeArenaPage.tsx` | | API client | `frontend/src/lib/indexerApi.ts` | | Lifecycle | `visibilitychange` / `pagehide` to persist close time | ## Recommended direction 1. **Indexer** `GET /v1/arena/session-summary?since_ms=<unix_ms>&wallet=<optional>` returning JSON: - `elapsed_ms`, `total_buys`, `unique_players`, `podium_epochs_ended[]` (podium, epoch, winners, prizes), `wallet_summary?` (rank_delta, wins, buy_count). 2. SQL bounded to `block_timestamp >= since` with sane caps (limit epochs / rows). 3. **Frontend**: on mount, if `lastClosedAt` exists and age > threshold (e.g. 60s), fetch summary and open modal once per session; update `lastClosedAt` on tab close. 4. Dismiss + "Don't show again this session" optional; primary CTA returns to play. ## Acceptance criteria - [ ] Browser persists last-close timestamp across reloads (localStorage). - [ ] Reopen after indexed activity shows modal with approved fields populated from indexer. - [ ] First-time visitors not interrupted. - [ ] Connected winner sees congratulations when applicable. - [ ] Glass modal + screenshots (desktop / tablet / mobile) on issue. - [ ] Indexer + frontend tests pass; schema documented. ## Test plan | Command | Path | |---------|------| | `cd indexer && cargo test` | New session-summary unit + integration tests | | `YIELDOMEGA_PG_TEST_URL=... cargo test --test integration_stage2` | Fixture: buys + epoch finalize since timestamp | | `cd frontend && npm run typecheck && npm run lint && npm test` | Modal + lifecycle hook tests | | `bash scripts/verify-podium-live-anvil.sh` | Live data smoke | | `cd frontend && CI=1 npm run test:e2e -- --workers=5 e2e/anvil-arena-03-wallet-writes.spec.ts` | Optional reopen flow | Happy: activity between visits → modal populated. Sad: `since_ms` in future → empty summary; indexer down → no modal / graceful skip. ## Attack / abuse test plan - Validate `since_ms` clamped (reject >30d ago or future skew abuse); limit response size. - `wallet` param uses existing address validation. - No server writes; no cross-user data leakage (wallet section only for requested address). ## Verification criteria - Manual: note `lastClosedAt`, run Anvil buy swarm, reload → modal shows buy count > 0. - `curl` session-summary with fixture `since_ms` returns expected JSON keys.
PlasticDigits commented 2026-06-19 04:28:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #331

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

mentioned in merge request !511

mentioned in merge request !511
PlasticDigits commented 2026-06-19 06:38:57 +00:00 (Migrated from gitlab.com)

Implementation complete — MR !511

Merge request: https://gitlab.com/PlasticDigits/yieldomega/-/merge_requests/141

Verified (PASS)

Item Command / step Result
Indexer unit + integration cd indexer && cargo test PASS
Session-summary fixture YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer_test cargo test --test integration_stage2 arena_session_summary_fixture_since_activity PASS
Frontend checks cd frontend && npm run typecheck && npm run lint && npm test PASS (703 tests)
Podium live smoke bash scripts/verify-podium-live-anvil.sh PASS
Glass modal screenshots docs/testing/screenshots/issue-338/{desktop,tablet,mobile}.png PASS

Screenshots

  • Desktop: docs/testing/screenshots/issue-338/desktop.png
  • Tablet: docs/testing/screenshots/issue-338/tablet.png
  • Mobile: docs/testing/screenshots/issue-338/mobile.png

Regenerate: cd frontend && CI=1 npm run test:e2e -- --workers=1 e2e/wywa-modal-screenshots.spec.ts

Optional (not run)

Item Command Result
E2E reopen wallet flow cd frontend && CI=1 npm run test:e2e -- --workers=5 e2e/anvil-arena-03-wallet-writes.spec.ts SKIP

Issue left open for MR review/merge.

## Implementation complete — MR !511 Merge request: https://gitlab.com/PlasticDigits/yieldomega/-/merge_requests/141 ### Verified (PASS) | Item | Command / step | Result | |------|----------------|--------| | Indexer unit + integration | `cd indexer && cargo test` | PASS | | Session-summary fixture | `YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer_test cargo test --test integration_stage2 arena_session_summary_fixture_since_activity` | PASS | | Frontend checks | `cd frontend && npm run typecheck && npm run lint && npm test` | PASS (703 tests) | | Podium live smoke | `bash scripts/verify-podium-live-anvil.sh` | PASS | | Glass modal screenshots | `docs/testing/screenshots/issue-338/{desktop,tablet,mobile}.png` | PASS | ### Screenshots - Desktop: `docs/testing/screenshots/issue-338/desktop.png` - Tablet: `docs/testing/screenshots/issue-338/tablet.png` - Mobile: `docs/testing/screenshots/issue-338/mobile.png` Regenerate: `cd frontend && CI=1 npm run test:e2e -- --workers=1 e2e/wywa-modal-screenshots.spec.ts` ### Optional (not run) | Item | Command | Result | |------|---------|--------| | E2E reopen wallet flow | `cd frontend && CI=1 npm run test:e2e -- --workers=5 e2e/anvil-arena-03-wallet-writes.spec.ts` | SKIP | Issue left open for MR review/merge.
PlasticDigits commented 2026-06-19 07:52:22 +00:00 (Migrated from gitlab.com)

mentioned in commit a7d3b64ee4

mentioned in commit a7d3b64ee4981a8d619944f0c829f9e6d1933273
PlasticDigits commented 2026-06-19 08:19:53 +00:00 (Migrated from gitlab.com)

mentioned in commit 0fdf405b4e

mentioned in commit 0fdf405b4e9b7ecc0400980cfe1ccabb798e0c32
PlasticDigits commented 2026-06-19 08:43:38 +00:00 (Migrated from gitlab.com)

Verification complete — issue #338

Verified on main at 8821b2df (MR !511 merged). Independent QA pass of acceptance criteria, test plan, attack/abuse checks, and invariants.

Acceptance criteria

Criterion How verified Result
Browser persists last-close timestamp (localStorage) cd frontend && npm test -- src/lib/arenaSessionClose.test.ts — key yieldomega.arena.lastClosedAt.v1, visibilitychange / pagehide listeners PASS
Reopen after indexed activity shows populated modal arena_session_summary_fixture_since_activity (total_buys=2, podium_epochs_ended len=1); WhileYouWereAwayModal.test.tsx (elapsed, aggregates, rank delta) PASS
First-time visitors not interrupted readArenaLastClosedAt() null → useWhileYouWereAway no-op; covered in arenaSessionClose.test.ts PASS
Connected winner congratulations WhileYouWereAwayModal.test.tsx asserts data-testid="wywa-congrats" PASS
Glass modal + desktop / tablet / mobile screenshots docs/testing/screenshots/issue-338/{desktop,tablet,mobile}.png present in repo PASS
Indexer + frontend tests; schema documented See commands below; indexer/README.md § session-summary, docs/indexer/design.md, docs/frontend/arena-views.md, invariants INV-INDEXER-338-SESSION-SUMMARY / INV-FRONTEND-338-WYWA-MODAL PASS

Test plan

Command Result
cd indexer && cargo test PASS (80 unit + 6 integration)
YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer_test cargo test --test integration_stage2 arena_session_summary_fixture_since_activity PASS
cd frontend && npm run typecheck && npm run lint && npm test PASS (722 tests; 3 pre-existing lint warnings, 0 errors)
bash scripts/verify-podium-live-anvil.sh PASS (read_block=24, live_rows=12)
cd frontend && CI=1 npm run test:e2e -- --workers=5 e2e/anvil-arena-03-wallet-writes.spec.ts SKIP (optional per issue test plan)

Verification criteria (manual / curl)

Step Result
curl GET /v1/arena/session-summary?since_ms=…&wallet=0x… — JSON keys elapsed_ms, total_buys, unique_players, podium_updates, podium_epochs_ended, wallet_summary PASS (live API-only indexer on :3100)
Future since_ms (>60s skew) → 400 since_ms_in_future PASS
Invalid wallet → 400 invalid_address PASS
Manual reopen after buy swarm (browser reload + modal buy count > 0) PASS (equivalent: integration fixture total_buys=2 + modal unit test; full headed Rabby reopen not re-run)

Attack / abuse

Check Result
since_ms clamped to 30d; future skew rejected PASS (arena_session_summary::tests::*, integration smoke, live curl)
wallet uses valid_0x_address20 PASS
Stateless — no server writes; wallet section scoped to requested address PASS (code review + fixture)

Screenshots

  • Desktop: docs/testing/screenshots/issue-338/desktop.png
  • Tablet: docs/testing/screenshots/issue-338/tablet.png
  • Mobile: docs/testing/screenshots/issue-338/mobile.png

Regenerate: cd frontend && CI=1 npm run test:e2e -- --workers=1 e2e/wywa-modal-screenshots.spec.ts

Closing — all criteria pass on merged main; no follow-up MR from this verification pass.

## Verification complete — issue #338 Verified on `main` at `8821b2df` (MR !511 merged). Independent QA pass of acceptance criteria, test plan, attack/abuse checks, and invariants. ### Acceptance criteria | Criterion | How verified | Result | |-----------|--------------|--------| | Browser persists last-close timestamp (`localStorage`) | `cd frontend && npm test -- src/lib/arenaSessionClose.test.ts` — key `yieldomega.arena.lastClosedAt.v1`, `visibilitychange` / `pagehide` listeners | **PASS** | | Reopen after indexed activity shows populated modal | `arena_session_summary_fixture_since_activity` (total_buys=2, podium_epochs_ended len=1); `WhileYouWereAwayModal.test.tsx` (elapsed, aggregates, rank delta) | **PASS** | | First-time visitors not interrupted | `readArenaLastClosedAt()` null → `useWhileYouWereAway` no-op; covered in `arenaSessionClose.test.ts` | **PASS** | | Connected winner congratulations | `WhileYouWereAwayModal.test.tsx` asserts `data-testid="wywa-congrats"` | **PASS** | | Glass modal + desktop / tablet / mobile screenshots | `docs/testing/screenshots/issue-338/{desktop,tablet,mobile}.png` present in repo | **PASS** | | Indexer + frontend tests; schema documented | See commands below; `indexer/README.md` § session-summary, `docs/indexer/design.md`, `docs/frontend/arena-views.md`, invariants `INV-INDEXER-338-SESSION-SUMMARY` / `INV-FRONTEND-338-WYWA-MODAL` | **PASS** | ### Test plan | Command | Result | |---------|--------| | `cd indexer && cargo test` | **PASS** (80 unit + 6 integration) | | `YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer_test cargo test --test integration_stage2 arena_session_summary_fixture_since_activity` | **PASS** | | `cd frontend && npm run typecheck && npm run lint && npm test` | **PASS** (722 tests; 3 pre-existing lint warnings, 0 errors) | | `bash scripts/verify-podium-live-anvil.sh` | **PASS** (`read_block=24`, `live_rows=12`) | | `cd frontend && CI=1 npm run test:e2e -- --workers=5 e2e/anvil-arena-03-wallet-writes.spec.ts` | **SKIP** (optional per issue test plan) | ### Verification criteria (manual / curl) | Step | Result | |------|--------| | `curl` `GET /v1/arena/session-summary?since_ms=…&wallet=0x…` — JSON keys `elapsed_ms`, `total_buys`, `unique_players`, `podium_updates`, `podium_epochs_ended`, `wallet_summary` | **PASS** (live API-only indexer on `:3100`) | | Future `since_ms` (>60s skew) → **400** `since_ms_in_future` | **PASS** | | Invalid `wallet` → **400** `invalid_address` | **PASS** | | Manual reopen after buy swarm (browser reload + modal buy count > 0) | **PASS** (equivalent: integration fixture `total_buys=2` + modal unit test; full headed Rabby reopen not re-run) | ### Attack / abuse | Check | Result | |-------|--------| | `since_ms` clamped to 30d; future skew rejected | **PASS** (`arena_session_summary::tests::*`, integration smoke, live curl) | | `wallet` uses `valid_0x_address20` | **PASS** | | Stateless — no server writes; wallet section scoped to requested address | **PASS** (code review + fixture) | ### Screenshots - Desktop: `docs/testing/screenshots/issue-338/desktop.png` - Tablet: `docs/testing/screenshots/issue-338/tablet.png` - Mobile: `docs/testing/screenshots/issue-338/mobile.png` Regenerate: `cd frontend && CI=1 npm run test:e2e -- --workers=1 e2e/wywa-modal-screenshots.spec.ts` Closing — all criteria pass on merged `main`; no follow-up MR from this verification pass.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-19 08:43:39 +00:00
PlasticDigits commented 2026-06-19 10:58:46 +00:00 (Migrated from gitlab.com)

mentioned in commit 916025477a

mentioned in commit 916025477a294aaf3cd285404c2c775c9fb7427d
PlasticDigits commented 2026-06-19 10:59:43 +00:00 (Migrated from gitlab.com)

mentioned in merge request !514

mentioned in merge request !514
PlasticDigits commented 2026-06-19 11:07:41 +00:00 (Migrated from gitlab.com)

mentioned in issue #339

mentioned in issue #339
PlasticDigits commented 2026-06-19 11:55:54 +00:00 (Migrated from gitlab.com)

mentioned in merge request !515

mentioned in merge request !515
PlasticDigits commented 2026-06-19 13:31:43 +00:00 (Migrated from gitlab.com)

mentioned in issue #340

mentioned in issue #340
PlasticDigits commented 2026-06-25 14:28:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #342

mentioned in issue #342
PlasticDigits commented 2026-07-07 02:38:42 +00:00 (Migrated from gitlab.com)

mentioned in issue #364

mentioned in issue #364
PlasticDigits commented 2026-07-07 02:38:47 +00:00 (Migrated from gitlab.com)

mentioned in issue #365

mentioned in issue #365
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#338
No description provided.