System Overview

TracktTabs — Real-time race day dashboard for backyard ultra runners. Vue 3 + Supabase, hosted on Neocities.

11
Active Pages
35
Service Modules
14
Utility Modules

Stack

  • Frontend: Vue 3 (Options API), vanilla JS, CSS custom properties
  • Backend: Supabase (PostgreSQL + Auth + Realtime + Storage)
  • Hosting: Neocities (static files only, no build step)
  • PWA: Service worker for offline caching
  • Domain: tracktabs.run (alias of tracktabs.neocities.org)
  • Module system: ES modules with ?v=N cache-bust params

Architecture Pattern

  • Each page is a standalone HTML file with its own Vue app
  • shell-bundle.js monkey-patches Vue.createApp to inject AppShell nav components
  • Services are ES module singletons imported by pages
  • supabase-client.js is the single Supabase client instance shared by all modules
  • No build pipeline — manual deploys via Neocities API
  • Cache busting via ?v=N query params on script imports

Auth Migration Status In Progress

Pages

Every active HTML page and what it loads. All pages use Vue 3 Options API.

Page Title Module JS JS Version Shell Imports
index-v3.html Command Center index-v3.js ?v=4 yes supabase-client, user-manager, crew-link-service, api-service
race-day-v3.html Race Day Dashboard race-day-v3.js ?v=11 yes 19 imports (largest page)
profile-select-v3.html Sign In profile-select-v3.js ?v=7 yes supabase-client, user-manager, role-service, crew-link, image-storage, pr-verification, spectator-privacy, passwordHash
settings-v3.html Settings settings-v3.js ?v=8 yes settings-service, user-manager, supabase-client, weather-provider, photo-upload
plan-v3.html Plan Wizard plan-v3.js ?v=9 yes plan-data-service, user-manager, product-database, custom-metrics, crew-link, api-service, shared components
analysis-v3.html Analysis analysis-v3.js ?v=6 yes analysis-service, user-manager, social-feed-service
compare-v3.html Compare Races compare-v3.js ?v=3 yes analysis-service, api-service, user-manager, supabase-client
conditions-v3.html Conditions conditions-v3.js ?v=3 yes conditions-service-v3, weather-provider, sweat-math, user-manager, race-profile-service
practice-v3.html Practice Sessions practice-v3.js ?v=2 yes practice-session-service, custom-metrics, user-manager, config
bibboard.html Bib Board bibboard.js ?v=5 no supabase-client, role-service, crew-link, image-storage, pr-verification, spectator-privacy, passwordHash
reset-password.html Reset Password reset-password.js ?v=2 no supabase-client

Non-V3 Active Pages (bundle-based, no module JS)

These pages exist but use older patterns or inline scripts: admin.html, crew-troubleshoot.html, live-race.html, live-tracking.html, fuel-menu.html, sweat-analyzer.html, and ~10 experimental lab pages (analytics-lab, energy-terrain, race-replay, pace-tunnel, mountain-climb, body-systems, catlap-vr, race-time-machine).

Service Layer

All ES module services in js/services/. Each is a singleton.

ServiceExportsImports FromStatus
supabase-client.jssupabase, supabaseClient, waitForAuthReady, getSession, getAuthUid, isAuthenticated(none — root module, uses window.supabase UMD)core
user-manager.jsuserManager, hashPassword, verifyPasswordapi-service, supabase-client?v=2, passwordHashcore
api-service.jsapiServicesupabase-client?v=2core
settings-service.jssettingsServicesupabase-client?v=2, user-manager?v=2ok
role-service.jsroleServicesupabase-client?v=2ok
crew-link-service.jscrewLinkServicesupabase-client?v=2, role-serviceok
runner-in-service.jsrunnerInService, RunnerInService, SOURCE_RANKSsupabase-client?v=2, role-serviceok
active-targets-service.jsactiveTargetsService, ActiveTargetsServicerace-profile-service, user-manager?v=2, configok
plan-data-service.jsplanDataService, RACE_FORMATS, calculateRecommendedTargets, + 5 moresupabase-client?v=2ok
analysis-service.jsanalysisServicesupabase-client?v=2, api-service no ?v, custom-metrics, sweat-mathversion
race-profile-service.jsraceProfileService, DEFAULT_PROFILE, FORMAT_PRESETS, SWEAT_MULTIPLIERSapi-service no ?vversion
conditions-service-v3.jsconditionsServiceV3supabase-client?v=2, user-manager?v=2, weather-provider, sweat-mathok
weather-provider-service.jsweatherProviderServiceuser-manager?v=2ok
practice-session-service.jspracticeSessionServicesupabase-client?v=2, configok
social-feed-service.jssocialFeedService, SocialFeedService, PAGE_SIZEsupabase-client?v=2ok
milestone-service.jsmilestoneService, MilestoneServicesupabase-client?v=2, social-feed-serviceok
cheer-service.jscheerService, CheerServicesupabase-client?v=2, role-service, social-feed-serviceok
reaction-service.jsreactionService, ReactionServicesupabase-client?v=2ok
spectator-privacy-service.jsspectatorPrivacyServicesupabase-client?v=2ok
photo-upload-service.jsphotoUploadService, PhotoUploadServicesupabase-client?v=2ok
image-storage-service.jsimageStorageServicesupabase-client?v=2ok
custom-metrics-service.jscustomMetricsServicesupabase-client?v=2ok
lap-clock-service.jslapClockServicesupabase-client?v=2ok
crew-checklist-service.jscrewChecklistService, CrewChecklistServicesupabase-client?v=2ok
crew-service.jscrewServicesupabase-client?v=2ok
live-race-service.jsliveRaceServicesupabase-client?v=2ok
offline-data-service.jsofflineDataServiceindexed-db, supabase-client?v=2ok
gpsStorageService.jsGPSStorageServicesupabase-client?v=2ok
gpx-map-service.jsgpxMapService, GpxMapService(none)ok
pr-verification-service.jsprVerificationServicesupabase-client?v=2ok
race-settings.jsraceSettingsuser-manager?v=2ok
speech-service.jsspeechService, SpeechService(none)ok
crew-alerts-service.jscrewAlertsService, CrewAlertsService, DEFAULT_THRESHOLDS(none)ok
product-database-service.jsproductDatabaseService(none)ok
sweat-math.jsStatsEngine, SweatMath(none)ok
decision-tree.jsdecisionTree(none)ok
image-service.jsimageService(none)ok
indexed-db.jsopenDB(none)ok
profile-check.jsrequireRaceProfile, getProfileStatus, renderProfileBlockerrace-profile-serviceok

