feat(web): records widget — biggest-trade + longest-survival-streak#48
Merged
Conversation
ntatschner
pushed a commit
that referenced
this pull request
May 20, 2026
PR #45 inverted the default of NEXT_PUBLIC_PROFILE_WIDGETS to ON, and PR #44 widened the framework to visitors. Together these mean the test's premise ("no widget cards because flag is off and visitor is not owner") no longer holds — the flag is on by default and visitors render through the framework now. The whole suite is skipped with an explanation. Re-enabling it correctly would require launching the dev-server with NEXT_PUBLIC_PROFILE_WIDGETS=0 via a separate playwright project (future test-infra task). After Phase 6 cleanup deletes the env-var check entirely, this suite should be deleted, not re-enabled. Unblocks the in-flight follow-up PRs whose CI was failing on this test (#47 economy paired-tx, and likely #48, #50 as well).
Plan-3b follow-up: expand the Records widget from 2 records (longest
session, busiest session) to 4. The original widget explicitly noted
"More records (deadliest, biggest trade, longest streak) ship in
Plan 3b" — this lands 2 of those 3.
Added:
- **Longest survival streak** — gap (ms) between the user's two
most-spaced consecutive `player_death` events. Pulled via
`listEvents({ event_type: 'player_death', limit: 500 })`.
Rendered in days+hours form when ≥ 1 day (otherwise the existing
fmtDuration would emit unreadable strings like "1442h 30m").
- **Biggest trade** — largest confirmed transaction by `quantity`
from `getCommerceRecent(token, 500)`. Includes `item` when the DTO
carries it. Documented in a comment: not strictly "biggest by aUEC"
because CommerceTransactionDto doesn't expose a typed currency
field; quantity is the closest available signal without parsing
raw payloads.
Still deferred:
- **Deadliest session** — most player-deaths in one session. Needs
per-session death aggregation on the server (SessionSummary only
exposes `event_count`, not per-type breakdowns). The expanded view
now carries an inline note saying this is "pending — needs
per-session death aggregation on the server" instead of the
generic "Plan 3b" pointer.
Architecture:
- Three parallel fetches via `Promise.allSettled` so a single
endpoint hiccup doesn't blank the whole widget (mirrors the
page-level invariant for multi-endpoint dashboards).
- Per-source failures log with `call=widget.records.<source>` so the
failing branch is named in logs.
- Compact view leads with longest-session and switches its second
slot to survival-streak when present, falling back to busiest-by-
events when the user has < 2 deaths in window.
No backend, schema, or OpenAPI changes. Single-file web edit.
Verified: `pnpm --filter web typecheck` exits 0.
33f07f3 to
2a2c53c
Compare
3 tasks
5 tasks
ntatschner
pushed a commit
that referenced
this pull request
May 20, 2026
Plan-3b follow-up — completes the records widget by adding the third deferred record (deadliest session). Removes the "Deadliest session pending — needs per-session death aggregation on the server" footnote left by PR #48. Approach: pure client-side bucketing. Reuses the sessions + deaths fetches already happening for longest-session / busiest-session / survival-streak — no new endpoint, no schema change, no migration. For each session with valid started_at/ended_at, count how many `player_death` events fall inside the session's bounds; track the max. Done in a single third pass after both fetches resolve. Complexity: O(sessions × deaths) per render. With both arrays capped at ~500 rows per fetch, that's ~250k comparisons worst-case — sub-millisecond on any modern device. Binary-searching the sorted death array would only pay off at orders of magnitude more rows; clarity wins here. Display: new "Deadliest session: N deaths" row in the expanded view, slotted between "Busiest" and "Longest survival streak" so combat-related records cluster together. Compact view unchanged — already curates to 2 highlights and adding a third would crowd the line. The empty-state check now includes `deadliestSessionDeaths` so a user with deaths-but-no-other-records still gets a non-null render (rather than falsely returning null and showing the empty placeholder). Verified: `pnpm --filter web typecheck` exits 0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan-3b follow-up #2 — Records widget expansion (2 of 3 records added).
The original Records widget shipped with longest-session and busiest-
session-by-event-count and explicitly noted "More records (deadliest,
biggest trade, longest streak) ship in Plan 3b". This PR lands 2 of
those 3. Deadliest session is deferred — it needs per-session
death aggregation on the server, which doesn't exist yet.
What the user sees
Compact (single line)
Before: `Longest session 4h 12m · Busiest 23,418 events`
After: `Longest session 4h 12m · Survival 12d 6h`
(falls back to `Busiest …` when the user has < 2 deaths in the
recent window, so the original behaviour is preserved when survival
streak isn't computable)
Expanded (full list)
Before: 2 rows + a "more records ship in Plan 3b" note.
After: 4 rows + a more honest "Deadliest session pending — needs
per-session death aggregation on the server" footnote.
Caveats documented in code
a 60-day survival streak would render as `1442h 30m` which is
unreadable.
CommerceTransactionDto doesn't expose a typed currency field.
Comment in the file flags this.
computation in any realistic player history. Server-side per-session
death aggregation would be more accurate but requires backend work.
Invariants honoured
hiccup doesn't blank the widget. Per-source rejections log with
`call=widget.records.
the empty placeholder.
follows the Plan-3b sharing-toggle work (separate PR).
Files
imports `getCommerceRecent` + `listEvents` (already in api.ts).
Test plan
visible in expanded view; compact view shows longest + survival
(or fallback to busiest)
sessions, no transactions, and no deaths
Notes
appetite to add the server endpoint.