A video game discovery app built with React Native and Expo, powered by the RAWG database.
Responsive from phone to desktop.
Where this project is heading: ARCADE (2021) is being resurrected and evolved into Respec — an app for time-poor gamers that answers "what can I actually finish?" The full product spec lives in
docs/PRODUCT.md, and validation work indocs/validation/. This README covers the current codebase: a modernised version of the original game-discovery app.
- Expo SDK 57 / React Native 0.86 / React 19
- TypeScript (strict) · expo-router file-based navigation
- ESLint + Prettier · CI via GitHub Actions
- RAWG API for game data
git clone https://github.com/ginoleeswan/sidequest
cd sidequest
npm install
# API key (free): https://rawg.io/apidocs
cp .env.example .env # then paste your key in
npx expo startOpen in Expo Go, an emulator, or press w for web.
| Command | |
|---|---|
npm run typecheck |
TypeScript, no emit |
npm run lint |
ESLint |
npm run format |
Prettier write |
npm test |
Jest (unit and component) |
npm run test:coverage |
the same, against the coverage floor CI enforces |
npm run test:hydration |
builds nothing — runs e2e/hydration.mjs over dist/ |
npm run test:a11y |
axe (WCAG A/AA) over dist/, both widths |
npm run test:icons |
every named icon draws from the subset font |
npm run test:data |
data-full screens say the sentences a reader acts on |
npm run test:perf |
4G + 4x CPU: bytes, FCP, LCP, CLS against budgets |
npm run test:live |
sync against a real Supabase project (see below) |
npm run build |
sitemap + static web export into dist/ |
node scripts/subset-icons.mjs |
regenerate the Ionicons subset (after adding an icon) |
node scripts/duration-coverage.mjs |
data-source validation (see docs/validation/) |
node scripts/brand-assets.mjs |
regenerate icons, splash and OG card from the mark |
EAS lives in its own scripts — build:dev / build:preview / build:prod
and update:preview / update:prod — plus npm run prebuild for the
native projects. scripts/brand-assets.mjs also writes the splash
wordmark that plugins/withSplashWordmark patches into the iOS
storyboard, which is why the two share their size constants.
test:hydration and test:a11y need a dist/ from npm run build, and a Chromium that
Playwright can launch. Where the installed browser does not match the
Playwright build (a sandbox with its own browsers, say), point at it:
CHROMIUM_PATH=/path/to/chrome npm run test:hydrationtest:live is the only suite that talks to a real database, and it is
deliberately outside npm test and outside CI. Everything else about
sync runs against a backend we wrote ourselves, which proves the engine
agrees with our understanding of Postgres rather than with Postgres — an
upsert whose conflict target misses the index, a policy that refuses the
anon role, a trigger that never fires: none of those can fail against a
fake. This one signs in and drives real rounds through PostgREST as two
devices, including a conflict, a delete and a row the server refuses.
It writes rows and deletes them again, but it is still somebody's database, so point it at a throwaway account:
SIDEQUEST_LIVE_URL=https://<ref>.supabase.co \
SIDEQUEST_LIVE_KEY=<publishable key> \
SIDEQUEST_LIVE_EMAIL=<throwaway user> \
SIDEQUEST_LIVE_PASSWORD=<their password> \
npm run test:liveIt runs on plain Node rather than the app's jest preset, which matters:
jest-expo installs React Native's fetch, and in a test process that is
a stub which resolves with no status and no body — a suite that cannot
reach the network while looking like it did.
The web build is a static export — vercel.json is already configured.
- Import the repo at vercel.com/new (framework
preset: Other —
vercel.jsonsupplies the build command and output directory). - Add environment variables in the project settings:
RAWG_API_KEY= your RAWG key. Server-side only (used byapi/rawg-proxy.tsandapi/preview.ts) — kept out of every client bundle. Web and native both call RAWG through this proxy, which answers from the edge cache; a native build only needsEXPO_PUBLIC_RAWG_API_KEY(withEXPO_PUBLIC_RAWG_DIRECT=1) if it should bypass the deployment and call RAWG itself.STEAM_API_KEY= your Steam Web API key (steamcommunity.com/dev/apikey) — powers Steam connect on The Plan. Server-side only (used byapi/steam.ts), never shipped to the browser. Optional: without it, Steam connect shows a friendly "not configured" message.STEAMGRIDDB_API_KEY= a SteamGridDB key (steamgriddb.com/profile/preferences/api) — the publishers' own artwork: title logos over the art, the wide heroes behind them on the desk, box art where IGDB has none, and the square marks on the plan's rows. Server-side only (used byapi/art.ts). Optional: without it, the same come from Steam's CDN for games with a Steam release, and the rest draw from RAWG.TWITCH_CLIENT_IDandTWITCH_CLIENT_SECRET= a Twitch application (dev.twitch.tv/console/apps) — IGDB authenticates through Twitch, and these power the reported time-to-beat figures the plan is built on. Server-side only (used byapi/igdb.ts). Optional: without them every length falls back to RAWG's average, which is the behaviour before they are set.
- Deploy. Dynamic game pages (
/game/123) are served via the rewrite invercel.json. - Universal links come with it:
public/apple-app-site-association.jsonis served at/.well-known/apple-app-site-association, so a game or plan link opens the installed iOS app. The file names every build variant's bundle id under the team inapp.json; a new variant needs a line there, andsrc/lib/__tests__/universalLinks.test.tschecks the two agree.
Or from the CLI: npx vercel --prod (after npx vercel env add RAWG_API_KEY).
Live at gosidequest.vercel.app.
src/
app/ # expo-router routes — (tabs)/, game/[id], about, account
api/ # typed clients: RAWG, IGDB, Steam, Twitch
components/ # UI components
lib/ # the logic: library, scheduler, sessions, auth, widgets…
hooks/ # useBreakpoint, useTonightPick, usePersistedState…
constants/ # icon subset, categories
styles/ # colors, typography, motion, theme
api/ # Vercel serverless functions (proxies, previews, report)
plugins/ # Expo config plugins (splash wordmark)
targets/ # iOS widgets (Swift, via @bacons/apple-targets)
supabase/ # migrations for the optional sync backend
scripts/ # brand assets, icon subset, duration coverage
e2e/ # the browser battery: hydration, a11y, perf, icons
assets/ # fonts, icons, images
docs/ # product spec, validation, screenshots