Conversation
The mute toggle relied on widget.setVolume(0), but volume is hardware-controlled on mobile (notably iOS) where setVolume is a no-op — so tapping mute kept playing at full volume. Mute now pauses and unmute plays (setVolume(100) on desktop); play/pause honor the user's tap on every platform.
Editable add/remove lists keyed their controlled inputs by array index, so deleting a middle row reused the wrong DOM node and shifted focus/values onto a sibling. Give each row a stable client-generated id (kept length-synced with the data) and key by it. Also fold the index into TeamPaymentSection's read-only member key: a wallet address is not unique across members (mock data redacts several to one string), so keying by address alone collided and tripped React's duplicate-key warning.
project.controller used raw console.log for the updateProject payload preview and the M2-agreement confirmation; route them through the existing logger util (info/success) like the rest of the controller.
…am pages The landing page now shows only the hero, live stats, and program cards (heading "Explore past programs"); the featured unit and the projects filter/grid are gone. The search view (search box, WINNERS/ALL toggle, category filters, grid + detail modal) is extracted into a reusable ProjectExplorer and rendered on each program's detail page, scoped to that program's entries.
The Programs page open section was labelled "Grid"; relabel it "Ongoing programs" to match the page's intent.
WebZero programs run across any tech and platform, not just Polkadot. Drop "on Polkadot" from the hackathons program-card blurb.
…rboard Bitrefill hackathon judging, iteration 1. Intake: - Public POST /programs/:slug/submissions (rate-limited, honeypot, dedup by Luma email) backed by a new program_submissions table. - SubmitProjectModal on hackathon program pages (name, Luma email, title, video link, GitHub url). Judging: - Email judges: new 'role' on program_admin_emails (admin|judge) reusing the existing magic-link invite; requireProgramJudge grants scoped write to the scoring routes only, never to payment/approval routes. - Rubric scoring (requirements/2, tech stack/5, innovation/5) with notes, draft save, and a ballot submit that locks once finalized. - Leaderboard gated until every registered judge submits; ranks by mean /12 with per-criterion breakdown, tie-break on innovation then tech stack, CSV export. - ProgramJudgingSection rendered for wallet admins and email judges. Mock fixtures (mockJudging.ts) so the Vercel preview exercises the full flow. Tests: validator, scoring service (gating/tally/eligibility), submission controller, requireProgramJudge middleware. Server 323 passing.
Role-playing agents (submitter, 3 judges, organizer) drive the real controllers, requireProgramJudge middleware, and scoring service against an in-memory Supabase fake. Walks the full journey (submit -> invite -> score -> ballots -> gated leaderboard + tally) plus edge cases (dedup, validation, non-invited block, out-of-range, ballot gating, lock-after-submit) and regenerates SIMULATION_REPORT.md. 10 scenarios, all green.
An ineligible submission (Luma email not in the signup list) could rank or win with nothing signalling it — the scoring view flagged it but the leaderboard did not. Leaderboard rows now include `eligible`; the UI tags ranked rows NOT IN LUMA. Surfaced by the judging simulation (Comet Bridge ranked #2).
…eam tracking Admins can promote a winning/selected submission into a real `projects` row so it flows into the existing project + payout machinery (team_members, payments, bounties_processed, the admin project tables). - Migration: promoted_project_id on program_submissions (FK -> projects, links + makes promotion idempotent). - scoringService.promoteToProject: creates the project (title/repo/demo carried, hackathon_* backfilled from the program, submitter as a team member, Luma email + video stamped into the description since team_members has no email column), then links it back. Idempotent. - POST /programs/:slug/submissions/:id/promote (requireProgramAdmin — judges cannot create projects). - Client: 'Add to Stadium projects' control on the admin scoring table (wallet admins only via canPromote); shows a link to the project once promoted. - Payout wallet is added by an admin at payout time via the existing flow (no wallet collected at submission). Simulation extended: promote the winner -> project + team member + back-link + NOT-PAID state -> record a payout. fakeSupabase gained .update(). Server 340 passing; client build + lint clean.
Payout tracking is a simple admin toggle, not wallet/on-chain machinery: an admin marks a (winning) submission paid/unpaid. - Migration: paid / paid_at / paid_by on program_submissions. - PATCH /programs/:slug/submissions/:id/paid (requireProgramAdmin) records who + when; reversible. Surfaced on submission rows. - Client: MARK PAID / MARK UNPAID toggle + ·PAID badge in the admin scoring table (wallet admins only). - Simulation: admin marks the winner paid (paid_by/paid_at asserted) and toggles it back. Replaces the earlier manual payments-row stub. Server 343 passing; client build + lint clean.
A logged-in client must see only their own event, and only what their role needs. Two fixes: - requireProgramViewer is now role-aware: only ADMIN-role emails (and wallet admins) get the program read surface. JUDGE-role emails are NOT viewers, so a judge can no longer read the event's applicant PII, signups, inbox, audit log, or admin roster — judging only (requireProgramJudge). - AdminProgramPage probes the email session's actual permissions and renders accordingly: judges see ONLY the scoring section; admins additionally see applications. Neither can reach another event (every call is gated by program + grant server-side). Cross-event isolation already held server-side (gates scope by slug -> program -> grant); now covered explicitly by the simulation, alongside the judge least-privilege check. Updated the viewer middleware test for the role-aware gate. Server 346 passing; client build + lint clean.
Make email + admin sign-in testable before the event. - npm run verify:email -- you@addr : sends one test message through the real Resend transport (no admin auth, no DB) and prints the message id or the exact failure. Confirms RESEND_API_KEY + verified RESEND_FROM_EMAIL in one command. - GET /api/health now reports email.resendConfigured / fromConfigured (booleans only) so a deployed env can be checked without sending. - docs/TESTING_EMAIL_AND_SIGNIN.md: runbook clarifying the TWO email systems (Resend for invites/notifications vs Supabase Auth for the magic-link sign-in), the deployed test steps, the redirect-allowlist + non-mock-deployment caveats, and a scale note (custom SMTP in Supabase for ~100 sign-ins). No production code paths change. Server 346 passing.
…panel by default - Point the SoundCloud widget at the otherside-podcast track instead of the pommeshdrms profile, and update the now-playing card metadata (fallback name, genre prefix, profile link, iframe title) to match. - Default the brightness/audio rack to collapsed on every viewport (was mobile-only); drop the now-unused isMobileViewport helper.
…ey suite Add 'npm run dev:harness' (client): boots mock mode + the //Alice test wallet with Alice whitelisted as a global admin (one env var), so the stadium-tester agent connects as a real-signing wallet and can reach every admin + judging surface — no real server, no secrets (Alice's seed is the public dev mnemonic; testWalletInjection no-ops in prod builds). Commit the canonical wallet-gated suite (flows/client-journey.spec.mjs): sign in -> public submit -> score every submission -> submit ballot -> leaderboard reveals the winner -> promote -> mark paid. Validated 5/5 green against the harness. Document the launch + limitation (auto-sign can't verify the routine-vs-important signing UX) in SKILL.md.
…nd-trips In mock mode, submitProjectSubmission was a no-op that returned a fake id, so a project submitted through the public form never appeared on the judging surface. The judging list was hardcoded to three seed rows, so the preview couldn't demonstrate the full submit -> score -> leaderboard -> promote -> pay journey on a project you actually submitted. mockJudging now persists public submissions to localStorage (addSubmission) and merges them into every judging view (judgeView, submitBallot, leaderboard, setPaid). Added rows are treated eligible (the form asks for the Luma email) and, having no second-judge score, average over this judge alone on the leaderboard. resetForTests clears the new key. Verified live against the dev:harness: a submitted PixelPay Wallet shows up in SCORE alongside the seeds.
Submitters now must describe what their project does, so judges have context beyond a title and two links. The brief is a required field (max 500 chars) on the public submit form, validated client- and server-side, stored on the submission, and shown to judges in the scoring card above the rubric. - SubmitProjectModal: new WHAT DOES IT DO? textarea with a live char counter; required + max-length checks in validate(). - submission.validator: projectBrief required, trimmed, max 500 (BRIEF_MAX mirrors the client constant); returned in value. - program-submission.repository: persist + transform project_brief. - Supabase migration: add nullable project_brief TEXT (required enforced in the app; nullable keeps the migration safe for any pre-existing rows). - ProgramJudgingSection: render the brief per submission. - api.ts: projectBrief on ApiSubmission + the submit payload. - mock + sim + tests updated; seeds get briefs so the preview/judging demo reads realistically. Server 348 passing; client build + lint clean. Verified live on dev:harness: submit with a brief -> it renders in judging; empty brief is blocked.
Reworks the judging panel so judges score and a platform (global) admin elects
winners afterward, replacing the per-card ADD TO STADIUM + MARK PAID actions
(those endpoints stay, just unwired from the panel).
- Judging panel: drop promote/paid buttons. SCORE tab shows ballot progress
once a judge submits ('2 of 3 judges in, waiting on the rest'). The second
tab is now RESULTS: ranked once every judge submits, with a per-row prize
selector + PUBLISH RESULTS for platform admins only (canSelectWinners =
isGlobalAdmin); read-only with winner badges for judges/per-program admins.
- Prizes are configurable per program (ProgramFormModal tier editor), default
500/200/100 EUR Bitrefill giftcards. Flexible: any tier to any number of
winners. Awarding is gated to global admins and blocked until judging is
complete (409).
- Public results: new GET /:slug/results returns PII-free submissions +
winners only after an explicit publish. ProgramDetailPage renders a ·RESULTS
section (reusing UnitCard) with winners highlighted by prize, ordered prize
desc; no Luma email or scores exposed.
- Schema: additive nullable migration — prize_tiers + results_published_at on
programs; prize_amount/currency/label + awarded_at/by on program_submissions.
Transforms, repos (setPrize, setResultsPublished), and scoring service
(ballotProgress, leaderboard prize, isJudgingComplete, publicResults) updated.
- Mock parity for the dev:harness demo; validator + controller tests for authz
(403 non-global, 409 mid-judging) and the PII-free public payload; sim
journey rewritten to award + publish + public results.
Server 365 passing; client build + lint clean. Verified live on dev:harness:
score -> submit (progress banner) -> assign 500/200/100 -> publish -> public
RESULTS shows all 3 winners, PII-free.
Lets judges split a large submission field. Submissions group into fixed
batches of 10 (derived from stable order; BATCH_SIZE shared client+server). A
judge claims batches ('Claim next 10' picks the least-covered batch so judges
auto-distribute; specific batches claimable too), scores only their claimed
batches, bulk-saves per batch, then submits once those are scored. Multiple
judges may share a batch; every project ends up scored by at least one judge.
Because not every judge scores every project, the leaderboard / winner-selection
gate changes from 'all registered judges submitted' to COVERAGE: every
submission has at least one score from a submitted judge. The RESULTS view now
lists all submissions ranked, each row expandable to the individual per-judge
scores.
- Migration: program_judge_batch_claims (program, judge_email, batch_number).
- New program-judge-batch.repository; submission-score.repository.upsertMany.
- scoring.service: batch derivation, claimBatch (least-covered), listForJudge
(batches + claimedBatches + batchNumber), submitBallot scoped to claimed
batches, coverage-based isJudgingComplete + leaderboard (locked reports
submissionsScored/Total + pendingJudges), judgeScores breakdown on rows.
- Controller + routes: POST /scoring/claim-batch, PUT /scoring/scores (bulk).
- Client: api types + claimBatch/saveScores; ProgramJudgingSection SCORE-tab
batch strip + CLAIM NEXT 10 + grouped SAVE BATCH; RESULTS expandable per-judge
breakdown. Mock parity for the dev:harness demo.
Server 376 passing; client build + lint clean. Verified live on dev:harness:
claim batch -> score -> SAVE BATCH -> submit -> coverage unlock -> RESULTS shows
each project's per-judge scores; public results stay PII-free.
Surfaces the at-a-glance numbers an organizer or judge wants on opening a
program: confirmed participants (Luma signups), projects submitted so far, and
a live countdown to the submission portal close.
- New GET /:slug/stats (requireProgramJudge — judges + admins, no PII) returning
{ confirmedParticipants, submissionsCount } from the existing countByProgramId
methods on the signup + submission repos.
- ProgramStatsHeader: three LCDStat tiles; the countdown targets the program's
eventEndsAt (display-only, no enforcement), ticks each minute, shows Dd Hh /
Hh Mm / Mm (CLOSED / — at the edges) with exact minutes in a tooltip.
- Mounted at the top of both AdminProgramPage access branches (wallet admin +
email judge/admin). Mock parity (stats() + a Bitrefill eventEndsAt so the
countdown demos).
Server 378 passing; client build + lint clean. Verified live on dev:harness:
48 participants · 3 submitted · 6d 4h to close.
…d admin view Bitrefill (hackathon-type) tweaks; M2/other program types are untouched. Public page (hackathons): - Replace the SPONSORS & HOW TO APPLY, APPLY (past-winner), and KEY DATES blocks with a single ·KEY INFO panel: cover image, date + location, a SIGN UP ON LUMA button (program.eventUrl), and the schedule (from the program's schedule content). Non-hackathons keep their existing sections + apply flow. - Submit copy now says only checked-in attendees can submit. Submission gating: - submit() rejects (403) any email not on the program's imported Luma list — that imported list is the checked-in / approved-guest set. New programSignupRepository.existsByEmail; eligible flag is now always true. Admin view (hackathons): - EDIT button (top-right) opens the program form, gated to program admins; PATCH /:slug moves from global-only to requireProgramAdmin. New cover-image-URL field in the form. - Hide sponsors, audit log, and the legacy applications table; relabel signups to ·LUMA-APPROVED GUESTS. Keep stats, judge invites, guests, and the judging panel. Schema: additive cover_image_url on programs. Mock: Bitrefill coverImageUrl + Luma eventUrl + schedule content; a checked-in guests dataset wired to the guests table, the submit gate, and the stats count. Server 380 passing; client build + lint clean. Verified live on dev:harness: key-info panel renders; off-list email 403s, checked-in email submits; admin view shows EDIT + guests + judging only.
The ·PROGRAM ADMINS block on the program page is now a single read-only list of the program's people — email admins/judges (email + ADMIN/JUDGE role) and any per-program wallet admins (by address). The separate ·EMAIL ADMINS & JUDGES sub-section is gone. All add/remove/invite controls move into the program EDIT modal (global admins only, matching the server's global-only grant gating). ProgramAdminsSection now takes an "editable" flag (read-only on the page, editable in the modal) and a "reloadToken" so the page list refreshes after edits made in the modal. Client-only — the grant endpoints and their auth are unchanged. Mock now persists email grants (seeded with a Bitrefill judge + admin) so invite/remove demos. Client build + lint clean. Verified live on dev:harness: page shows the read-only list; EDIT modal hosts the controls; inviting a judge and closing refreshes the page list.
Minor bug fixes
Resolve ProgramDetailPage.tsx conflict with #168: keep the submit-modal state (submitOpen) from this branch and adopt develop's ProjectExplorer for the projects section (which removed the now-unused navigate/useNavigate). Hackathon key-info / results / section-gating from this branch are preserved.
Bitrefill hackathon: judging suite + public/admin overhaul
Part A (Bitrefill preview): real event cover image (client/public/bitrefill-cover.png), program name set to the Luma title "PROMPT x PURCHASE — A Bitrefill Hackathon", and sacha@joinwebzero.com added as a checked-in guest so the submit flow can be tested. Part B (Option 2 — move the SIWS line): no in-app action moves funds on-chain and the email magic-link session is a verified identity, so email-invited program admins can now run the lighter organizer actions without a wallet: - requireProgramAdmin gains a write-enabled email path: a valid admin-role program_admin_emails grant administers the program (sponsors, signups import, applications status, program edit). Judge-role / no grant falls through to the wallet gate, unchanged. Wallet admins unaffected. - Winners + payouts stay wallet/global-admin: promote + mark-paid now require isGlobalAdmin (joining award-prize + publish, which already did). create-program, the admin roster, and confirm-payment remain global-wallet. Tests: new requireProgramAdmin email-path middleware test; promote/setPaid 403-for- non-global + 200-for-global controller tests. Server 387 passing; client build + lint clean. Verified live: cover image + name render; sacha@ submits; a non-listed email still 403s. Note: this is server-side authz only — surfacing the new controls to email admins in the client (and teaching the EDIT modal to save via the email token) is a flagged follow-up; no regression for wallet admins meanwhile.
…auth save (Part C) Completes Option 2 on the client. Email-invited program admins (role=admin) can now run the lighter organizer actions in the UI, matching the server gates from the previous commit: - ProgramFormModal: emailMode prop — when set, the save authorizes with the Supabase session header (signAuthHeader) instead of a SIWS signature. Wallet path unchanged (the SIWS branch is byte-identical, now under an else). - AdminProgramPage: the EDIT button + program-edit modal are available to email admins (emailMode, x-supabase-token). The social-admin branch now shows the read-only admins list, the Luma-approved guests (CSV import), sponsors (non-hackathon), and editable applications. Relabel the email-admin session badge VIEW-ONLY -> ADMIN. Winners/payouts stay wallet/global-admin (award-prize/publish/promote/mark-paid require isGlobalAdmin, which an email session never sets). Client build + lint clean; wallet admin view verified live (EDIT opens/cancels, sections intact). The email-admin path is code-reviewed but not exercisable in the harness (no email sign-in in mock) — it relies on the server gates + the shared section components already used by the wallet path.
…e flag Submitters now get a best-effort confirmation email on submit/resubmit, and can resubmit to change their entry before the deadline. - Resubmit = overwrite the existing entry (one per Luma email). submit() upserts (findByEmail -> updateSubmission 200 / create 201) instead of 409-ing duplicates. - Confirmation email: new submission-confirmation.service + notification template (mirrors program-admin-invite; best-effort, never fails the submission), with a link to the program page + resubmit instructions + the deadline. - Late flag: the deadline stays informational, but a submit after the program's event-end time is recorded late=true (new nullable column) and shown as a ·LATE badge in the judging panel + results. Carried on submissions, leaderboard rows, and public results. - Client: submit-modal success copy + drop the 409 message; api types + mock upsert/late parity; one seeded late submission so the badge demos. Server 391 passing; client build + lint clean. Verified live on dev:harness: submit -> confirmation copy; resubmit same email -> overwrites (one entry, no 409); a late submission shows ·LATE.
The transport now blind-copies every outbound email to the address(es) in EMAIL_BCC (comma-separated), merged with any caller-supplied bcc. Recipients never see it; leave EMAIL_BCC empty to disable. Documented in .env.example.
The ·RESULTS section renders <UnitCard> but the import was dropped when #168 swapped the projects grid to <ProjectExplorer>. tsc (strict:false) didn't flag the bare identifier, so it built clean and threw "UnitCard is not defined" at runtime the moment a program with published results rendered. Re-add the named import from @/components/unit-card.
Email-admin authz + submission confirmation/resubmit + BCC (post-#170 follow-up)
High-stakes admin actions now re-sign a fresh wallet signature every time
instead of riding the cached admin session, so each is a deliberate human
confirmation. Low-stakes program edit moves the other way — onto the cached
session — so it stops nagging for a signature on every save.
- New SIWS statements: mark-paid, award-prize, publish-results, confirm-payment.
- ProgramJudgingSection: award prize + publish results re-sign via a new
signWinnerAction prop; added a PAID toggle to the results table (global
wallet admin only) wired to setSubmissionPaid with the same fresh-sign.
- Leaderboard now surfaces each submission's paid flag (scoring.service +
ApiLeaderboardRow + mock) so the toggle reflects state.
- m2 confirm-payment (AdminPage M1/M2 payout handlers + WinnersTable payout)
re-signs fresh via signAction('confirm-payment'); WinnersTable gets a
dedicated signPaymentAction prop so other admin writes keep the cache.
- ProgramFormModal: drop the manual @talismn/siws block; program create/edit
now rides signAuthHeader (cached bearer for wallet, Supabase for email).
Removed the now-dead connectedAddress/emailMode props.
Email/per-program admins remain blocked from payouts/winners/publish
server-side (isPlatformAdmin) and in the UI (canSelectWinners).
Test: leaderboard surfaces paid; server suite green (392).
The auto-slug effect's stale slugEdited closure raced the hydrate effect on
open, running setSlug(slugify('')) after the loaded slug was set — leaving the
(disabled) slug field empty, which failed validate() and blocked EVERY program
edit save. Guard the auto-slug to create mode only (!editing) so edit keeps the
loaded slug. Surfaced while verifying the relaxed program-edit flow.
fix(programs): restore UnitCard import — RESULTS section runtime crash
feat(programs): fresh-signature gate on payouts/results + PAID toggle; relax program edit
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Merged
3 tasks
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.
Release PR: promotes
developtomain, shipping the hackathon judging system and everything since #168 to production. This is the first production release of the judging / email-access / submissions feature set.Highlights
Hackathon judging system (new)
Email-based admin and judge access (new)
Email notifications
EMAIL_BCC) on every outbound email.Signing UX
Programs and UI
Tooling and data
Scope
78 files changed, +7,066 / -706. Bundles #168, #170, #171, #172, #173.
Deploy prerequisites
ADMIN_SESSION_SECRET(boot-blocker on the new code),RESEND_API_KEY+RESEND_FROM_EMAIL,FRONTEND_URL.VITE_SUPABASE_URL+VITE_SUPABASE_ANON_KEY,VITE_USE_MOCK_DATA=false,VITE_USE_TEST_WALLEToff.🤖 Generated with Claude Code