Skip to content

Sprint 7: engagement mechanics — points wallet, leaderboards, streaks - #19

Merged
hynding merged 14 commits into
mainfrom
sprint-7-engagement
Jul 9, 2026
Merged

Sprint 7: engagement mechanics — points wallet, leaderboards, streaks#19
hynding merged 14 commits into
mainfrom
sprint-7-engagement

Conversation

@hynding

@hynding hynding commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the Sprint 7 plan (docs/superpowers/plans/2026-07-08-sprint-7-engagement.md) — the v1.x engagement trio in thin, demo-visible slices, built on the transactional ingestion substrate from Sprint 5:

  • Points ledger + wallet: append-only points_ledger written inside the ingestion transaction (dedup/rollback semantics inherited — a replayed event or unlock awards nothing); points flow from per-event rules (project.pointRules) and achievement unlock bonuses (pointsValue); GET /v1/users/:id/wallet returns the computed balance + last 20 entries
  • Leaderboards: GET /v1/leaderboard?window=all|7d|30d&limit=1..100SUM(delta) ranked, deterministic ties, windowed by ledger timestamps; <Leaderboard> widget highlights the current user
  • Streaks: consecutive-local-day counter maintained in the same transaction via SELECT ... FOR UPDATE (race-tested); the SDK auto-sends the device tz offset per event, with an always-lenient schema (catch(undefined)) so an invalid offset degrades to UTC-day rather than 400ing the track
  • SDK: getWallet(), getStreak(), getLeaderboard(opts), currentUserId getter
  • Demo + e2e: wallet/streak readouts + leaderboard; new engagement e2e asserts the exact seeded math (10 rule + 50 bonus = 60) against a fresh-seeded compose stack

Behavior notes

  • No existing wire shapes changedtzOffsetMinutes is additive; three new pk endpoints (rate-limit + auth middleware inherited)
  • Leaderboard privacy posture (documented): entries expose external user ids to any pk holder — host apps map ids to display names; pass opaque ids to identify() for pseudonymity. Wallet/streak reads for arbitrary userIds match the pre-existing GET /v1/users/:id/achievements posture — not a new grant, but the leaderboard makes id discovery easier
  • Timed-event multipliers apply to achievement progress only; point awards are never multiplied (documented in READMEs; a "double points" mechanic is future work)
  • Streaks are always-on (every ingest carries the streak lock-compute-write + a cached pointRules fetch); the points feature is what goes dormant when nothing is configured
  • Erasure extended: DELETE /v1/users/:id now also removes points_ledger + user_streaks rows (counts additive in the response)

Test plan

  • 27/27 turbo tasks green post fix-wave (contracts 42, core 56, adapter-db 41 Testcontainers incl. FOR UPDATE race + rollback + erasure matrix, sdk 32, api 135)
  • Fresh-seeded compose stack: all 4 Playwright specs green incl. the new engagement loop; hand-curl confirmed wallet=60, streak=1, leaderboard row=60
  • CI runs on this PR (test + compose e2e) — first CI run of the branch; treat green checks as the merge precondition

Review notes

Nine tasks through the full gate cycle (four fix→re-review loops: zod idiom equivalents, seed coherence, api boundary tests) plus a final whole-branch review — verdict ready to merge with fixes; fix wave 53606d0 applied and re-approved: the erasure gap (the one Critical — erased users would have persisted on the leaderboard), spec-lenient tz schema, multiplier docs, wallet-order determinism, Node-portability nit, Math.floor guard. Delivers the third v1.x roadmap slice (leaderboards, streaks, points/XP wallet).

🤖 Generated with Claude Code

hynding and others added 14 commits July 8, 2026 21:06
…ds, streaks)

Scope decided with Steve: all three mechanics in thin slices. Points from
achievements (pointsValue) + per-event rules (project.pointRules), awarded
inside the ingestion transaction; computed balances; client-tz-offset
streak days; pk read endpoints for wallet/streak/leaderboard; Leaderboard
widget; demo + compose-stack e2e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nine tasks in dependency order: contracts (wallet/streak/leaderboard
schemas, tz offset) → core (pure engagement calc, EngagementWrite/Store
ports, ingest widening) → adapter-db (migration 0006, transactional
awards, FOR UPDATE streaks, engagement reads) → cms (pointsValue,
pointRules, endpoint, seed) → adapter-strapi → api (award wiring + three
endpoints) → sdk → widgets (Leaderboard) → demo/e2e/docs DoD.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds pure engagement domain logic (localDayFromOffset, applyStreak,
pointsForEvent) plus the port shapes downstream adapters will implement:
ConfigStore.getPointRules, EngagementWrite/EngagementStore, and a 5th
ingestEvent param carrying computed engagement data into the existing
ingestion transaction. AchievementDefinition gains a required
pointsValue field.

Known break (expected, not fixed here): adapter-db, adapter-strapi, and
apps/api no longer typecheck until Tasks 3/5/6 implement the widened
ports and add pointsValue to their AchievementDefinition construction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation 0006)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rd endpoints

Wires point-rule/achievement point awards into POST /v1/events (fail-open
config fetch, safe defsById lookup, no second config-plane round trip) and
adds the three engagement read endpoints (wallet, streak, leaderboard) backed
by PgEngagementStore. Ends the known typecheck-break chain from the upstream
core/adapter-db/contracts changes; workspace typecheck is fully green again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…minism and portability nits

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hynding
hynding merged commit 04a9eb5 into main Jul 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant