Community-run transit monitoring for Singapore MRT/LRT disruptions.
mrtdown-site is a TanStack Start React app deployed to Fly.io. The Node server
reads canonical mrtdown data through a Postgres/PostGIS-backed read model
populated from data archives by durable Upstash Workflows.
- React 19, TanStack Start, TanStack Router, and Vite.
- Node.js and
srvx, packaged as a container and deployed to Fly.io. - Upstash Workflow and QStash for durable jobs and recurring schedules.
- Postgres/PostGIS with Drizzle ORM and Drizzle Kit migrations.
- Redis-compatible Valkey locally and Redis in deployed environments for short-window rate limiting.
- Biome, TypeScript, and Vitest for quality checks.
-
Install dependencies:
npm ci
-
Create local environment variables:
cp .env.example .env
The example points
VITE_ROOT_URL,DATABASE_URL, andREDIS_URLat the local services.QSTASH_DEV=trueenables the local QStash development server used by Upstash Workflow. -
Start local Postgres/PostGIS and Valkey services, then run migrations and seed fixture data:
docker compose up -d postgres valkey npm run db:migrate npm run db:seed:fixtures
-
Start the app:
npm run dev
Visit http://localhost:3000.
npm run dev: start the Vite dev server.npm run build: build the app.npm run start: serve an existing production build withsrvx.npm run typecheck: run TypeScript without emitting files.npm run lint: run Biome linting.npm run format:check: check formatting across the repository.npm run test:run: run Vitest once.npm run verify: run typecheck, lint, formatting check, migration drift check, and tests.npm run db:generate: generate Drizzle migrations after schema changes.npm run db:generate:check: verify schema changes do not require a new migration.npm run dev:pull: trigger the local pull workflow endpoint while the dev server is running.npm run dev:public-holidays: trigger the local public-holiday workflow.npm run qstash:schedules:sync: create or update the three managed QStash schedules. Deployment workflows normally own this command.
app/routes: file-based routes, route loaders, page metadata, and API routes.app/util/*.functions.ts: TanStack server functions used by loaders and client code.app/util/db.queries.ts: DB-backed read model queries.app/db: Drizzle schema, enum helpers, and database connection setup.app/workflows: Upstash Workflow definitions that synchronize canonical data and public holidays.app/server.ts: Node server entry, request instrumentation, and Markdown request negotiation..github/scripts/syncQstashSchedules.mjs: idempotent QStash schedule provisioning used by deployment workflows.app/components: reusable UI and page-level components.docs: architecture, data pipeline, quality, and generated-file notes.
Canonical mrtdown archive data is fetched by the pull workflow, inserted into
*_next staging tables, promoted into normalized live tables, and read by server
functions through app/util/db.queries.ts.
The generated MRTDown API client has been retired. Server-side reads use the
DB-backed query layer, with canonical domain types coming from @mrtdown/core
where possible.
Do not hand-edit generated files unless the task is explicitly about generated output. Regenerate them through the relevant script or tool, then review the produced diff.
app/routeTree.gen.ts: generated by TanStack Router tooling.drizzle/**: generated by Drizzle Kit withnpm run db:generate.app/components/StationMap/components/Map*.tsx: mechanically converted station map snapshots.
See docs/GENERATED_FILES.md for more detail.
docs/ARCHITECTURE.md: current app architecture and deployment branch mapping.docs/DATA_PIPELINE.md: canonical data pull and read model flow.docs/CACHING.md: Cloudflare edge-cache policy, invalidation, and required deployment configuration.docs/QUALITY.md: verification harness and current test gaps.docs/OVERHAUL_BASELINE.md: historical baseline for the read-model migration.docs/plans/README.md: conventions for checked-in execution plans.docs/plans/active/: active multi-phase work, including read-model overhaul and production performance plans.docs/investigations/: dated investigation notes that inform plans and architecture docs.
Run npm run verify before publishing changes. Use narrower commands such as
npm run typecheck, npm run lint, npm run format:check, and
npm run test:run while iterating.
Commit subjects and pull request titles should follow
Conventional Commits, for example
feat: add station history filters or fix: handle missing fact coverage.
Agent-created commits must include the appropriate co-author trailer; Codex
commits use Co-authored-by: Codex <codex@openai.com>.