尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Authentic voting rounds: staff awards, recognition votes and feedback rounds, where one person, one vote and a secret ballot are the point.
Most poll tools count clicks. This one is for the votes where the count has to be defensible:
closes_at, enforced by a trigger,
whichever path the ballot takes.The composer opens on the Awards / voting round preset (invite-only,
results after close, closes in a week). Quick poll (/?preset=quick) keeps
the open, live-results poll that anyone with the link can answer. Every setting
can be changed under either preset.
digital-brand-platform-system-plan.mdnpm install
cp .env.example .env.local
Fill in your Supabase project values in .env.local.
Apply the SQL in supabase/schema.sql using the Supabase SQL editor, then
run supabase/verify.sql and confirm it reports
verify.sql: all assertions passed. Then run supabase/verify-rls.sql,
which checks cross-account row-level security and shows one PASS/FAIL row
per check (the header of that file lists the FAILs known when it was written).
Start the app:
npm run dev
The app runs without Supabase configured: with the environment variables unset
it falls back to demo mode, serving fixtures from src/lib/demo-data.ts and
short-circuiting every mutation with an informational notice. Useful for working
on layout and copy, but nothing persists.
npm run dev — local developmentnpm run lint — ESLintnpm run typecheck — TypeScript checksnpm test — Vitest unit tests (npm run test:watch to watch)npm run build — production buildAll four checks run on every pull request via .github/workflows/ci.yml.
/ — recent polls, how a voting round works, and the poll composer (opens on the Awards / voting round preset; /?preset=quick for a quick poll)/p/[postId] — public permalink for one poll post (works signed-out); its author also gets the Open / Invite-only switch, invite issuing, and turnout/p/[postId]?invite=<token> — an invite link: votes on an invite-only poll, signed in or not, then redirects to the bare permalink/u/[username] — public profile and that account's polls (works signed-out)/auth/login — email/password sign-in plus Google OAuth entrypoint/auth/sign-up — account creation/onboarding — profile setup/notifications — in-app notification center/moderation — report review queue/api/health — deployment health/config checkTarget: Vercel for the app, Supabase Cloud for the database and auth.
Vercel is the deliberate choice rather than the default one. This project uses
Next.js 16's proxy.ts convention, which replaced middleware.ts in 16.0.
Third-party Next hosting adapters typically lag major releases, and the gaps
tend to land squarely on middleware/proxy and Server Actions — which is this
app's entire mutation layer. Vercel supports a Next release on the day it ships.
Order matters: apply the schema before deploying the app. getCurrentUser()
selects profiles.onboarded_at, which does not exist until step 1.
supabase/schema.sql. It is
idempotent, so re-running it later to pick up changes is safe.supabase/verify.sql. It must report
verify.sql: all assertions passed. This exercises the signup trigger, the
notification triggers, and the column-level privileges; everything it creates
is rolled back.
Then run supabase/verify-rls.sql. It signs in as two throwaway users, a
throwaway moderator, and as anon, tries to read and change the other user's rows in every user-owned
table, and shows one row per check with PASS or FAIL. It also ends in
rollback. Any FAIL that isn't in the file's "known fails" header is a
regression.anon public key,
and the service_role secret key.Import the GitHub repository at vercel.com/new.
Next.js is auto-detected — no vercel.json and no build configuration.
Add the environment variables, for Production and Preview both:
| Variable | Value |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | Project URL from step 1.4 |
NEXT_PUBLIC_SUPABASE_ANON_KEY | anon public key |
SUPABASE_SERVICE_ROLE_KEY | service_role secret key |
Deploy.
The service_role key bypasses row-level security entirely. It belongs only in
server-side environment variables — never in a NEXT_PUBLIC_* variable, and
never in code reachable from a component or a Server Action.
This step is easy to skip and it silently breaks sign-in. Without the redirect
allowlist, /auth/callback never establishes a session, so both Google OAuth
and email confirmation dead-end on an apparently successful redirect.
Under Authentication → URL Configuration:
https://your-app.vercel.apphttps://your-app.vercel.app/auth/callbackhttps://*-<your-vercel-scope>.vercel.app/auth/callback (only if you want
preview deployments to support sign-in)For Google sign-in, enable the Google provider under Authentication → Providers, then create an OAuth client in the Google Cloud Console and set its authorized redirect URI to the callback URL Supabase shows you on that screen.
Check these in order — each one depends on the last.
GET /api/health returns configured.supabasePublicEnv: true and
configured.supabaseServiceRoleEnv: true. If either is false, the
environment variables did not reach the deployment./onboarding already signed in — if you land signed out, the
redirect allowlist in step 3 is wrong./notifications.Step 4 is the real end-to-end check: it proves the signup trigger, the notification triggers, and session persistence are all working together.
supabase/schema.sql, re-run it in the SQL editor,
then re-run supabase/verify.sql and supabase/verify-rls.sql. There is no
migration tool — keep every statement re-runnable.schema.sql now drops the reactions and reposts tables (with their six
RLS policies, two triggers and two indexes), the notify_post_reaction() and
notify_post_repost() trigger functions, and every post_reaction /
post_repost notification, and narrows notifications_engagement_dedupe_idx
to poll_vote. The first paste on the live database permanently deletes
every like, dislike, repost and the notifications they produced; there is no
undo short of a backup. Later pastes do nothing new. Polls, options, ballots,
invites, reports, profiles and the other notification types are untouched.
After pasting, re-run both verify files: verify-rls.sql should give
132 checks, 132 PASS, 0 FAIL (the reactions FAIL accepted on 2026-10-11
is gone with the table). The previous pending changes (2026-10-11 to
2026-10-15: report and post created_at pinned, posts' UPDATE grant revoked,
staff limited to a report's moderation columns, every grant stated outright)
were pasted on 2026-10-20, giving 151 checks, 150 PASS, 1 FAIL.schema.sql): adds
polls.results_visibility (live, the default for every existing poll, or
after_close), with a check that after_close has a closes_at.
poll_option_vote_counts now returns a null votes for an after_close
poll that hasn't closed yet, plus a total_votes turnout column. On such a
poll, notify_poll_vote() leaves the choice out of the owner's notification.
This deletes nothing, and existing polls behave exactly as before. After
pasting: verify.sql passes and verify-rls.sql gives 139 checks,
139 PASS, 0 FAIL.development and only. Add staging when
the release process is risky enough to need it.dynamic = "force-dynamic", so nothing is CDN-cached and
every request reaches the server. Irrelevant at low traffic, but it does mean
the usual static-Next hosting economics do not apply here.production