React + Vite + TypeScript frontend for start.ng. Homepage with the games list, plus email/password and Google sign-in against startng-be.
To add a game to the homepage, add it to src/games.ts (it links to https://<slug>.start.ng).
cp .env.example .env # set VITE_API_URL and VITE_GOOGLE_CLIENT_ID
npm install && npm run devEvery new feature lives in src/features/<feature>/ with one owner (the PRD, section 3).
- Copy
src/features/_templateand rename things, then add one line tosrc/features/index.ts(import "./<feature>";). - From your folder's
index.tsx, register what you add (src/features/registry.ts). Everything you register is hidden while your flag is off (GET /featureson the API;useFeature("name")in components).- Pages:
registerPage({ feature, path, shell: "app" | "bare", load: () => import("./Page") })."app"sits inside the start.ng shell (top bar, sign-in). A RegExp path's named groups becomeparams. Also add the address tonginx.conf(onelocationline servingindex.html). - Game cards:
registerCardPart({ feature, key, slot: "badges" | "stats" | "actions", render: (game) => ... }). Returnnullto leave a card alone. Cards aresrc/GameCard.tsx; don't edit it for a feature. - Admin tabs:
registerAdminTab({ feature, key, label, permission, Component }). You getaccess,can,goand the shared confirm dialogask. Register the permission on the API side (PERMISSIONS). - Your games:
registerYourGamePart({ feature, key, Component }), shown on each of the owner's games (withgameandreload). - Profile sections:
registerProfileSection({ feature, key, title, Component }). The profile page shows them with<ProfileSections userId isMe />. - Boxes on start.ng's own pages:
registerPagePart({ feature, key, page: "tournament", Component }), shown where the page puts<PageParts page="..." />(withuserandonSignIn).
- Pages:
- Your API calls live in your folder too (
src/features/<feature>/api.ts), usingcallandsendfromsrc/api.ts. Admin tabs and profile sections are wrapped in an error boundary, so a crash in yours doesn't take down the page.
src/features/moderation; flag moderation. Admin tab Name filter (permission moderation.words): try a name,
add blocked or allowed words, see names already on start.ng that fail, and the built-in lists. The filter itself runs
on the API; when it refuses a name, the form shows the API's message.
src/features/needs_fixing; flag needs_fixing.
- Admin tab Needs fixing: mark a game with a note for its owner; games their owners say are fixed come first, then games waiting for the owner. Put back, send back, or change the note.
- Your games: the owner sees what's wrong and "tell us it's fixed".
src/features/duplicates; flag duplicates.
- Admin tab Duplicates: look-alike pairs side by side (older first) with why they match; hide as copy, delete (type DELETE to confirm), keep both; scan all games; put hidden copies back.
- Your games: "Hidden: looks like a copy", naming the original. Hidden games show a Hidden tag instead of Live.
src/features/email; flag email.
- start.ng/emails: signed-in players switch topics on and off; visitors sign up and confirm by link. The confirm
and unsubscribe links in emails land here (
?confirm=,?unsubscribe=&topic=). - Admin tab Email (permission
email.view): subscribers by topic, the outbox by status, and "Send me a test email". - An Emails section on your own profile, shown once the profile page shows sections.
src/features/game_health; flag game_health.
- Admin tab Game health (permission
games.health): counts by status, failing and unhealthy games with what's wrong, "Check now" and "Check all again". - Your games: a Not loading notice on an unhealthy game, with "check again".
Part of src/features/email: the Get the tournament by email box on start.ng/tournament (a registerPagePart).
Visitors leave an address and confirm by link; signed-in players turn on tournament and results emails in one click.
Dockerfile build pack (nginx, port 80). VITE_API_URL and VITE_GOOGLE_CLIENT_ID are build-time, so mark them as Build Variables in Coolify. GitHub secrets: COOLIFY_WEBHOOK_URL (optional COOLIFY_TOKEN); the Deploy (Coolify) workflow runs only after Malware Scan (CI Gate) passes on main.
src/analytics.ts loads Google Analytics 4 (measurement ID G-9K8146WXDG, start.ng's web stream) on the live
site only: never in npm run dev, on localhost, or on /connect (games load it invisibly). Page views come
from GA itself; the app also records view_item (a game page), play_game, share (X or copy link),
login, tournament_enter and publish_game. Nothing to set in Coolify; a build variable
VITE_GA_MEASUREMENT_ID can change the ID, or off switches it off.
A game gets a leaderboard by shipping a startng.json that lists its own stats (for example waves and kills,
or gems and time). The homepage shows a Leaderboard button only on games that have one, with that game's
columns. Games save runs and read the board through the API with a game session: after
start.ng/?return_to=<game url>, start.ng sends the player back with #startng_session=....
See the builder documentation and the startng-be README.