Dependency Graph

How modules depend on each other. Core modules at top, pages at bottom.

Layer 0: Globals (UMD scripts, loaded before modules)

vue.global.prod.js

Vue 3 runtime. Exposes window.Vue

supabase.min.js

Supabase JS client UMD. Exposes window.supabase

shell-bundle.js

IIFE. Monkey-patches Vue.createApp to register AppShell, nav, toast, command palette components.

Layer 1: Core Services (no app-level dependencies)
supabase-client.js root
Uses window.supabase.createClient() — no ES imports
Exports: supabase, supabaseClient, waitForAuthReady, getSession, getAuthUid, isAuthenticated
Imported by: 32 modules (everything)
api-service.js core CRUD
Imports: supabase-client?v=2
Exports: apiService (getUser, getLaps, saveLap, getRacePlan, saveFuelMenu, etc.)
Layer 2: Auth & User (depends on Layer 1)
user-manager.js auth
Imports: api-service no ?v, supabase-client?v=2, passwordHash
Exports: userManager (signUp, signIn, signOut, initAuth, requireUser, getCurrentUser, etc.)
Imported by: 10 page/service modules
Layer 3: Feature Services (depend on Layers 1-2)

35 services. Most import only supabase-client. Some import user-manager or api-service. See Services tab for full list.

High-connectivity services

  • role-service — imported by runner-in, crew-link, cheer, bibboard
  • social-feed-service — imported by milestone, cheer, analysis-v3
  • race-profile-service — imported by active-targets, conditions, profile-check
  • crew-link-service — imported by index-v3, plan-v3, profile-select, bibboard

Zero-dependency services

  • speech-service, crew-alerts-service, product-database-service
  • sweat-math, decision-tree, image-service
  • gpx-map-service, indexed-db
Layer 4: Pages (depend on everything above)
race-day-v3.js 19 imports
The largest and most complex page. Imports: active-targets, user-manager, role-service, runner-in, speech, api-service, supabase-client, race-profile, custom-metrics, social-feed, cheer, crew-checklist, crew-alerts, gpx-map, photo-upload, milestone, spectator-privacy, reaction, lap-clock, product-database
profile-select-v3.js 8 imports
Login page. Imports: supabase-client, user-manager, role-service, crew-link, image-storage, pr-verification, spectator-privacy, passwordHash
plan-v3.js 7 imports
Plan wizard. Imports: plan-data-service, user-manager, product-database, custom-metrics, crew-link, api-service, shared components
Other pages: 3-5 imports each

Database Schema

Supabase PostgreSQL tables. RLS policies active on core tables.

users RLS

PK: user_id (text)
Columns: auth_uid (UUID, nullable), name, email, password_hash, settings (JSONB), profile_image, banner_image, weight_kg, caffeine_half_life, flavor_text, bib_number
Used by: user-manager, api-service, settings-service, crew-link, role-service

laps RLS

PK: id
FK: user_id → users
Columns: lap_number, timestamp, carbs, fluids, sodium, caffeine, protein, fiber, mood, notes, run_time, runner_in_timestamps (JSONB)
Used by: api-service, analysis-service, race-day-v3

race_plans RLS

PK: id
FK: user_id → users
Columns: plan_data (JSONB — race_status, targets, pacing, caffeine schedule)
Used by: api-service, plan-data-service, race-day-v3, index-v3

fuel_items RLS

PK: id
FK: user_id → users
Columns: name, category, carbs, fluids, sodium, caffeine, protein, fiber, notes
Used by: api-service, fuel-menu page

crew_members RLS

PK: id
FK: user_id → users
Columns: crew_user_id, name, role, crew_code, status
Used by: crew-link-service, crew-service, index-v3

