M4 · Product (T40–T44)
Exit gate: a complete cycle on testnet: the team publishes 5 candidates, people vote by email, the vote closes, the winner's operation starts automatically, and every dapp screen shows it live.
Design source: the Design canvas « Vivra · Landing & Dapp » (artboards Main, Vote, Candidate, Operating, Family). Ask the owner to share it with you. Copy layouts, colours, type and copy; do not redesign. Example data in the mockups must be replaced by API data.
T40 · Email auth#
Goal Passwordless email login with 6-digit code or magic link, no wallet.
Depends on T03
Read first cycles-candidats-et-vote.md (Vote par email), annexe-api.md (Connexion), annexe-base-de-donnees.md (users, auth_codes, sessions), securite-et-observabilite.md
Build apps/api/src/routes/auth.ts, packages/core/src/email.ts (normalize, disposable check), apps/api/src/lib/{mailer.ts,crypto.ts,turnstile.ts}, packages/config/disposable-domains.txt
Steps
- Normalize email (trim, lowercase); reject disposable domains (list + setting for extras).
- Store
email_hash= HMAC-SHA256(EMAIL_HMAC_KEY) andemail_enc= AES-256-GCM(EMAIL_ENC_KEY). Never log the email. POST /auth/email(Turnstile required) → 6-digit code + magic token, 15 min TTL, both hashed; send via Resend/Postmark; always 204.POST /auth/verify→ 5 attempts max per code; creates session 30 days; cookie; returnscan_vote_at= created_at +min_account_age_hours.GET /me,POST /auth/logout.
Done when
- Tests: disposable domain → 422; 6th wrong code → code invalidated; expired code → 422
- DB inspection test: no plaintext email anywhere in tables
- Rate limits from annexe B enforced (tests)
T41 · Cycles, candidates and vote#
Goal The team composes and publishes a cycle; votes and supports work; the clock closes the vote and creates the operation.
Depends on T16, T22, T40
Read first cycles-candidats-et-vote.md (entire page), annexe-api.md (cycles, votes, supports, admin cycles), parcours-1.md, parcours-2.md
Build apps/api/src/routes/{cycles,candidates,votes,supports}.ts, apps/api/src/routes/admin/cycles.ts, apps/workers/src/jobs/cycles.clock.ts, apps/admin/app/cycles/*
Steps
- Admin: create cycle (number, times, min account age; cycle 1 defaults to 0h), pick 5 validated tokens, edit dossier texts (tagline, thesis, reasons ×3, plan, risks 1–3), trigger Scanner pre-reports, publish (freezes recovery scores, sets
published_at, tokens →candidate). - Public:
GET /cycles/current,GET /candidates/:id(full dossier fields from annexe B),GET /cycles/:id. POST /voteswith all rules of annexe A "Règles à faire respecter"; change of vote allowed until close; WSvote.tally≤ 1/s.POST/DELETE /supports.cycles.clock: opens votes atvote_opens_at; atvote_closes_atcomputes winner (tie → most supports → earliest position), sets J0, creates the operation (T22), losing candidates →validated.
Done when
- Vote rule tests: closed cycle, team account, too-new account, blocked account → refused with the right code
- Tie-break test
- E2E (API level): publish → 10 votes → close → operation exists with J0 = close time
- Frozen score on the dossier does not change when the live score changes (test)
T42 · Dapp screens#
Goal Vote, Dossiers, War room, Revived & ledger, wired to the API and WebSocket, matching the mockups.
Depends on T22, T33, T41
Read first dapp-et-landing.md, the Design canvas artboards Vote / Candidate / Operating / Family, annexe-score-de-reprise.md (Sur la dapp), annexe-api.md (WebSocket)
Build apps/web/app/(dapp)/{vote,dossiers/[id],war-room/[id],ledger}/page.tsx, packages/ui/* components (Header, PfpAvatar, SocialButtons, ScoreRing, PillarBars, Countdown, LogFeed, SliceGrid, LedgerTable…)
Steps
- Build shared components first in
packages/uifrom the mockups (tokens: colours, radii, fonts). - Vote: launchpad cards (banner, pfp, socials, score ring, key figures, share bar), comparison matrix, email vote flow (email → code → voted / switch).
- Dossier: tabs per candidate, banner + pfp + socials, market cap chart with peak and last-team-tweet markers, why we picked it, Scanner checks, how it died, takeover plan, risks, score breakdown with raw values, market, socials & docs, "if it wins".
- War room: header, progress, chart with slice markers, KPIs, streaming public log with agent filter, agents' spend, shipped outputs, slices grid + recent tx, Pons CTO steps.
- Revived & ledger: stats, weekly flywheel, family cards with verdict, filterable ledger, weekly burns, public wallets.
- Every number links to its source (tx, contract or ledger row). Disclaimer banner on each page. Responsive at 390px. Reduced motion respected.
Done when
- Playwright: each screen renders with seeded data at 1440px and 390px, no console errors
- Voting flow e2e with a test inbox (Mailpit in docker) works end to end
- A new vote appears in shares in < 5s; a new log line appears in the war room without reload (e2e)
- Visual review against the mockups attached to the PR (screenshots side by side)
T43 · Landing page#
Goal The marketing landing, light theme with orange gradient, as in the Design canvas artboard « Landing ».
Depends on T00 (UI tokens from T42 if available)
Read first dapp-et-landing.md, Design canvas artboard Main (Landing)
Build apps/web/app/(marketing)/page.tsx + section components
Steps
- Sections in order: hero (gradient, glass tray with the 4 step cards, rule numbers row), problem, agents (6 cards with output previews), flywheel (loop diagram, compounding points, the math card, coins paying in), 72-hour clock, recovery score, final CTA, footer.
- Live values (voters, countdown, coins paying in) come from
/statsand/cycles/current; anything not yet live is hidden, not faked. - SEO: title, description, OG image (generated with the brand template), sitemap, robots.
Done when
- Lighthouse ≥ 90 on performance, accessibility, SEO (CI)
- Playwright snapshot at 1440px and 390px
- No example number from the mockups is hard-coded (grep test for "3,812", "$GRIDL", "+58%")
T44 · Vivra X bot#
Goal Automatic posts of the Vivra account for every event of the spec.
Depends on T27, T31, T41
Read first bot-x-et-integrations-sociales.md (Posts automatiques, Règles d'automatisation)
Build packages/templates/social/vivra/*.ts (one template per event), event hooks emitting social.publish jobs, visual templates (rendered PNG)
Steps
- Events: dossiers published (one per candidate), vote open, vote closed, operation milestones, each slice (after execution), verdict, weekly ledger, milestones.
- Each template is a pure function
(data) → { text, media? }, ≤ 280 chars, tested. - Respect rate rule and validation setting; slices are always posted after execution.
Done when
- Snapshot tests for every template with fixture data, all ≤ 280 chars
- Integration test: a slice execution emits exactly one post job, after the tx is confirmed