Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
Magic: The Gathering Commander companion — Go + PostgreSQL API, a native Android life tracker with real-time sync, and a Nuxt web client for Moxfield imports, stats and tournaments.
GamesGo · KotlinGitHub @mllagosteraOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
An app for Magic: The Gathering games — Commander format. A Go backend
owns all the real state (auth, decks, games, statistics), consumed by
two independent clients that share no code with each other: a
native Android client (Kotlin/Compose), designed to track life
during the game with the app on the table —
the priority there is that any action takes less than two seconds — and a
web client (Nuxt), designed for what's done better on desktop:
importing decks from Moxfield and reviewing post-game statistics.Read the full README ↗An app for Magic: The Gathering games — Commander format. A Go backend owns all the real state (auth, decks, games, statistics), consumed by two independent clients that share no code with each other: a native Android client (Kotlin/Compose), designed to track life during the game with the app on the table — the priority there is that any action takes less than two seconds — and a web client (Nuxt), designed for what's done better on desktop: importing decks from Moxfield and reviewing post-game statistics.
This document is the entry point for any person or AI starting to work on the repo. Read it before touching code.
The priority is NOT to have hundreds of features. The priority is that any action during a game can be done in under two seconds. Everything revolves around three pillars: simplicity, speed, data. See details in docs/roadmap/ROADMAP.md.
Top level only — the file-by-file map of each app lives in docs/architecture/PROJECT-STRUCTURE.md, which is kept current, so it is not duplicated here.
commander-companion/
├── backend/ # Go API (Fiber + PostgreSQL + sqlc + goose)
│ ├── cmd/api/ # entrypoint (main.go)
│ ├── internal/ # one package per feature (auth, users, decks, games, game-actions,
│ │ # playgroups, statistics, tournaments, friends, admin, sync,
│ │ # websocket, ...) plus shared ones (common, config, email, notify)
│ ├── migrations/ # goose migrations
│ └── Makefile # run, test, lint, migrate, generate-sql
├── android/ # Native Android client (Kotlin, Compose, Hilt, Room, Retrofit)
│ └── app/src/main/java/com/vansid/tapeandocartones/
│ ├── presentation/ # screens, viewmodels, navigation, theme — depend on domain/
│ ├── domain/ # use cases + repository interfaces that data/ implements
│ ├── data/ # Retrofit, Room, repositories (decide Room vs. backend), SessionManager
│ └── core/ # DI (Hilt) and utilities
├── web/ # Web client (Nuxt 4 SSR + Tailwind), see ADR-0004
│ ├── server/ # Nitro BFF: the only code that touches session cookies or calls the Go API
│ └── app/ # pages, components, composables (one per REST slice), middleware
├── docs/ # sources of truth, ADRs, roadmap — see §8 for the full index
├── scripts/screenshots/ # demo-data seed + Playwright capture for the landing and Play Store images
├── .github/ # CI workflows (§6), check-architecture.sh, i18n checks, PR template
└── docker-compose.yml # db + api + web, to try the full stack locally
Before assuming how something works, consult the corresponding document — not another module's code by analogy, and not memory from previous conversations. Full detail (what each one defines, where it lives) in docs/architecture/ARCHITECTURE.md §"The 4 Sources of Truth": DBML, OpenAPI 3.1, Mermaid, ADR.
Rule: if you're going to change how the backend and its clients communicate, edit openapi.yaml first. If you change the data model, edit schema.dbml first and then create the goose migration.
Full instructions for agents live in AGENTS.md — CodeGraph usage, language rules, architecture rules per area, quality gates and git workflow. What follows is the summary.
docs/roadmap/TASKS.md first. It's the list of pending tasks organized by stage, with real status audited against the code (not against what "should" exist). It's a compact checklist on purpose — for the narrative behind any item (why, gotchas, how it was verified), see docs/roadmap/DECISIONS-LOG.md; don't read the whole log up front, only the entries you actually need.TASKS.md.TASKS.md before picking something up: the original build-out sequence is complete, and that section now lists what is left (what is blocked on the owner, not on code, included), unless the user explicitly asks to prioritize something else.docs/decisions/.openapi.yaml / schema.dbml (and the corresponding migration) as part of the same change, not afterward.docs/roadmap/TASKS.mdTASKS.md is a living document — it gets updated in the same change that resolves the task, not as a separate step. Since 2026-08-01 it's deliberately kept compact: one short line per item (status + what + a file/module pointer). Narrative — why, gotchas hit, how it was verified, dates, exact user requests — goes in docs/roadmap/DECISIONS-LOG.md instead, not back into TASKS.md; that split exists specifically to keep a new session from having to load the whole project history just to check status.
- [x] only when the task is functionally complete (it builds and works), not when the file simply exists or compiles with a stub.- [ ] and add a short note in parentheses explaining what's missing (see examples already present in the document).DECISIONS-LOG.md under the matching stage and link to it from the TASKS.md line instead of inlining the detail.**Last reviewed:** line at the end of the work session with the current date, and add the corresponding entry to DECISIONS-LOG.md's audit/session history.schema.dbml, openapi.yaml, diagram, ADR) was updated before marking it done.In .github/workflows/ there are five quality pipelines that run on every push/PR, plus security-ci.yml, which runs on PRs only (the four per-area ones each carry a changes/dorny-paths-filter job that always reports a check, so none is left "hanging" on PRs that do not touch its folder; architecture-ci.yml deliberately has none, see below). Every check name is in English; they double as the identifiers branch protection matches on, so renaming one means updating the protection settings in the same pass.
backend-ci.yml: gofmt + go vet, golangci-lint, verifies that sqlc generate leaves no uncommitted diffs, build + go test -race + applies the goose migrations against a real Postgres from the job, and hadolint on backend/Dockerfile.android-ci.yml: Android Lint, unit tests (testDebugUnitTest), assembleDebug, and string resources translated in every locale.web-ci.yml: ESLint + typecheck (vue-tsc) + nuxt build (SSR), i18n keys resolve in every locale, and hadolint on web/Dockerfile.docs-ci.yml: validates that the sources of truth remain valid — Spectral lint on openapi.yaml, schema.dbml compiling to SQL, and schema.dbml matches the migrations, which builds the schema twice in one Postgres (once with goose, once from the compiled DBML) and diffs information_schema. Compiling only proves the DBML is well-formed; this proves it is true. It compares tables, columns, types and nullability — not indexes or constraints, since DBML cannot express partial indexes and dbml2sql renders unique constraints as unique indexes, so those comparisons report differences that aren't drift. It runs when backend/migrations/** changes too, not just docs/: a migration alone can invalidate the DBML, which is how deck_resync_jobs went undocumented for weeks.security-ci.yml (pull requests only): known-vulnerability scan of the dependencies — govulncheck for the Go backend (reports only vulnerabilities in code the binary actually calls) and npm audit through audit-ci for the web's production dependencies, failing on high/critical. It runs on PRs and not on push because Render deploys main once the pushed commit's checks pass, and a scan there would let an advisory published against untouched code block every deploy. Advisories with no patched release that don't reach the deployed bundle are allowlisted, with the reason, in web/audit-ci.jsonc.architecture-ci.yml: runs , which enforces what and otherwise only assert in prose — the Handler layer free of persistence, no slice reaching into another slice's sqlc , SQL confined to , the web client never bypassing the Nitro BFF, every registered route present in and no phantom paths, and every document under linked from this hub (§8). It also warns, without blocking, when 's review date has fallen behind the code. Unlike the other four it has : the invariants are cross-cutting (a backend edit breaks the OpenAPI check, a docs edit breaks the hub check) and the whole script is a handful of greps. It exists because of a 2026-09-03 audit: every rule with a check was at 100% compliance, while three that lived only in prose — link new ADRs from the hub, keep fresh, keep true — had all drifted despite being clearly written. In a repo worked on only by agents, each starting cold, a rule that isn't checked isn't a rule.Architecture guardrails. Beyond the pipelines above, the layering itself is enforced by four mechanisms, each owning the rules it can express natively and nothing checked twice: depguard in backend/.golangci.yml (a handler must not import a database driver), Konsist as unit tests in testDebugUnitTest (Android layering and the enumerated auth exception; two of its rules started as ratchets over known debt and are plain invariants since that debt was paid off, see ADR-0019), eslint-plugin-boundaries plus no-restricted-imports in web/eslint.config.mjs (app/ ↔ server/, both directions), and check-architecture.sh for everything that is not an import. The three native tools ride gates that already exist, so they cost no new CI job. Full breakdown of who owns what, and why the cross-slice Queries rule can only live in the script, in AGENTS.md §7.
The i18n checks (.github/scripts/check-i18n-{web,android}.mjs) exist because a missing translation key fails nothing else: on the web Vue renders the key itself and the page still returns 200; on Android the string falls back to the default locale and shows up in Spanish. Both run in seconds without a build. Each verifies that the locales define the same keys and that every key referenced in the source resolves. Android resources deliberately left untranslated must carry tools:ignore="MissingTranslation", the same annotation Android Lint honours.
Note on branch protection: which checks are required on main is configured in GitHub's branch protection settings, not in this repo, so this document can't keep that list true — check the settings. On 2026-10-17 GitHub reported 12 required checks. One non-obvious detail does live in the repo: the hadolint (Dockerfile) job in web-ci.yml has the same name as the one in backend-ci.yml, and GitHub matches required checks by job name, not by workflow, so either one satisfies a requirement on that name.
Before considering a task done, run sh .github/scripts/check-architecture.sh plus the local gate for whatever you touched: make lint && make test in backend, npm run lint && npm run typecheck && npm run build in web, ./gradlew lintDebug testDebugUnitTest in Android. The definition of done is in AGENTS.md §7.
Full stack (from the root, requires Docker):
docker compose up --build # db + api + web in containers (see web/README.md)
The first time, the backend migrations need to be applied by hand (they
don't run automatically inside the container), see backend/Makefile (make migrate-up).
Backend (cd backend):
make run # run the API locally
make test # go test -v -race -p 1 ./... (some tests need Postgres)
make lint # golangci-lint
make generate-sql # regenerate repos with sqlc after editing query.sql
make migrate-up # apply goose migrations
Web (cd web, Node 24+ / npm 11+ — enforced by engines + engine-strict):
npm install
npm run dev # http://localhost:3000, requires the API running separately
npm run lint # eslint (CI uses --max-warnings=0)
npm run typecheck # vue-tsc
npm run build # nuxt build (SSR)
Android (cd android, JDK 21):
./gradlew assembleDebug # build
./gradlew testDebugUnitTest # unit tests (Konsist architecture tests included)
./gradlew lintDebug # Android Lint
./gradlew connectedAndroidTest # instrumented tests
This is the full index — every document in the repo should be
linked from here. If you add a new document under docs/ (or a README
for a new module), add it to this list too in the same change.
Start here:
Sources of truth (see §3, full detail in ARCHITECTURE.md):
Diagrams (docs/diagrams/):
games) and of each player's lifecycle (game-actions).NavHost/routes).Use cases and wireframes (docs/ux/):
ADRs — technical decisions (docs/decisions/):
READMEs per module:
make), backend stack..github/scripts/check-architecture.shARCHITECTURE.mdPROJECT-STRUCTURE.mdQueriesquery.sqlopenapi.yamldocs/TASKS.mdTASKS.mdARCHITECTURE.md