Other Tables

race_profiles, condition_snapshots, practice_sessions, practice_laps, race_comparisons, lap_clock_entries, social_feed, cheers, reactions, custom_metrics

RLS Policy Status

Phase 1 migration added granular RLS policies to users, laps, race_plans, fuel_items, crew_members. Old "Allow all operations" blanket policies still exist and need cleanup (sql/auth-migration-phase1-cleanup.sql). The blanket policies override the granular ones, meaning RLS is effectively open until cleanup runs.

Infrastructure

Hosting, service worker, deploy process, vendored libraries.

Hosting: Neocities

  • Static file hosting only — no server-side logic
  • Custom domain: tracktabs.run
  • Deploy via API: curl -F "file=@file" -H "Authorization: Bearer KEY" neocities.org/api/upload
  • No Cache-Control headers — only ETag/Last-Modified
  • Neocities strips .html from URLs (tracktabs.run/race-day serves race-day.html)

Service Worker

  • Current version: backyard-crew-v9.0.0
  • HTML: network-first with cache: 'no-store'
  • JS/CSS: network-first with cache: 'no-store'
  • Hashed assets: cache-first (immutable)
  • Vendor/static assets: cache-first
  • Supabase API: network-only, offline returns 503 JSON
  • Activate: skipWaiting() + clients.claim() + notifies clients

Vendored Libraries

  • Vue 3: vue.global.prod.js (production UMD), vue.global.js (dev), vue.esm-browser.prod.js
  • Supabase: supabase.min.js (UMD)
  • Chart.js: chart.umd.min.js + chart.js
  • Leaflet: leaflet.js + leaflet.css + marker images
  • Three.js: three.min.js + OrbitControls.js
  • A-Frame: aframe-extras.min.js
  • Fonts: Inter (6 weights), Barlow + Barlow Condensed (4 weights each)

CSS Architecture

  • Design tokens: css/tokens/ (primitives, semantic, typography, spacing)
  • Layout: css/layout.css — page scaffolding
  • Components: css/components.css — shared UI components
  • Page-specific: css/race-day-v3.css, css/index.css, etc.
  • Features: css/features.css, css/realtime.css, css/skeletons.css, css/experimental.css

Known Issues

Problems found during the April 17, 2026 audit. Ordered by severity.

Critical

Service worker serves stale files on user's device

The app loads correctly in a clean browser (verified via Chrome automation). The user's phone/PWA has an old service worker cached that serves stale JS files, preventing Vue from mounting. The SW has been updated to v9 with cache: 'no-store' for JS/CSS, but the user's device hasn't picked up the new SW yet. Fix: User must clear caches via debug.html, or delete and reinstall the PWA.

Old "Allow all operations" RLS policies still active

Phase 1 auth migration added granular RLS policies, but the old blanket "Allow all operations" policies on users, laps, race_plans, fuel_items were never dropped. These override the new policies, meaning any anonymous user with the anon key can read/write all data. Fix: Run sql/auth-migration-phase1-cleanup.sql in Supabase dashboard.

Warnings

api-service.js cache-bust inconsistency

3 files import api-service.js?v=5 (index-v3, plan-v3, race-day-v3). 6 files import api-service.js with no version (user-manager, analysis-service, race-profile-service, compare-v3, offlineApi, syncManager). Browser treats these as different module specifiers, potentially loading the module twice with separate instances. Fix: Add ?v=5 consistently to all api-service imports.

user-manager.js imports api-service without version

user-manager.js imports import { apiService } from './api-service.js' (no ?v). Pages that import user-manager?v=2 AND api-service?v=5 may get two separate api-service module instances with two separate Supabase clients. Impact: Potential duplicate network requests, inconsistent state.

bibboard.html and reset-password.html missing shell-bundle

These pages don't load shell-bundle.js or performance-bundle.js. If their templates use <app-shell>, Vue will fail to compile. If they don't use it, this is fine (standalone pages).

No build pipeline

All files are manually edited and deployed. No minification, no bundling, no type checking, no linting. Cache busting is manual (?v=N params). Easy to ship stale code or version mismatches.

index-v3.html has debug scaffolding

Global error catchers and dynamic import wrapper were added for debugging. These should be removed before race day. The module import was changed from <script type="module" src="..."> to an inline <script type="module"> with dynamic import().

Info

Auth migration Phase 4 not started

Phases 1-3 complete. Phase 4 remaining: create Supabase Auth accounts for existing users, backfill auth_uid, remove auth.uid() IS NULL anon fallback, enforce RLS, drop password_hash column, remove legacy userId params from api-service.

settings-v3.js uses Composition API

All other page files use Options API. settings-v3.js uses setup() with ref(), reactive(), onMounted(). Functionally fine but inconsistent with codebase convention.

race-day-v3.js is ~2,800 lines with 19 imports

This file is the core of the app and is very large. Any change risks regressions. Consider splitting into smaller composables or sub-components after race day.

~19 deprecated HTML pages in deprecated/ folder

Old v1/v2 pages still exist. Not linked from active navigation but still accessible via URL.