No description
  • TypeScript 68.7%
  • CSS 31%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-07 10:24:32 +02:00
.vscode Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
server/routes/api Fix premature audio looping and add replay control 2026-09-07 10:24:32 +02:00
src Fix premature audio looping and add replay control 2026-09-07 10:24:32 +02:00
tests Fix premature audio looping and add replay control 2026-09-07 10:24:32 +02:00
.cta.json Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
.cursorrules Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
.dockerignore Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
.env.example Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
.gitignore Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
biome.json Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
bun.lock Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
components.json Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
compose.yaml Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
Dockerfile Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
package.json Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
playwright.config.ts Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
README.md Fix premature audio looping and add replay control 2026-09-07 10:24:32 +02:00
tsconfig.json Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
tsr.config.json Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00
vite.config.ts Build Navster multiplayer game and container deployment 2026-09-07 00:07:28 +02:00

Navster

A Hitster-inspired music timeline game using your Navidrome library. Built with TanStack Start, React, Tailwind CSS, shadcn/ui, and Nitro WebSockets.

Run it

bun install
cp .env.example .env
# Fill in NAVIDROME_URL, NAVIDROME_USERNAME, and NAVIDROME_PASSWORD.
bun run dev

Open http://localhost:3000. Credentials stay on the server and are never included in browser bundles or invite links.

For friends on your local network, use bun run dev -- --host 0.0.0.0 and share your computer's network address. For online play, deploy behind HTTPS with WebSocket upgrade support.

bun run build
bun run start

Production requires Node 22+ and .env (or use node .output/server/index.mjs with environment variables supplied by your platform). Set PORT to change the default port. Run one server instance, or configure sticky routing so all connections for a room reach the same instance.

Deploy with Docker Compose

Copy compose.yaml and .env.example to the deployment machine, then:

cp .env.example .env
# Fill in the three NAVIDROME_* values in .env.
docker compose up -d

Compose pulls gitea.gametec-live.com/gametec-live/navster:latest; no local build, database, or volumes are needed. The image supports Linux AMD64 and ARM64 and runs as a non-root user. Credentials are supplied at runtime and excluded from both Git and the image. Open port 3000, or set NAVSTER_PORT in .env to change the published port.

For a private registry package, first run docker login gitea.gametec-live.com using a Gitea username and a token with package read access. For internet access, put the service behind an HTTPS reverse proxy that forwards WebSocket upgrades on /api/room. Run one replica: the relay keeps room connections in memory, so a restart interrupts active rooms. Song sets remain in each player's browser.

To update, run docker compose pull && docker compose up -d. Use docker compose logs -f for logs. Published images also have a sha-<short-commit> tag; replace latest in Compose with that tag to pin a release.

To build and publish a new image from a committed checkout:

docker login gitea.gametec-live.com
docker buildx build --platform linux/amd64,linux/arm64 \
  -t gitea.gametec-live.com/gametec-live/navster:latest \
  -t gitea.gametec-live.com/gametec-live/navster:sha-$(git rev-parse --short HEAD) \
  --push .

The build installs from bun.lock, runs type checking and game-rule tests, then copies only the production output into the distroless Node runtime.

Play

  1. Enter your name and create a lobby. Share its six-character code or invite URL.
  2. Choose all songs or a saved song set and adjust the house rules.
  3. Every player gets a revealed starting card. On each turn, place the mystery track in its chronological position and optionally identify its artist and title. Tentative placements and typed guesses update live for everyone until the active player locks in.
  4. Everyone else may skip or spend a chip to challenge in the older/newer direction. Multiple throws enter a timed year tiebreak.
  5. The first player to reach the card target wins. The host returns everyone to the lobby for another game, or closes the room.

A solo practice game is also supported. Rooms hold up to 12 players. People arriving during a game spectate until the next lobby.

Rules and defaults

  • 10 cards to win, including the starting card; 2 starting chips.
  • No player timeout; 30 seconds for challenges and year tiebreaks.
  • Exact naming ignores casing and repeated whitespace. Relaxed naming checks whether a combined title/artist Navidrome search includes the current track. Players mode uses a strict majority of the other players, with one vote per player.
  • Equal release years can sit in either chronological order.
  • A correct original placement always keeps its card. Otherwise the correct directional throw wins; multiple throws use distance from the actual year. Equal distances break clockwise from the active player.
  • Every throw consumes its chip. Missing year guesses forfeit; unanswered challenges are skips. Player timeouts discard the current card.
  • A card at the oldest end can only be challenged as newer; a card at the newest end can only be challenged as older. Impossible directions are disabled and rejected without spending a chip.
  • After the reveal, the active player selects Next turn to hand off. The host can substitute if that player disconnects. Returning to the lobby after a win remains a host action.
  • Naming chips can be earned even when the timeline placement is wrong.
  • Only tracks with a positive release year are playable. The Navidrome year tag is the source of truth, so compilation/reissue tags may differ from the original single's release year.

Music and song sets

The read-only /api/music proxy signs Subsonic requests using server-side credentials and streams audio and cover art. All songs pages through the complete library. The set editor supports individual songs, whole albums, an artist's albums, and genre browsing. Open a genre to select tracks or add all its songs with a release year; genre loading fetches every page. The Included tab lists the full set with title, artist, album, and year filtering, individual removal, and undo. Editing a saved set opens this list; changes apply when you save.

Song sets live in localStorage. Sharing compresses the complete set into a URL fragment; opening it imports the set into that browser. Large sets produce long links, so smaller themed crates are easier to share. Names and house-rule preferences also stay in the browser.

Host-only playback is the default. Disable it for remote play. Browsers may require a tap on Play before allowing audio. Playback initially aligns to the round timestamp, then plays to completion without looping. Play/pause, replay from the beginning, and volume are local controls; replay does not reset the round or other players' audio. Synchronization is approximate, not sample-accurate multiroom audio.

State and reconnects

The host browser owns the deck, game rules, timers, and state transitions. /api/room is an in-memory relay that authenticates each connection using a private reconnect token and routes player actions to the host. It stores room membership and tokens only: no game snapshots, database, or permanent server state.

The host keeps a sessionStorage snapshot so reloading the same tab resumes the game. Guests receive a fresh snapshot on reconnect. The deck and unrevealed metadata are omitted from guest snapshots. This is a casual, trusted-host game: the host can inspect the answers, and the accessible library is not a competitive anti-cheat boundary.

Keep the host tab open. A disconnected host has 45 seconds to return before the room closes; game deadlines continue to elapse. Disconnected players keep their live-game seats and are skipped on subsequent turns. If the active player disappears without a timer, the host can skip that turn. Closing a room clears its temporary server connections. Closing the browser tab can lose its session snapshot.

Checks

bun run typecheck
bun run test
bun run build
bunx playwright install chromium
bun run test:e2e

The unit suite exercises game rules and permissions. Browser tests use the configured live Navidrome instance to verify real two/three-player WebSockets, refresh recovery, bonus chips, directional throws and year tiebreaks, lobby return, song-set editing and sharing, and mobile layout.