not fetched ● offline
Guide
Logs
Insights
Infractions
Session
Debug
Schema
Ready — go to Session tab to paste your token, then fetch.
Current session
No token stored
1
Open eor-us.purple-fox.fr in this browser and make sure you're logged in as scorekeeper/judge.
2
Open DevTools → Network tab. Click any request to supabase.co. In the request headers, find authorization — copy everything after Bearer (the long JWT string starting with eyJ).
Tip: filter by XHR/Fetch and look for requests to upbcarvmkmyzhbosheyo.supabase.co.
3
Paste the token below. The server stores it in memory and proxies all Supabase queries through it — everyone on the LAN can fetch without their own login.
Tokens typically last 1 hour. If fetches start failing with auth errors, paste a fresh one here.
Token lifetime

PurpleFox JWTs expire after 1 hour. The exporter shows a warning badge on this tab when expiry is under 10 minutes. To refresh: reload the tournament page in your browser, open DevTools Network, click any supabase.co request, copy the authorization header value (minus Bearer ), and paste it here.

Raw Supabase table explorer

Query any table directly. Useful for discovering the schema if automatic fetching is incomplete.


    
Fetch PostgREST schema

Hits /rest/v1/ to discover every table name exposed by PostgREST. Uses your stored JWT if one is set (required for this Supabase instance).

Probe known tables

Check which tables exist and are accessible with your current token.

