Simple buy card: clarify projected effect pills with live +Xs, streak, and per-source BP #227

Closed
opened 2026-05-21 04:38:40 +00:00 by PlasticDigits · 6 comments
PlasticDigits commented 2026-05-21 04:38:40 +00:00 (Migrated from gitlab.com)

Summary

The Projected effects pill rail on the Simple buy card (/timecurve) is hard to parse and does not reflect live timer pressure. Copy like “Hard-reset timer toward 15m” hides the actual seconds a buy would add; “+120s time-booster credit” duplicates the timer pill; BP bonuses are lumped into one vague chip; and defended-streak outcomes are ambiguous. Pills should tick with the hero countdown (especially under the 13-minute hard-reset band) and show one short, numeric chip per effect.

What is wrong today

Implementation lives in frontend/src/pages/timecurve/timeCurveBuyProjectedEffects.ts, rendered from TimeCurveSimplePage.tsx via TimeCurveBuyProjectedEffects.tsx.

Area Current behavior Problem
Timer / time booster When secondsRemaining < 780 (13m): “Hard-reset timer toward 15m”. When timerExtensionPreview > 0: both +{n}s timer and +{n}s time-booster credit. User cannot see how many seconds the buy adds. Time-booster pill is a duplicate of the timer seconds pill (time-booster podium tracks cumulative actualSecondsAdded; the preview only needs one +{x}s chip). Under 13m, x grows every second as remaining falls (900 − remaining before cap) but the UI shows static prose instead.
Defended streak When secondsRemaining < 900 (15m): either “Continue your streak (N → N+1)” or “Start or break defended streak”. Does not distinguish continue own streak vs break someone else’s streak vs start a new streak when no active holder. Uses wallet activeDefendedStreak only; streak-break BP depends on _dsLastUnderWindowBuyer (internal, not exposed) per TimeCurve.sol _processDefendedStreak.
Battle Points Single combined chip: “+250 BP base”, “+250 BP + reset bonus”, or “+250 BP + reset/clutch bonuses”. Onchain awards are separate line items (docs/product/primitives.md): base 250, hard-reset 500, clutch 150 (remaining < 30s), streak-break priorActiveStreak × 100, ambush 200 (hard reset + streak break same tx). User wants one pill per source with exact amount + 1–2 word label.
Realtime saleCountdownSec from useTimecurveHeroTimer already recomputes every 1s; timerExtensionPreviewSec in useTimeCurveSaleSession depends on it. Hard-reset branch bypasses numeric preview; BP/streak pills do not re-evaluate as remaining crosses 780s / 900s / 30s thresholds every tick.

Reference — onchain timer math (TimeMath.extendDeadlineOrResetBelowThreshold):

  • remaining ≥ 780: add min(timerExtensionSec, timerCapSec − remaining) (canonical extension 120s, capped).
  • remaining < 780: snap deadline toward now + 900 (15m remaining), capped by timerCapSec; actualSecondsAdded = newDeadline − oldDeadline (typically 900 − remaining when cap allows).

Python mirror: simulations/timecurve_sim/model.py (extend_deadline_or_reset_below_threshold, warbow_buy_bp_delta).

