尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Shared house-cleaning rotations, volunteering and a friendly monthly league (with a weekly champion on the side) for a six-person house. Built from spec.md (V3).
Stack: React 19 + TypeScript + Vite + Tailwind v4 · Supabase (Postgres, Auth, Realtime) · PWA · Vercel.
All rules that matter (points, rotation advancement, volunteer claiming, streaks, achievements, cooldowns) live in Postgres functions in supabase/migrations. The browser never sends a points value; it only calls RPCs like complete_cleaning(area_id).檢視完整 README ↗Shared house-cleaning rotations, volunteering and a friendly monthly league (with a weekly champion on the side) for a six-person house. Built from spec.md (V3).
Stack: React 19 + TypeScript + Vite + Tailwind v4 · Supabase (Postgres, Auth, Realtime) · PWA · Vercel.
All rules that matter (points, rotation advancement, volunteer claiming, streaks, achievements, cooldowns) live in Postgres functions in supabase/migrations. The browser never sends a points value; it only calls RPCs like complete_cleaning(area_id).
https://supabase.com/dashboard/project/<ref>).cp .env.example .env # then paste the URL + anon key into .env (not into .env.example)
npm install
VITE_SUPABASE_URL must be the API URL shown under Project Settings → API (https://<ref>.supabase.co), not the dashboard link.
npx supabase login # opens the browser once
npx supabase link --project-ref <ref>
npx supabase db push --include-seed
The seed creates:
Asia/Kolkata, week starts Monday, 4-hour re-flag cooldown)Logins: every seeded member signs in as <firstname>@house.local with password chorewars. Everyone should change it from Profile → Change password on first login.
If
db push --include-seedcomplains aboutauth.users, run the contents ofsupabase/seed.sqlin the dashboard SQL Editor instead — it is idempotent.
The migration adds the tables to the supabase_realtime publication. If live updates don't arrive, check Database → Replication and make sure the publication includes cleaning_area_state etc.
npm run dev # http://localhost:5173
npm run typecheck
npm run build # production build in dist/
VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY.vercel.json already rewrites all routes to index.html for client-side routing.Open the Vercel URL on a phone and "Add to Home Screen" — it installs as a PWA.
Migration 0009 queues notifications in Postgres; the send-push Edge Function delivers them. One-time setup:
npx web-push generate-vapid-keysnpx supabase secrets set VAPID_PUBLIC_KEY=<public> VAPID_PRIVATE_KEY=<private> VAPID_SUBJECT=mailto:<[email protected]> PUSH_WEBHOOK_SECRET=<long random string>
npx supabase functions deploy send-push
select vault.create_secret('https://<ref>.supabase.co', 'chorewars_project_url');
select vault.create_secret('<long random string>', 'chorewars_push_secret');
VITE_VAPID_PUBLIC_KEY=<public> to .env and to Vercel's environment variables, then redeploy.Push needs the built service worker, so test with npm run build && npm run preview, not npm run dev. Delivery problems show in notifications.last_error and the function's logs.
| Task | Where |
|---|---|
| Add a new housemate | Supabase → Authentication → Users → Add user (email + password), then app → Admin → Members → Add member (same email) |
| Change rotation order / who's up | Admin → Areas → tap area → drag order with arrows, tap a number to set current turn |
| Change intervals, points, grace, cooldown | Admin → Areas → tap area |
| Timezone / week start / bonuses | Admin → Settings |
| Turns per round / max round length / forfeit | Admin → Settings → League rounds |
| Give or take points (dirty dishes, unchecked area, extra effort) | Admin → Members → tap member → Adjust points (reason required; undo from History until the round settles; cap under Settings → "Biggest admin adjustment") |
| Remove an area | Admin → Areas → tap area → Remove (history is kept; it can be restored) |
| Spec | Implementation |
|---|---|
| Fresh / Due Soon / Ready / Attention computed on read | src/lib/status.ts (computeStatus) — pure function of last_cleaned_at + thresholds; nothing stored |
| Needs Cleaning + 4h reactivation cooldown | mark_needs_cleaning() |
| Atomic volunteer claim after grace period | claim_volunteer() — single conditional UPDATE … WHERE activation_state='active' AND volunteer_user_id IS NULL AND now() >= activated_at + grace RETURNING |
| Scheduled person can always complete (even after a claim) | complete_cleaning() — scheduled user → normal; volunteer → volunteer; claim simply cleared |
| Points ledger, round/weekly/all-time derived | user_points_ledger + get_leaderboard('round'|'week'|'all'), get_user_stats(); the round score comes from league_round_scores() |
| Week boundary in house timezone | house_week_start() reads house_settings.timezone and week_start_day |
| Weekly champion snapshot | award_weekly_champions() — idempotent; called lazily by the leaderboard RPC and hourly by pg_cron when available |
| League rounds: a round ends when every eligible member has had N own turns (default 3) or after the max length (default 45 days). Only your first N turns count for you; rescues always count. Champion → Skip Pass + achievement; lowest scorer among those who had all N turns → forfeit (only if at least two did) | settle_league_rounds() — idempotent/lazy like the weekly champion; league_rounds, skip_passes, get_league_round(), get_league_rounds(), mark_forfeit_done() |
Rescue penalty (scheduled person loses rescue_penalty_points, default 5) | negative row in user_points_ledger written by complete_cleaning(); undone with the completion |
| Ties: champion → more rescues, fewer times rescued, reached the score first; forfeit → rescued more, fewer turns, reached the score last, else no forfeit; weekly champion → reached the score first | settle_league_rounds(), award_weekly_champions(), (0011) |
Run these by hand with two browsers signed in as different people:
grace_period); after grace it works. (Temporarily set grace to 0 hours on an area to test quickly.)not_active.supabase.from('user_points_ledger').insert(...) from the console → RLS rejects.league_round_scores().last_scored_atAdmin point adjustments with a required reason, capped by max_point_adjustment (default 50), visible to the whole house | admin_adjust_points(), admin_undo_adjustment(); ledger rows with reason admin_adjustment + note + created_by |
| Skip Pass: turn goes to last round's lowest scorer (next in rotation if they're away), counts as their turn, can't be skipped again; rotation then resumes after the skipper | use_skip_pass(), league_skip_target(), cleaning_area_state.skip_from_user_id |
| "Looks fine" needs half the rotation to agree | dismiss_votes + vote_dismiss(); admins override with dismiss_activation() |
| Away members skipped everywhere, no penalty, round doesn't wait for them, no forfeit | house_members.away, admin_set_member_away(), next_rotation_user() skips them |
| Streak: per-user, global, only broken by a rescue | inside complete_cleaning() (user_streaks) |
| Achievements evaluated in the same transaction | evaluate_achievements() called from complete_cleaning() |
| RLS: members read, nobody writes directly | 20260913000003_rls_realtime.sql |