No data — go to Logs tab and click Fetch logs first.
No data — go to Logs tab and click Fetch logs first.
Data sources
SUPABASE PurpleFox backend — fetched on each sync, requires JWT
CARDE carde.io tournament engine — fetched via admin API token, no login needed
DERIVED Computed locally — never stored as-is in the source system
round_timers one row per round per tournament — primary timing record
ColumnTypeSourceDescription
tournament_idTEXT PKSUPABASEUUID identifying the tournament
roundINTEGER PKCARDERound number (1-based)
carde_round_idINTEGERCARDEInternal carde.io round identifier — used for pairings API calls
started_atTEXTCARDEISO datetime when the round timer was started by the TO
timer_end_datetimeTEXTDERIVEDScheduled end time — started_at + timer_duration_minutes + any round-level extensions
completed_atTEXTCARDEISO datetime when the round was marked COMPLETE (all results in — later than time-called)
timer_duration_minutesINTEGERCARDEBase round duration in minutes as configured by the TO
extra_time_secondsINTEGERCARDERound-level extra time added beyond the base duration (if carde provides it)
carde_statusTEXTCARDEUPCOMING / IN_PROGRESS / COMPLETE
incomplete_at_endINTEGERCARDENumber of matches with no result at the moment the round clock hit zero. Written once, never updated.
missing_tables_jsonTEXTCARDEJSON int array of table numbers missing results at time-called. Populated from a fresh pairing fetch queued the moment the clock expires. Written once.
round_pairings one row per (round, table) — match-level pairings and results
ColumnTypeSourceDescription
tournament_idTEXT PKSUPABASETournament UUID
roundINTEGER PKCARDERound number
table_numberINTEGER PKCARDEPhysical table number
match_idINTEGERCARDEcarde.io match identifier
p1_name / p2_nameTEXTCARDEPlayer display names from carde user_identifier
p1_user_id / p2_user_idTEXTCARDEcarde.io internal user IDs
statusTEXTCARDEMatch status (e.g. COMPLETE)
time_extension_secINTEGERCARDEPer-table time extension in seconds granted by a judge
winner_user_idTEXTCARDEcarde user ID of the winner. Empty = result not yet submitted.
drops player drop events — from PurpleFox tournament_drops table
ColumnTypeSourceDescription
tournament_idTEXT PKSUPABASETournament UUID (mapped from tournamentId)
player_game_idTEXT PKSUPABASEPurpleFox player game ID (playerGameId)
roundINTEGER PKSUPABASERound the drop was recorded in
table_numberINTEGERSUPABASETable where the player was seated when they dropped
player_nameTEXTSUPABASEPlayer display name
is_checkedINTEGERSUPABASE1 = drop verified by a second judge
is_cancelledINTEGERSUPABASE1 = drop was recorded in error and cancelled
added_by_nameTEXTDERIVEDJudge who first recorded the drop — inferred from updated_by_name while is_checked=0. Write-once.
verified_by_nameTEXTDERIVEDJudge who verified the drop — inferred from updated_by_name when is_checked flips to 1. Write-once.
time_logs judge action log entries — from PurpleFox tournament_logs table
ColumnTypeSourceDescription
idINTEGER PKSUPABASEMonotonically increasing log entry ID
tournament_idTEXTSUPABASETournament UUID
roundINTEGERSUPABASERound number when the action occurred
table_numberINTEGERSUPABASETable the judge acted on
actionTEXTSUPABASEFree-text action description, e.g. "Change time from 5min to 10min"
user_idTEXTSUPABASEUUID of the judge who performed the action — resolved to display name via the users cache
created_atTEXTSUPABASEISO timestamp of the action
penalties infraction records — from PurpleFox tournament_penalities table
ColumnTypeSourceDescription
idTEXT PKSUPABASEUUID of the penalty record
tournament_idTEXTSUPABASETournament UUID
roundINTEGERSUPABASERound the infraction occurred in
player_nameTEXTSUPABASEName of the penalized player
descriptionTEXTSUPABASEFree-text infraction description
typeTEXTSUPABASEInfraction category, e.g. "Slow Play", "Late to Match"
sanctionTEXTSUPABASEPenalty level (Warning, Game Loss, etc.)
creator_id / creator_nameTEXTSUPABASEJudge who issued the penalty. creator_name is also used to seed the users name cache.
table_coverage judge seating — from PurpleFox tables.coveredBy field
ColumnTypeSourceDescription
tournament_id / table_number / covered_byTEXT PKSUPABASEComposite PK — one row per (table, judge) pair ever seen
covered_byTEXTSUPABASEJudge UUID — resolved to display name via users cache at read time
first_seen_atTEXTDERIVEDTimestamp of the sync run that first observed this (table, judge) assignment
roundINTEGERDERIVEDRound inferred from the highest round seen in logs/drops at the time of the sync
table_judge_results judge-entered match results — from PurpleFox tables.judgeResult field
ColumnTypeSourceDescription
tournament_id / table_number / judge_resultTEXT PKSUPABASEComposite PK — one row per (table, result string) ever seen
judgeTEXTDERIVEDCross-referenced from table_status.updated_status_by at sync time, resolved via users cache
first_seen_at / roundTEXT / INTEGERDERIVEDSync timestamp and inferred round, same logic as table_coverage
table_time_updates per-table status change events — from PurpleFox table_status table
ColumnTypeSourceDescription
tournament_id / table_number / updated_atPKSUPABASEComposite PK — unique status change event per table per timestamp
statusTEXTSUPABASETable status label at this point in time
time_minutesINTEGERSUPABASEExtension granted in minutes (PurpleFox time field)
updated_byTEXTSUPABASEUUID of the judge who updated the table status
users judge name cache — resolves UUIDs to display names
ColumnTypeSourceDescription
user_idTEXT PKDERIVEDSupabase/PurpleFox user UUID
display_nameTEXTDERIVEDHuman-readable name. Seeded from: drops.updated_by_name, penalties.creator_name, JWT metadata (current user), and any global drops across all tournaments.
tournament_meta per-tournament settings and lifecycle state
ColumnTypeSourceDescription
tournament_idTEXT PKSUPABASETournament UUID
last_tableINTEGERSUPABASEHighest table number — total active tables
default_timeINTEGERSUPABASEDefault round duration in seconds as configured in PurpleFox
nameTEXTSUPABASETournament display name
is_endedINTEGERDERIVED1 = event manually ended via the UI (stops auto-refresh, shows "ended" label)
API endpoints (serve.py)
EndpointMethodAuthDescription
/api/loginPOSTnoneAuthenticate — returns Bearer token stored in localStorage
/api/logoutGETBearerInvalidate current session token
/api/meGETBearerReturn current username and is_admin flag
/api/sync?tournamentId=GETBearer + JWTFetch all Supabase + carde data, upsert SQLite, return full dataset
/api/logs?tournamentId=GETBearerRead accumulated SQLite data without hitting any external API
/api/backfill[?tournamentId=]GETBearerRe-fetch carde round data for all (or one) tournament(s) and fill missing timing fields
/api/tournamentsGETBearerList configured tournaments with their ended status
/api/end-tournament?tournamentId=GETBearerMark tournament as ended — stops auto-refresh
/api/set-tokenPOSTBearerStore a PurpleFox Supabase JWT in server memory
/api/token-statusGETBearerCheck stored PF JWT validity and expiry
/proxy?url=GETBearer + JWTAuthenticated proxy to PurpleFox Supabase (restricted to upbcarvmkmyzhbosheyo.supabase.co)
Backfill round timing