Note: Arena buy hub is intended to share this helper (file comment / #191) but only Simple renders the rail today; keep Simple + Arena aligned when Arena adopts it.

1. Shared pure preview helper (TS)

Add e.g. previewBuyTimerSecondsAdded + previewBuyWarBowBpPills in a small module under frontend/src/lib/ (or extend timeCurveBuyProjectedEffects.ts) mirroring:

Read constants from chain in session (same pattern as timerExtensionSecR) — do not hardcode 250/500/150 if deployment can differ.

2. Timer pill — single +{x}s

  • Remove +{n}s timer and +{n}s time-booster credit dual chips.
  • Always show exactly one timer pill when x > 0: +{x}s (nothing else on that pill).
  • When remaining < 780, compute x from hard-reset branch (not timerExtensionPreview alone).
  • When x === 0 (timer at cap — see describeTimerPreview), omit timer pill or show a minimal “Timer capped” chip (product call).

3. Realtime wiring

  • Ensure buildTimeCurveBuyProjectedEffectLines (or successor) receives secondsRemaining that updates every 1s — already available via hero timer; confirm useMemo deps include it (they do on Simple; avoid latch freezing countdown during active sale except on brief RPC gaps).
  • Recompute x, streak pills, and BP pills on each tick so crossing 780 → 779, 30 → 29, etc. updates pills without waiting for indexer poll.

4. Defended streak — separate pills

When remaining < 900 (DEFENDED_STREAK_WINDOW_SEC):

Condition Pill (example)
Viewer is _dsLastUnderWindowBuyer equivalent and activeDefendedStreak > 0 Streak +1 or Continue streak (short; optional (N→N+1) if space)
Another wallet holds active streak under window (need holder address + their activeDefendedStreak) Break streak +{prior×100} BP or split: Break streak + BP pill
No qualifying holder / viewer starts fresh Start streak

Streak holder discovery (pick one):

  • Preferred (accurate): add a public view on TimeCurve for _dsLastUnderWindowBuyer (small contract change + indexer optional), or
  • Frontend-only v1: infer holder from latest qualifying buy in recentBuys / indexer head when actual_seconds_added > 0 and buy was under 15m window — document staleness if no indexer.

Do not show a generic “Start or break defended streak” combo pill.

5. BP — one pill per source

Replace combined BP chip with only the pills that apply to this hypothetical buy, e.g.:

  • +250 Base
  • +500 Reset (when hard-reset branch)
  • +150 Clutch (when remaining < 30)
  • +{prior×100} Streak break (when breaking another holder’s active streak)
  • +200 Ambush (when hard reset and streak break in same tx)

Omit zero amounts. Keep “Become latest buyer” (and WarBow flag pills) as separate non-BP chips.

6. Tests & docs

Acceptance criteria

  • Exactly one timer seconds pill when extension > 0, formatted +{x}s only (no “timer”, “time-booster”, or “hard reset toward 15m” prose on that pill).
  • No time-booster credit pill in projected effects (time-booster podium remains elsewhere).
  • Under 13m remaining, x increases by ~1 each second as the hero countdown ticks (e.g. at 12:00 remaining, x ≈ 180; at 11:59, x ≈ 181).
  • At ≥13m remaining with normal extension, pill shows +120s (or cap-truncated value), updating if cap binds.
  • Defended streak: distinct pills for continue / break / start — never a combined “start or break” string.
  • BP: separate pill per awarding source with exact numeric amount and ≤2 word label; no combined “+250 BP + reset/clutch bonuses”.
  • WarBow flag opt-in pills unchanged in intent; CHARM / CL8Y spend pills unchanged (#191).
  • Vitest coverage for timer + BP + streak preview matrix.

Verification checklist

Manual (local stack — docs/testing/qa-local-full-stack.md):

  • SKIP_ANVIL_RICH_STATE=1 + bot swarm or manual buys: open /timecurve with wallet connected during saleActive.
  • Calm timer (remaining > 15m): projected effects show +120s (or cap-limited) single timer pill; +250 Base BP pill; no time-booster duplicate.
  • 13m band: warp or wait until remaining is 779s → 778s; confirm timer pill switches from +120s to +{900−remaining}s and increments every second while watching hero digits.
  • Clutch: at remaining < 30s, confirm +150 Clutch pill appears in addition to base/reset pills.
  • Defended streak window (remaining < 15m): after a buy establishes a streak, second wallet sees break copy + +{N×100} Streak break BP pill; same wallet sees continue pill.
  • Timer cap edge: when remaining ≈ timerCapSec, timer pill omits or shows capped state; no false +120s.
  • Reduced motion / offline: pills do not throw; latch behavior from #82 still prevents flicker on brief RPC gaps.

Automated:

  • npm test -- timeCurveBuyProjectedEffects (and new helper tests) pass in frontend/.
  • Product rules: docs/product/primitives.md — timer hard reset, defended streak, BP table.
  • Hero timer / phase: #48, #115.
  • Buy hub numeric formatting: #191.
  • Recent-buy timer chip (good +Ns precedent): TimeCurveSections.tsx / lastExtension on Simple timer panel.
## Summary The **Projected effects** pill rail on the Simple buy card (`/timecurve`) is hard to parse and does not reflect live timer pressure. Copy like **“Hard-reset timer toward 15m”** hides the actual seconds a buy would add; **“+120s time-booster credit”** duplicates the timer pill; BP bonuses are lumped into one vague chip; and defended-streak outcomes are ambiguous. Pills should tick with the hero countdown (especially under the 13-minute hard-reset band) and show **one short, numeric chip per effect**. ## What is wrong today Implementation lives in [`frontend/src/pages/timecurve/timeCurveBuyProjectedEffects.ts`](frontend/src/pages/timecurve/timeCurveBuyProjectedEffects.ts), rendered from [`TimeCurveSimplePage.tsx`](frontend/src/pages/TimeCurveSimplePage.tsx) via [`TimeCurveBuyProjectedEffects.tsx`](frontend/src/pages/timecurve/TimeCurveBuyProjectedEffects.tsx). | Area | Current behavior | Problem | |------|------------------|---------| | **Timer / time booster** | When `secondsRemaining < 780` (13m): **“Hard-reset timer toward 15m”**. When `timerExtensionPreview > 0`: **both** `+{n}s timer` **and** `+{n}s time-booster credit`. | User cannot see **how many seconds** the buy adds. Time-booster pill is a duplicate of the timer seconds pill (time-booster podium tracks cumulative `actualSecondsAdded`; the preview only needs **one** `+{x}s` chip). Under 13m, `x` grows every second as remaining falls (`900 − remaining` before cap) but the UI shows static prose instead. | | **Defended streak** | When `secondsRemaining < 900` (15m): either **“Continue your streak (N → N+1)”** or **“Start or break defended streak”**. | Does not distinguish **continue own streak** vs **break someone else’s streak** vs **start a new streak** when no active holder. Uses wallet `activeDefendedStreak` only; streak-break BP depends on **`_dsLastUnderWindowBuyer`** (internal, not exposed) per [`TimeCurve.sol`](contracts/src/TimeCurve.sol) `_processDefendedStreak`. | | **Battle Points** | Single combined chip: **“+250 BP base”**, **“+250 BP + reset bonus”**, or **“+250 BP + reset/clutch bonuses”**. | Onchain awards are **separate line items** ([`docs/product/primitives.md`](docs/product/primitives.md)): base **250**, hard-reset **500**, clutch **150** (`remaining < 30s`), streak-break **`priorActiveStreak × 100`**, ambush **200** (hard reset + streak break same tx). User wants **one pill per source** with exact amount + 1–2 word label. | | **Realtime** | `saleCountdownSec` from [`useTimecurveHeroTimer`](frontend/src/pages/timecurve/useTimecurveHeroTimer.ts) already recomputes **every 1s**; `timerExtensionPreviewSec` in [`useTimeCurveSaleSession`](frontend/src/pages/timecurve/useTimeCurveSaleSession.ts) depends on it. | Hard-reset branch bypasses numeric preview; BP/streak pills do not re-evaluate as remaining crosses **780s / 900s / 30s** thresholds every tick. | **Reference — onchain timer math** ([`TimeMath.extendDeadlineOrResetBelowThreshold`](contracts/src/libraries/TimeMath.sol)): - `remaining ≥ 780`: add `min(timerExtensionSec, timerCapSec − remaining)` (canonical extension **120s**, capped). - `remaining < 780`: snap deadline toward **`now + 900`** (15m remaining), capped by `timerCapSec`; `actualSecondsAdded = newDeadline − oldDeadline` (typically **`900 − remaining`** when cap allows). Python mirror: [`simulations/timecurve_sim/model.py`](simulations/timecurve_sim/model.py) (`extend_deadline_or_reset_below_threshold`, `warbow_buy_bp_delta`). **Note:** Arena buy hub is intended to share this helper (file comment / [#191](https://gitlab.com/PlasticDigits/yieldomega/-/issues/191)) but only Simple renders the rail today; keep Simple + Arena aligned when Arena adopts it. ## Recommended fix ### 1. Shared pure preview helper (TS) Add e.g. `previewBuyTimerSecondsAdded` + `previewBuyWarBowBpPills` in a small module under `frontend/src/lib/` (or extend `timeCurveBuyProjectedEffects.ts`) mirroring: - [`TimeMath.extendDeadlineOrResetBelowThreshold`](contracts/src/libraries/TimeMath.sol) for **`+{x}s`** - [`TimeCurve.buy`](contracts/src/TimeCurve.sol) BP branch order for pill list - Constants from **`TimeCurve` public getters** already in [`frontend/public/abis/TimeCurve.json`](frontend/public/abis/TimeCurve.json): `WARBOW_BASE_BUY_BP`, `WARBOW_TIMER_RESET_BONUS_BP`, `WARBOW_CLUTCH_BONUS_BP`, `WARBOW_STREAK_BREAK_MULT_BP`, `WARBOW_AMBUSH_BONUS_BP`, `TIMER_RESET_BELOW_REMAINING_SEC`, `TIMER_RESET_TO_REMAINING_SEC`, `DEFENDED_STREAK_WINDOW_SEC` Read constants from chain in session (same pattern as `timerExtensionSecR`) — do **not** hardcode 250/500/150 if deployment can differ. ### 2. Timer pill — single `+{x}s` - **Remove** `+{n}s timer` and `+{n}s time-booster credit` dual chips. - **Always** show exactly one timer pill when `x > 0`: **`+{x}s`** (nothing else on that pill). - When `remaining < 780`, compute `x` from hard-reset branch (not `timerExtensionPreview` alone). - When `x === 0` (timer at cap — see [`describeTimerPreview`](frontend/src/lib/timeCurveUx.ts)), omit timer pill or show a minimal **“Timer capped”** chip (product call). ### 3. Realtime wiring - Ensure `buildTimeCurveBuyProjectedEffectLines` (or successor) receives **`secondsRemaining` that updates every 1s** — already available via hero timer; confirm `useMemo` deps include it (they do on Simple; avoid latch freezing countdown during active sale except on brief RPC gaps). - Recompute **`x`, streak pills, and BP pills** on each tick so crossing **780 → 779**, **30 → 29**, etc. updates pills without waiting for indexer poll. ### 4. Defended streak — separate pills When `remaining < 900` (`DEFENDED_STREAK_WINDOW_SEC`): | Condition | Pill (example) | |-----------|----------------| | Viewer is `_dsLastUnderWindowBuyer` equivalent **and** `activeDefendedStreak > 0` | **`Streak +1`** or **`Continue streak`** (short; optional `(N→N+1)` if space) | | Another wallet holds active streak under window (need holder address + their `activeDefendedStreak`) | **`Break streak +{prior×100} BP`** or split: **`Break streak`** + BP pill | | No qualifying holder / viewer starts fresh | **`Start streak`** | **Streak holder discovery (pick one):** - **Preferred (accurate):** add a **public view** on `TimeCurve` for `_dsLastUnderWindowBuyer` (small contract change + indexer optional), **or** - **Frontend-only v1:** infer holder from latest qualifying buy in **`recentBuys`** / indexer head when `actual_seconds_added > 0` and buy was under 15m window — document staleness if no indexer. Do **not** show a generic **“Start or break defended streak”** combo pill. ### 5. BP — one pill per source Replace combined BP chip with **only the pills that apply** to this hypothetical buy, e.g.: - `+250 Base` - `+500 Reset` (when hard-reset branch) - `+150 Clutch` (when `remaining < 30`) - `+{prior×100} Streak break` (when breaking another holder’s active streak) - `+200 Ambush` (when hard reset **and** streak break in same tx) Omit zero amounts. Keep **“Become latest buyer”** (and WarBow flag pills) as separate non-BP chips. ### 6. Tests & docs - Extend [`timeCurveBuyProjectedEffects.test.ts`](frontend/src/pages/timecurve/timeCurveBuyProjectedEffects.test.ts): hard-reset `+300s` at 600s remaining; no duplicate time-booster; BP decomposition at `<30s` + hard reset; streak-break amount. - Add Vitest for pure timer helper vs [`TimeMath`](contracts/src/libraries/TimeMath.sol) / Python sim fixtures. - Short contributor note in [`docs/frontend/timecurve-views.md`](docs/frontend/timecurve-views.md) + invariant row in [`docs/testing/invariants-and-business-logic.md`](docs/testing/invariants-and-business-logic.md) if warranted. ## Acceptance criteria - [ ] Exactly **one** timer seconds pill when extension > 0, formatted **`+{x}s`** only (no “timer”, “time-booster”, or “hard reset toward 15m” prose on that pill). - [ ] **No** `time-booster credit` pill in projected effects (time-booster podium remains elsewhere). - [ ] Under 13m remaining, **`x` increases by ~1 each second** as the hero countdown ticks (e.g. at 12:00 remaining, `x ≈ 180`; at 11:59, `x ≈ 181`). - [ ] At ≥13m remaining with normal extension, pill shows **`+120s`** (or cap-truncated value), updating if cap binds. - [ ] Defended streak: **distinct** pills for continue / break / start — never a combined “start or break” string. - [ ] BP: **separate pill per awarding source** with exact numeric amount and ≤2 word label; no combined “+250 BP + reset/clutch bonuses”. - [ ] WarBow flag opt-in pills unchanged in intent; CHARM / CL8Y spend pills unchanged ([#191](https://gitlab.com/PlasticDigits/yieldomega/-/issues/191)). - [ ] Vitest coverage for timer + BP + streak preview matrix. ## Verification checklist Manual (local stack — [`docs/testing/qa-local-full-stack.md`](docs/testing/qa-local-full-stack.md)): - [ ] **`SKIP_ANVIL_RICH_STATE=1`** + bot swarm or manual buys: open `/timecurve` with wallet connected during **`saleActive`**. - [ ] **Calm timer** (`remaining > 15m`): projected effects show **`+120s`** (or cap-limited) single timer pill; **`+250 Base`** BP pill; no time-booster duplicate. - [ ] **13m band**: warp or wait until `remaining` is **779s → 778s**; confirm timer pill switches from `+120s` to **`+{900−remaining}s`** and **increments every second** while watching hero digits. - [ ] **Clutch**: at **`remaining < 30s`**, confirm **`+150 Clutch`** pill appears in addition to base/reset pills. - [ ] **Defended streak window** (`remaining < 15m`): after a buy establishes a streak, second wallet sees **break** copy + **`+{N×100} Streak break`** BP pill; same wallet sees **continue** pill. - [ ] **Timer cap edge**: when remaining ≈ `timerCapSec`, timer pill omits or shows capped state; no false `+120s`. - [x] **Reduced motion / offline**: pills do not throw; latch behavior from [#82](https://gitlab.com/PlasticDigits/yieldomega/-/issues/82) still prevents flicker on brief RPC gaps. Automated: - [ ] `npm test -- timeCurveBuyProjectedEffects` (and new helper tests) pass in `frontend/`. ## Related - Product rules: [`docs/product/primitives.md`](docs/product/primitives.md) — timer hard reset, defended streak, BP table. - Hero timer / phase: [#48](https://gitlab.com/PlasticDigits/yieldomega/-/issues/48), [#115](https://gitlab.com/PlasticDigits/yieldomega/-/issues/115). - Buy hub numeric formatting: [#191](https://gitlab.com/PlasticDigits/yieldomega/-/issues/191). - Recent-buy timer chip (good `+Ns` precedent): `TimeCurveSections.tsx` / `lastExtension` on Simple timer panel.
Brouie commented 2026-05-21 05:26:40 +00:00 (Migrated from gitlab.com)

Walked what's walkable on this pre-ship. Goal was to establish a clean baseline of current behavior before the refactor lands, and verify the items that don't require new code.

Automated tests (current code on main @ 8fd0841):

  • npm test -- timeCurveBuyProjectedEffects → 4/4 PASS (existing test file, pre-refactor baseline)
  • npm test (full suite) → 534/534 PASS across 93 files

So the existing test surface is green going into the refactor.

Current-state baseline (production /timecurve, sale live with timer transitions captured):

Captured the pill rail at two timer states during the same session — calm timer (>13m remaining) and hard-reset band (<13m remaining). All four "what is wrong today" items from the issue body are visible and reproducible.

At 14:35 remaining (calm band, no throttling): +9.029 CHARM · 11.48 CL8Y spend · +120s timer · +120s time-booster credit · Start or break defended streak · +250 BP base · Become latest buyer

image.png{width=900 height=469}

At 14:10 remaining (calm band, offline mode engaged with prefers-reduced-motion: reduce): Same pill set, rendered cleanly under both emulations.

image.png{width=900 height=474}

At 11:33 remaining (after crossing 13:00 hard-reset threshold, offline still on): +9.028 CHARM · 11.48 CL8Y spend · Hard-reset timer toward 15m · +120s time-booster credit · Start or break defended streak · +250 BP + reset bonus · Become latest buyer

image.png{width=900 height=464}

Confirms each of the four points in the "What is wrong today" table:

  1. Timer / time booster duplication: +120s timer + +120s time-booster credit ship as two separate pills in the calm band. After crossing 13:00 the timer pill swaps to Hard-reset timer toward 15m prose but the +120s time-booster credit pill stays as a second timer-related pill. Both states ship two pills covering the same mechanic.
  2. Hard-reset hides numbers: Under 13m the timer pill becomes Hard-reset timer toward 15m — pure prose, no +{x}s value. At 11:33 remaining the actual onchain hard-reset would add ~207s (900 − 693), invisible to the user.
  3. BP lumped into one chip: At <13m the BP pill becomes +250 BP + reset bonus — exact case from the issue body. Reset bonus is not surfaced as its own +{n} pill.
  4. Defended streak ambiguous: Start or break defended streak combined string visible in both timer states — exactly the string the acceptance criteria says should never appear.

Acceptance criteria items walkable pre-ship:

Item Status Notes
Reduced motion / offline: pills do not throw PASS DevTools Rendering → prefers-reduced-motion: reduce engaged simultaneously with Network → Offline. Full pill rail rendered cleanly across both timer states (calm + hard-reset band). 2,626 console errors from offline RPC blackholing, no React error boundaries triggered, no missing components. Hero timer continued ticking client-side throughout.
Vitest coverage for timer + BP + streak preview matrix Existing 4 tests PASS; new matrix tests don't exist yet (expected — issue is pre-ship) Will re-run on QA server after refactor lands

Acceptance criteria items NOT walkable pre-ship (blocked on dev refactor or live sale state):

  • Exactly one timer seconds pill / no time-booster credit pill / under-13m +{900-remaining}s increment-by-second behavior / hard-reset cap binding / streak continue-vs-break-vs-start distinct pills / BP one-pill-per-source

These all require the refactor to land first — they describe the target behavior, not current.

Verification checklist items walkable pre-ship:

  • Automated test pass on current code: PASS (above)
  • Reduced motion / offline: PASS (above)

Verification checklist items blocked pre-ship:

  • SKIP_ANVIL_RICH_STATE=1 + bot swarm — requires Anvil local stack (separate setup)
  • Calm timer +120s / 13m band warp / clutch <30s / streak window — all describe post-refactor target behavior on Anvil; existing pre-refactor behavior captured above for diff reference
  • Timer cap edge — Anvil-only post-sale

Notes for the refactor:

  1. Issue body says "Read constants from chain in session... do not hardcode 250/500/150" — agree. The hardcoded examples in the issue body itself (+250 Base, +500 Reset, +150 Clutch) should be templated as +{base} Base etc. in the implementation so deployment-time values flow through.
  2. Streak holder discovery v1 (frontend-only inference from recentBuys head) can be wrong when the latest qualifying buy isn't the actual current _dsLastUnderWindowBuyer (e.g. holder buys → someone else buys outside window → third party buys under window — the inference would tag the third party, not the original holder). Worth either surfacing staleness in the chip or blocking the streak-break chip when uncertain. Preferred path (small contract change to expose _dsLastUnderWindowBuyer) is more robust if low cost.
  3. Verification checklist is heavily sale-dependent. Once the refactor ships, most items need either Anvil walking (warp to 779s, warp under 30s for clutch, etc.) or the next live sale. Worth deciding upfront whether you want me to walk the post-ship verification on Anvil or wait for next live sale.

Pre-ship baseline locked in. Ready to re-walk against the refactor when it lands.

Walked what's walkable on this pre-ship. Goal was to establish a clean baseline of current behavior before the refactor lands, and verify the items that don't require new code. **Automated tests (current code on main @ 8fd0841):** * `npm test -- timeCurveBuyProjectedEffects` → 4/4 PASS (existing test file, pre-refactor baseline) * `npm test` (full suite) → 534/534 PASS across 93 files So the existing test surface is green going into the refactor. **Current-state baseline (production /timecurve, sale live with timer transitions captured):** Captured the pill rail at two timer states during the same session — calm timer (\>13m remaining) and hard-reset band (\<13m remaining). All four "what is wrong today" items from the issue body are visible and reproducible. _At 14:35 remaining (calm band, no throttling):_ `+9.029 CHARM` · `11.48 CL8Y spend` · `+120s timer` · `+120s time-booster credit` · `Start or break defended streak` · `+250 BP base` · `Become latest buyer` ![image.png](/uploads/db607b802b127139b7177009edfc7ec1/image.png){width=900 height=469} _At 14:10 remaining (calm band, offline mode engaged with `prefers-reduced-motion: reduce`):_ Same pill set, rendered cleanly under both emulations. ![image.png](/uploads/2e87091188c69df7befb5fd052ae91e9/image.png){width=900 height=474} _At 11:33 remaining (after crossing 13:00 hard-reset threshold, offline still on):_ `+9.028 CHARM` · `11.48 CL8Y spend` · `Hard-reset timer toward 15m` · `+120s time-booster credit` · `Start or break defended streak` · `+250 BP + reset bonus` · `Become latest buyer` ![image.png](/uploads/ae1ac135d41a98ceb52ebc43f29e5da9/image.png){width=900 height=464} Confirms each of the four points in the "What is wrong today" table: 1. **Timer / time booster duplication:** `+120s timer` + `+120s time-booster credit` ship as two separate pills in the calm band. After crossing 13:00 the timer pill swaps to `Hard-reset timer toward 15m` prose but the `+120s time-booster credit` pill stays as a second timer-related pill. Both states ship two pills covering the same mechanic. 2. **Hard-reset hides numbers:** Under 13m the timer pill becomes `Hard-reset timer toward 15m` — pure prose, no `+{x}s` value. At 11:33 remaining the actual onchain hard-reset would add \~207s (900 − 693), invisible to the user. 3. **BP lumped into one chip:** At \<13m the BP pill becomes `+250 BP + reset bonus` — exact case from the issue body. Reset bonus is not surfaced as its own +{n} pill. 4. **Defended streak ambiguous:** `Start or break defended streak` combined string visible in both timer states — exactly the string the acceptance criteria says should never appear. **Acceptance criteria items walkable pre-ship:** | Item | Status | Notes | |------|--------|-------| | Reduced motion / offline: pills do not throw | PASS | DevTools Rendering → `prefers-reduced-motion: reduce` engaged simultaneously with Network → Offline. Full pill rail rendered cleanly across both timer states (calm + hard-reset band). 2,626 console errors from offline RPC blackholing, no React error boundaries triggered, no missing components. Hero timer continued ticking client-side throughout. | | Vitest coverage for timer + BP + streak preview matrix | Existing 4 tests PASS; new matrix tests don't exist yet (expected — issue is pre-ship) | Will re-run on QA server after refactor lands | **Acceptance criteria items NOT walkable pre-ship (blocked on dev refactor or live sale state):** * Exactly one timer seconds pill / no time-booster credit pill / under-13m `+{900-remaining}s` increment-by-second behavior / hard-reset cap binding / streak continue-vs-break-vs-start distinct pills / BP one-pill-per-source These all require the refactor to land first — they describe the target behavior, not current. **Verification checklist items walkable pre-ship:** * Automated test pass on current code: PASS (above) * Reduced motion / offline: PASS (above) **Verification checklist items blocked pre-ship:** * SKIP_ANVIL_RICH_STATE=1 + bot swarm — requires Anvil local stack (separate setup) * Calm timer +120s / 13m band warp / clutch \<30s / streak window — all describe post-refactor target behavior on Anvil; existing pre-refactor behavior captured above for diff reference * Timer cap edge — Anvil-only post-sale **Notes for the refactor:** 1. Issue body says "Read constants from chain in session... do not hardcode 250/500/150" — agree. The hardcoded examples in the issue body itself (`+250 Base`, `+500 Reset`, `+150 Clutch`) should be templated as `+{base} Base` etc. in the implementation so deployment-time values flow through. 2. Streak holder discovery v1 (frontend-only inference from `recentBuys` head) can be wrong when the latest qualifying buy isn't the actual current `_dsLastUnderWindowBuyer` (e.g. holder buys → someone else buys outside window → third party buys under window — the inference would tag the third party, not the original holder). Worth either surfacing staleness in the chip or blocking the streak-break chip when uncertain. Preferred path (small contract change to expose `_dsLastUnderWindowBuyer`) is more robust if low cost. 3. Verification checklist is heavily sale-dependent. Once the refactor ships, most items need either Anvil walking (warp to 779s, warp under 30s for clutch, etc.) or the next live sale. Worth deciding upfront whether you want me to walk the post-ship verification on Anvil or wait for next live sale. Pre-ship baseline locked in. Ready to re-walk against the refactor when it lands.
Brouie commented 2026-05-21 05:27:27 +00:00 (Migrated from gitlab.com)

marked the checklist item Reduced motion / offline: pills do not throw; latch behavior from #82 still prevents flicker on brief RPC gaps. as completed

marked the checklist item **Reduced motion / offline: pills do not throw; latch behavior from \#82 still prevents flicker on brief RPC gaps\.** as completed
PlasticDigits commented 2026-05-21 12:42:49 +00:00 (Migrated from gitlab.com)

mentioned in commit b0d4f9067c

mentioned in commit b0d4f9067c729bc6f6db6aecc454d4916558efe8
PlasticDigits commented 2026-05-21 12:42:55 +00:00 (Migrated from gitlab.com)

Implementation landed on main (b0d4f90)

Refactored Simple Projected effects pills per #227.

What changed

  • Added timeCurveBuyPreview.ts mirroring TimeMath.extendDeadlineOrResetBelowThreshold and WarBow buy BP branches.
  • Timer: single +{x}s pill (ticks with hero countdown; under 13m uses 900 − remaining).
  • Removed duplicate time-booster credit pill and Hard-reset timer toward 15m prose.
  • Spend: -{3dp} CL8Y|ETH|USDM (e.g. -2.260 CL8Y).
  • BP: separate pills — +{n} BP Base, Reset, Clutch, Streak break, Ambush.
  • Defended streak: +1 streak (N), Break streak, or Start streak (holder inferred from indexer recentBuys head).
  • Copy: Become Last Buyer.
  • Docs/invariants/manual QA/skills cross-linked: INV-FRONTEND-227-BUY-PROJECTED, timecurve-views §227.

Automated tests

  • npm test -- timeCurveBuyPreview timeCurveBuyProjectedEffects — 14/14 PASS
  • Full frontend suite — 568/568 PASS

Verification checklist (please confirm on live or Anvil)

  • Calm timer (>13m): one +120s timer pill; +250 BP Base; no time-booster duplicate
  • Hard-reset band (<13m): +{900−remaining}s increments each second with hero digits
  • Spend chip shows negative 3-decimal asset (e.g. -2.260 CL8Y)
  • Clutch (<30s remaining): +150 BP Clutch pill appears
  • Defended streak window (<15m): continue vs break vs start pills are distinct
  • Timer cap: Timer capped or no false +120s
  • Reduced motion + offline: rail renders without React errors

@toddking — please verify on the next live sale window or local Anvil walk (SKIP_ANVIL_RICH_STATE=1 + warp to 779s / <30s). Issue stays open until sign-off.

## Implementation landed on `main` (b0d4f90) Refactored Simple **Projected effects** pills per [#227](https://gitlab.com/PlasticDigits/yieldomega/-/issues/227). ### What changed - Added [`timeCurveBuyPreview.ts`](frontend/src/lib/timeCurveBuyPreview.ts) mirroring `TimeMath.extendDeadlineOrResetBelowThreshold` and WarBow buy BP branches. - **Timer:** single `+{x}s` pill (ticks with hero countdown; under 13m uses `900 − remaining`). - **Removed** duplicate `time-booster credit` pill and `Hard-reset timer toward 15m` prose. - **Spend:** `-{3dp} CL8Y|ETH|USDM` (e.g. `-2.260 CL8Y`). - **BP:** separate pills — `+{n} BP Base`, `Reset`, `Clutch`, `Streak break`, `Ambush`. - **Defended streak:** `+1 streak (N)`, `Break streak`, or `Start streak` (holder inferred from indexer `recentBuys` head). - **Copy:** `Become Last Buyer`. - Docs/invariants/manual QA/skills cross-linked: `INV-FRONTEND-227-BUY-PROJECTED`, [timecurve-views §227](docs/frontend/timecurve-views.md#timecurve-buy-projected-effects-gitlab-227). ### Automated tests - `npm test -- timeCurveBuyPreview timeCurveBuyProjectedEffects` — **14/14 PASS** - Full frontend suite — **568/568 PASS** ### Verification checklist (please confirm on live or Anvil) - [ ] Calm timer (>13m): one `+120s` timer pill; `+250 BP Base`; no time-booster duplicate - [ ] Hard-reset band (<13m): `+{900−remaining}s` increments each second with hero digits - [ ] Spend chip shows negative 3-decimal asset (e.g. `-2.260 CL8Y`) - [ ] Clutch (<30s remaining): `+150 BP Clutch` pill appears - [ ] Defended streak window (<15m): continue vs break vs start pills are distinct - [ ] Timer cap: `Timer capped` or no false `+120s` - [ ] Reduced motion + offline: rail renders without React errors @toddking — please verify on the next live sale window or local Anvil walk (`SKIP_ANVIL_RICH_STATE=1` + warp to 779s / <30s). Issue stays open until sign-off.
PlasticDigits commented 2026-05-21 12:51:04 +00:00 (Migrated from gitlab.com)

mentioned in commit 3bd16a8068

mentioned in commit 3bd16a806884f9d5f36f27d07f5a96906301c09f
Brouie commented 2026-05-22 02:43:24 +00:00 (Migrated from gitlab.com)

Re-ran the touched tests on QA server post-refactor (against 2c2948a):

✓ src/lib/timeCurveBuyPreview.test.ts (8 tests)

✓ src/pages/timecurve/timeCurveBuyProjectedEffects.test.ts (6 tests) 14 passed

Matches your 14/14 from pre-merge. Shipped code is clean on the automated side.

Yesterday's pre-ship baseline (screenshots at 14:35 / 14:10 / 11:33 remaining capturing the 4 "what is wrong today" items) is now superseded by the refactor. Leaving the manual visual walk on the new pill rail to @totdking per your tag — the comparison against yesterday's baseline gives a clean before/after on this thread when he gets to it.

Re-ran the touched tests on QA server post-refactor (against `2c2948a`): `✓ src/lib/timeCurveBuyPreview.test.ts (8 tests) ` `✓ src/pages/timecurve/timeCurveBuyProjectedEffects.test.ts (6 tests) 14 passed` Matches your 14/14 from pre-merge. Shipped code is clean on the automated side. Yesterday's pre-ship baseline (screenshots at 14:35 / 14:10 / 11:33 remaining capturing the 4 "what is wrong today" items) is now superseded by the refactor. Leaving the manual visual walk on the new pill rail to @totdking per your tag — the comparison against yesterday's baseline gives a clean before/after on this thread when he gets to it.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-22 05:41:06 +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#227
No description provided.