A React + TypeScript + Vite ticket board for URC and Lunabotics rover work. It includes an intentionally labeled local demo board and versioned Supabase security migrations.
npm install
Copy-Item .env.example .env
npm run devThe existing .env must define VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY. Never commit .env, print its values, or put a service-role key in it. Enable Anonymous Sign-Ins in Supabase Authentication. Set VITE_DEMO_MODE=true only when intentionally reviewing sample tickets.
With Supabase configured, members sign in with Discord (Supabase's Discord provider), so the same account and tickets follow them across devices, and their board name is their verified Discord handle in member_profiles. Every visitor first gets an anonymous Supabase session so the board can load. Older members whose work lives in such a guest session see a prompt to connect Discord; before the redirect, the guest session issues a one-time merge token (create_account_merge), and after sign-in the Discord account redeems it (complete_account_merge) to take over that session's tickets and comments. After every Discord sign-in, claim_guest_sessions also merges guest sessions whose typed name equals the member's Discord username (read from auth.identities, not editable metadata), so it does not matter which device they sign in on. Administrators can assign any remaining guest work from the Administration tab. A typed display name alone never grants access to anything.
Checks:
npm test
npm run build- In the Supabase dashboard, enable Anonymous Sign-Ins and the Discord provider under Authentication > Providers, and add the site URL (for example
https://slugbotics.github.io/rover-ticketing/**) under Authentication > URL Configuration > Redirect URLs. - Apply
supabase/migrations/202610120001_ticket_board.sqlwith the Supabase CLI (supabase link --project-ref YOUR_PROJECT_REF, thensupabase db push) or paste it into the SQL editor. Do not run it against production until reviewed and authorized. - Create the first administrator: create a normal Supabase Auth user through the dashboard, then run this SQL while signed in as the project owner, replacing the UUID with that user's Auth user ID:
insert into public.leads (user_id, role) values ('AUTH_USER_UUID', 'administrator');- Optional: populate the board with sample rows by replacing
AUTH_USER_UUIDinsupabase/seed_demo.sqlwith that administrator UUID and running the file in the SQL editor. Without this optional seed, an emptyticketstable and an empty board are correct.
To find an Auth user UUID, open Supabase Dashboard > Authentication > Users, copy the UUID for the administrator account, and replace the placeholder in the seed file. Do not paste service-role keys into the seed file or the frontend environment.
Add subteam scopes for non-administrators:
insert into public.leads (user_id, role) values ('AUTH_USER_UUID', 'subteam_lead');
insert into public.lead_scopes (user_id, subteam) values ('AUTH_USER_UUID', 'Software');Anonymous and Discord members never inherit lead permissions. Guest (anonymous) ownership stays with its browser until the member signs in with Discord, which merges it into their account.
The production build uses /rover-ticketing/ as its Vite base path. Configure Pages to deploy the dist artifact from GitHub Actions. Add repository Actions variables (Settings > Secrets and variables > Actions > Variables) VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY; the deploy job fails rather than publishing a demo build if either is missing. Set Settings > Pages > Build and deployment > Source to GitHub Actions. The site is served at https://slugbotics.github.io/rover-ticketing/. Ticket details use query-string URLs such as /rover-ticketing/?ticket=RB-104, which work on direct refreshes on Pages without server routing.
No file uploads are implemented. With Supabase configured and migrations applied, ticket reads, participant join/leave, comments, lead ticket creation, sub-ticket creation, and lead review status changes all use shared database state. The UI reports auth/data errors rather than silently switching to demo identity or demo data. See PROJECT_SPEC.md, AGENTS.md, and ACCEPTANCE_CHECKLIST.md.
Tickets support multiple independent participants and parent-linked sub-tickets. Apply 202610120002_multi_participant_subtickets.sql after the base migration when enabling those shared database workflows.
Apply 202610120004_create_subticket_rpc.sql as well. Sub-ticket creation is intentionally lead-scoped because it changes ticket requirements; anonymous members can join existing work but cannot create requirements.
Apply 202610120005_lead_ticket_workflows.sql to enable the lead sign-in ticket creation and lead review status functions used by the app.
Apply 202610140006_fix_workflow_rpc_privileges.sql last. It runs the workflow functions as security definer with explicit membership/lead-scope checks; without it, joining tickets, creating tickets, and status changes fail under RLS because ticket_activity intentionally has no client INSERT policy.
Apply 202610140007_subtickets_and_comment_replies.sql and 202610140008_review_workflow_and_reply_fix.sql after 006. Together they let ticket participants (or scoped leads) create one level of sub-tickets with server-allocated IDs, add one level of comment replies, and let participants submit work for review. Only leads in the ticket's subteam (or administrators) can mark work Done; Done tickets leave the board and appear in the Archive tab. Sub-tickets appear under their parent ticket and in the My tasks list of members who join them, not on the main board.
Apply 202610140009_admin_lead_management.sql and 202610140010_pending_lead_grants.sql to manage leads from the app. An administrator can grant a role and subteams by email from the Administration tab at any time; if that email has no confirmed account yet, the grant is stored as pending and applied automatically once the person accepts their invite (Supabase Authentication > Users > Add user > Send invitation). The admin-only database functions re-check the caller and refuse to remove or demote the last administrator.