Re-fetches carde.io round data for both tournaments and fills in any missing timer_end_datetime, started_at, and extra_time_seconds values. No token required.

Ready
Quick reference

This tool gives tournament staff a live, searchable view of all PurpleFox event data — drops, time extensions, penalties, judge activity, and round timing — pulled from PurpleFox and carde.io and stored locally so nothing is lost between sessions.

Signing in
Login

Enter your username and password at the sign-in prompt. Sessions last 7 days — you stay logged in across page reloads. Click Logout in the top-right to end your session manually.

Users
admin full access — all tabs including Data and admin sections
hj standard access — all tabs except Data
Tabs — available to all users
Logs

Main feed of all recorded events grouped by round — drops, time extensions, judge seats, table results, penalties. Most recent round first.

Fetch logs — pull fresh data from PurpleFox (requires a token in Session tab). Cached data loads automatically on page open without a token.
Auto-refresh — keep fetching on a timer. 30 sec is good during a live event; turn off for ended events.
Round filter — narrow feed to one round.
Search — filter by player name, table number, judge, or any text.
Type filters — show/hide drops, extensions, penalties independently.
Export CSV / JSON — export the current filtered view.
Insights

Round-by-round breakdown: timer (start → time-called → completed), how many tables had extensions, outstanding tables at time-called, drops, and penalty types. Click any round header to expand.

Infractions

Penalty-focused view across the entire event. Flags players with multiple penalties, shows a breakdown by type (Slow Play, Late to Match, etc.), and lists every penalty with player, round, type, sanction, and issuing judge.

Session

Paste your PurpleFox JWT token here to enable live syncing. Without a token the app shows cached data only.

How to get your token: open PurpleFox in a browser → F12 → Application → Local Storage → find the value starting with eyJ → copy and paste it here. Tokens expire after about an hour.
Debug

Query the PurpleFox Supabase database directly. Requires a token. Useful for exploring what data is available or diagnosing sync issues. Not needed during normal event operation.

Schema

Documents every local database table, its columns, and where the data comes from. Also lists all server API endpoints and a backfill button to re-pull round timing from carde.io.

Tips
During a live event Set auto-refresh to 30 sec. Switch to the Session tab first and paste your PF token to get live data rather than just the cache.
After the event Click End event in the header to stop auto-refresh and mark it done. All data stays in the database.
New entries look highlighted Each fetch marks entries that weren't there last time. Click Clear seen hashes to reset this — useful at the start of a new day.
Round timing gaps If Insights shows "—" for round start/end times, go to Schema → Backfill and run it. Timing comes from carde.io separately from PurpleFox.
Raw data browser admin only Live rows from action_logs.db — data loads only when you expand a table.
drops— player drop records
Expand to load data
time_logs— judge time-extension actions
Expand to load data
penalties— issued penalties with player, type and sanction
Expand to load data
round_timers— start/end/duration per round
Expand to load data
round_pairings— carde.io pairings with player names, results, extensions
Expand to load data
rounds_fetched— which carde rounds have been fetched and their last status
Expand to load data
table_time_updates— per-table timer status change events
Expand to load data
table_coverage— first time each judge was seen at each table
Expand to load data
table_judge_results— judge-entered results per table, first observation
Expand to load data
tournament_meta— one row per tournament: name, table count, default time, ended flag
Expand to load data
table_players— current player–table seat assignments
Expand to load data
users— UUID → display name cache
Expand to load data