FMC tech stack
Default stack for geo-heavy React SPAs at FixMyBerlin / FMC. Use for tech decisions when creating or changing an app — not as a deep dive into any one library.
Install sibling skills separately: bunx skills add FixMyBerlin/fixmyskills --skill <name> -a cursor -y
When to apply
- Greenfield app scaffold (dependencies, tooling, folder layout)
- Adding or renaming
package.json scripts
- Library or pattern choice (“should we use X?”)
- Cross-cutting convention review on an existing app
- Cursor MCP setup for Postgres schema inspection, agent DB queries, or browser exploration MCP
LLM resources
Load references/llm-resources.md only for the area the task touches.
Maps: load skill react-map-gl (and upstream React Map GL docs when the skill points there).
Specialized skills
Prefer installed skill names when present; otherwise fetch from git.
Runtime and build
- Runtime / package manager: Bun — install policy bun-install.md (≥ 1.3.14, global store,
bun --bun for Vite, .nvmrc for Prisma/Playwright)
- Build: latest Vite (8+)
- Lint / format: oxlint and oxfmt with fix flags; Prettier-compatible defaults:
- class sorting, import sorting,
package.json sorting
printWidth 100, semicolons asNeeded, single quotes
'typescript/switch-exhaustiveness-check': 'error'
- React Compiler: oxlint
react plugin + 'react/unsupported-syntax': 'error'; Vite via viteReact({ compiler: true }) + oxc-transform-react (not Babel)
- Templates: examples/oxfmt.config.mjs, examples/oxlint.config.mjs
- Setup and per-project tuning: references/oxc-config.md
- Client browser target:
browserslist in package.json drives Vite client build.target and eslint-plugin-compat in oxlint — references/browser-target.md
React and TypeScript
- UI: React 19
- TypeScript (default):
typescript@^7 (native port; 7.0 release). Typecheck with tsc --noEmit via bun run type-check. The workspace typescript package provides both CLI (tsc) and editor language service — do not add a separate compiler binary for type-check. Nightlies: typescript@next (not the retired @typescript/native-preview package).
- Editor/CLI alignment:
js/ts.tsdk.path must resolve to the same devDependencies.typescript install as tsc (package root, not lib/). Misalignment makes IDE diagnostics disagree with bun run type-check.
- Scaffold (TS 7):
package.json — devDependencies.typescript ^7, script "type-check": "tsc --noEmit" (add -p when using split tsconfigs); pin @typescript/typescript-darwin-arm64 / @typescript/typescript-linux-x64 in optionalDependencies at the same version when CI runs on a different platform than dev (the typescript package lists these as optional — extend for other platforms as needed). bunfig.toml — bun-install.md. Commit .vscode/settings.json (template: examples/vscode.settings.typescript.json.template) and recommend the TypeScript 7 extension in .vscode/extensions.json (examples/vscode.extensions.json.template).
- Monorepos: when TypeScript lives in a package subfolder, prefix
js/ts.tsdk.path to that package’s node_modules/typescript (e.g. app/node_modules/typescript — tilda-geo).
- Cursor / VS Code: install TypeScript 7 (
TypeScriptTeam.native-preview — marketplace ID unchanged). Enable via command palette TypeScript: Enable TypeScript 7 or the settings template. Copy examples/vscode.settings.typescript.json.template into .vscode/settings.json (merge with oxc keys from references/oxc-config.md). Toggle back with Disable TypeScript 7 Language Server when editor plugins still require TS 6 (Vue, Astro, Svelte, Angular templates — see release notes).
- Cursor MCP (user-level):
~/.cursor/mcp.json only — examples/mcp.json.template. Postgres: references/cursor-mcp.md. Browser exploration: references/agent-browser-mcp.md. Never commit MCP config to a repo.
- Cursor rules (app-level): copy examples/package-json-scripts.md.template →
.cursor/rules/package-json-scripts.md on scaffold — references/package-json-scripts.md
Verify: node -e "console.log(require('typescript/package.json').version)", tsc --version, and bun run type-check — all 7.x. In the editor, confirm TypeScript 7 is enabled (status bar / command palette). Do not add typescript to repos that have no TS source (e.g. this skills monorepo).
- React Compiler: on by default — see skill
react-dev for memoization and typing conventions
- GeoJSON:
@types/geojson for all GeoJSON payloads
- Dates / times:
@date-fns/tz
- Validation: Zod 4
tsconfig templates
Use two profiles — do not merge app and scripts into one config.
| Profile |
Template |
Profile analogue |
| App (DOM, TanStack Start) |
examples/tsconfig.app.json |
total-typescript bundler/dom |
| Scripts (CLI, no DOM) |
examples/tsconfig.scripts.json |
total-typescript bundler/no-dom |
Copy and adapt on scaffold. Adjust paths to project layout (./src/* vs ./*). Add allowJs: true only when the repo still has .js files.
TanStack Start (app only): verbatimModuleSyntax: false — type-only imports can become empty runtime imports and leak server code into client bundles. See header comments in tsconfig.app.json.
Never install or extends @total-typescript/tsconfig — keep settings inlined with deviation comments.
Typecheck:
tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.scripts.json
Single-config repos (e.g. one root tsconfig.json covering app + scripts) are acceptable; greenfield TanStack Start apps should prefer split configs.
Optional root tsconfig.json with "references" to both child configs.
ES2025: target and lib stay in sync. lib adds typings only; new ES2025 runtime APIs in client bundles are not auto-polyfilled — use deliberately on the client; fine on server/Bun/scripts. Which browsers get that client bundle: references/browser-target.md.
Component typing, Compiler, oxlint React rules, and useEffect discipline: skill react-dev. Map camera, clicks, and layers: skill react-map-gl (not useEffect + map.on()).
Data and state
| Need |
Choice |
| Server / async data |
TanStack Query — no raw useEffect fetch; Suspense where the router supports it |
| Forms (non-trivial) |
TanStack Form |
| Routing |
TanStack Router / Start |
| Shareable URL state (TanStack apps) |
Route validateSearch (Zod) — see tanstack-router-conventions → params-search-ui-routes.md |
| Shareable URL state (Next.js) |
nuqs — see skill nuqs |
| High-frequency URL updates |
@tanstack/react-pacer — see https://tanstack.com/pacer/latest |
| Global client state |
Zustand (tkdodo patterns) — skill zustand-state-management |
| Local UI state |
useState |
| Prisma schema / migrations |
skill prisma — naming, relation fields, migrate workflows |
Loader + Query integration: router-and-query.md.
Styling and UI
- CSS: Tailwind CSS,
@tailwindcss/forms, @tailwindcss/typography
- Class merging:
tailwind-merge with twJoin, twMerge
- Fonts: Fontsource imports
- Accessibility: semantic HTML; keyboard-accessible map controls; do not rely on color alone
Maps and geo (JS)
Stack choices only — map component patterns, layers, events, URL viewport: skill react-map-gl.
| Choice |
Default |
| Library |
react-map-gl/maplibre (not raw MapLibre in components) |
| Basemap |
OpenFreeMap; Maptiler if required |
| Coordinates |
WGS84, [lng, lat] at API boundaries; validate with Zod |
| Turf |
@turf/* per-function imports — not import * as turf |
| Feature editing |
tilda-geo drawing, maplibre-gl-terradraw |
Rust / WASM geo
Turf vs WASM, crates, Vite wiring: skill rust-wasm-geo.
Dependency updates (Dependabot)
- Schedule by project type — AskQuestion if unclear. FixMyCity: weekly Monday. Private OSS and Astro: first Friday of the month. Same template. Details: dependabot.md.
- One open PR at a time per ecosystem (
open-pull-requests-limit: 1).
- Template: examples/dependabot.yml.template
- Reviewing and merging PRs: skill
review-dependabot (changelog triage, risk tiers, rebase merge)
CI (GitHub Actions)
- PR dependency review: permissive
allow-licenses (not deprecated deny-licenses); include AGPL-3.0 and AGPL-3.0-or-later when the project is AGPL — template: examples/ci.yml.template
Tests and quality
| Need |
Skill / tool |
| Unit / component tests |
Vitest (always) |
| Cross-route, map, or auth flows |
playwright-skill |
| Pure logic / utils only |
Vitest — skip Playwright |
| Explicit deps / dead exports |
knip.md · verify scripts package-json-scripts.md |
Quick decisions
flowchart TD
start[Tech decision] --> maps{Maps involved?}
maps -->|yes| reactMapGl[react-map-gl skill]
maps -->|no| data{Server or URL state?}
data -->|server| tanstackQuery[router-and-query.md]
data -->|URL TanStack| validateSearch[params-search-ui-routes.md]
data -->|URL Next/shared| nuqsSkill[nuqs skill]
data -->|client global| zustandSkill[zustand-state-management]
maps -->|slow geo compute| rustWasm[rust-wasm-geo skill]
1---2name: tech-stack3description: Default FMC tech stack for geo-heavy React SPAs: Bun/Vite, React 19, TanStack, maps, styling, TypeScript editor/CLI alignment, tsconfig templates, browserslist client targets, Cursor MCP (Postgres, agent-browser), and when to pick sibling skills. Use when scaffolding a new app, evaluating libraries, setting up Bun install/global store or Knip, changing supported browsers or compat lint, setting up Cursor MCP, or making stack/architecture decisions on existing apps.4---56# FMC tech stack78Default stack for **geo-heavy React SPAs** at FixMyBerlin / FMC. Use for tech decisions when **creating** or **changing** an app — not as a deep dive into any one library.910Install sibling skills separately: `bunx skills add FixMyBerlin/fixmyskills --skill <name> -a cursor -y`1112## When to apply1314- Greenfield app scaffold (dependencies, tooling, folder layout)15- Adding or renaming `package.json` scripts16- Library or pattern choice (“should we use X?”)17- Cross-cutting convention review on an existing app18- Cursor MCP setup for Postgres schema inspection, agent DB queries, or browser exploration MCP1920## LLM resources2122Load [references/llm-resources.md](references/llm-resources.md) **only for the area the task touches**.2324**Maps:** load skill `react-map-gl` (and upstream [React Map GL docs](https://visgl.github.io/react-map-gl/) when the skill points there).2526## Specialized skills2728Prefer installed skill names when present; otherwise fetch from git.2930| Area | Skill | GitHub |31| ----------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |32| Rust/WASM geo | `rust-wasm-geo` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/rust-wasm-geo> |33| Maps (react-map-gl) | `react-map-gl` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/react-map-gl> |34| React TS patterns, useEffect discipline | `react-dev` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/react-dev> |35| TanStack Router (search, loaders, Query) | `tanstack-router-conventions` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/tanstack-router-conventions> |36| Router search params (UI routes) | — | <https://github.com/FixMyBerlin/fixmyskills/blob/main/skills/tanstack-router-conventions/references/params-search-ui-routes.md> |37| Router search serialization (pretty URLs) | — | <https://github.com/FixMyBerlin/fixmyskills/blob/main/skills/tanstack-router-conventions/references/router-search-serialization.md> |38| Router + Query loaders | — | <https://github.com/FixMyBerlin/fixmyskills/blob/main/skills/tanstack-router-conventions/references/router-and-query.md> |39| Devtools debug panel | — | <https://github.com/FixMyBerlin/fixmyskills/blob/main/skills/tanstack-router-conventions/references/devtools.md> |40| TanStack Start (boundaries, SSR, server) | `tanstack-start-conventions` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/tanstack-start-conventions> |41| App folder layout (Start) | — | <https://github.com/FixMyBerlin/fixmyskills/blob/main/skills/tanstack-start-conventions/references/app-structure.md> |42| URL state (TanStack Router apps) | `tanstack-router-conventions` | `params-search-ui-routes.md` |43| URL state (Next.js / legacy) | `nuqs` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/nuqs> |44| Client global state | `zustand-state-management` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/zustand-state-management> |45| Prisma schema naming, migrations | `prisma` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/prisma> |46| E2E / Playwright | `playwright-skill` | <https://github.com/FixMyBerlin/fixmyskills/tree/main/skills/playwright-skill> |4748## Runtime and build4950- **Runtime / package manager:** [Bun](https://bun.sh) — install policy [bun-install.md](references/bun-install.md) (≥ 1.3.14, global store, `bun --bun` for Vite, `.nvmrc` for Prisma/Playwright)51- **Build:** latest Vite (8+)52- **Lint / format:** oxlint and oxfmt with fix flags; Prettier-compatible defaults:53 - class sorting, import sorting, `package.json` sorting54 - `printWidth` 100, semicolons `asNeeded`, single quotes55 - `'typescript/switch-exhaustiveness-check': 'error'`56 - React Compiler: oxlint `react` plugin + `'react/unsupported-syntax': 'error'`; Vite via `viteReact({ compiler: true })` + `oxc-transform-react` (not Babel)57 - Templates: [examples/oxfmt.config.mjs](examples/oxfmt.config.mjs), [examples/oxlint.config.mjs](examples/oxlint.config.mjs)58 - Setup and per-project tuning: [references/oxc-config.md](references/oxc-config.md)59- **Client browser target:** `browserslist` in `package.json` drives Vite client `build.target` and `eslint-plugin-compat` in oxlint — [references/browser-target.md](references/browser-target.md)6061## React and TypeScript6263- **UI:** React 1964- **TypeScript (default):** `typescript@^7` (native port; [7.0 release](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/)). Typecheck with `tsc --noEmit` via `bun run type-check`. The workspace `typescript` package provides both CLI (`tsc`) and editor language service — do not add a separate compiler binary for type-check. Nightlies: `typescript@next` (not the retired `@typescript/native-preview` package).65- **Editor/CLI alignment:** `js/ts.tsdk.path` must resolve to the same `devDependencies.typescript` install as `tsc` (package root, not `lib/`). Misalignment makes IDE diagnostics disagree with `bun run type-check`.66- **Scaffold (TS 7):** `package.json` — `devDependencies.typescript` `^7`, script `"type-check": "tsc --noEmit"` (add `-p` when using split tsconfigs); pin `@typescript/typescript-darwin-arm64` / `@typescript/typescript-linux-x64` in `optionalDependencies` at the same version when CI runs on a different platform than dev (the `typescript` package lists these as optional — extend for other platforms as needed). `bunfig.toml` — [bun-install.md](references/bun-install.md). Commit `.vscode/settings.json` (template: [examples/vscode.settings.typescript.json.template](examples/vscode.settings.typescript.json.template)) and recommend the **TypeScript 7** extension in `.vscode/extensions.json` ([examples/vscode.extensions.json.template](examples/vscode.extensions.json.template)).67- **Monorepos:** when TypeScript lives in a package subfolder, prefix `js/ts.tsdk.path` to that package’s `node_modules/typescript` (e.g. `app/node_modules/typescript` — [tilda-geo](https://github.com/FixMyBerlin/tilda-geo/blob/develop/.vscode/settings.json)).68- **Cursor / VS Code:** install **TypeScript 7** (`TypeScriptTeam.native-preview` — marketplace ID unchanged). Enable via command palette **TypeScript: Enable TypeScript 7** or the settings template. Copy [examples/vscode.settings.typescript.json.template](examples/vscode.settings.typescript.json.template) into `.vscode/settings.json` (merge with oxc keys from [references/oxc-config.md](references/oxc-config.md)). Toggle back with **Disable TypeScript 7 Language Server** when editor plugins still require TS 6 (Vue, Astro, Svelte, Angular templates — see release notes).69- **Cursor MCP (user-level):** `~/.cursor/mcp.json` only — [examples/mcp.json.template](examples/mcp.json.template). Postgres: [references/cursor-mcp.md](references/cursor-mcp.md). Browser exploration: [references/agent-browser-mcp.md](references/agent-browser-mcp.md). Never commit MCP config to a repo.70- **Cursor rules (app-level):** copy [examples/package-json-scripts.md.template](examples/package-json-scripts.md.template) → `.cursor/rules/package-json-scripts.md` on scaffold — [references/package-json-scripts.md](references/package-json-scripts.md)7172**Verify:** `node -e "console.log(require('typescript/package.json').version)"`, `tsc --version`, and `bun run type-check` — all 7.x. In the editor, confirm **TypeScript 7** is enabled (status bar / command palette). Do not add `typescript` to repos that have no TS source (e.g. this skills monorepo).7374- **React Compiler:** on by default — see skill `react-dev` for memoization and typing conventions75- **GeoJSON:** `@types/geojson` for all GeoJSON payloads76- **Dates / times:** `@date-fns/tz`77- **Validation:** Zod 47879### tsconfig templates8081Use **two profiles** — do not merge app and scripts into one config.8283| Profile | Template | Profile analogue |84| ------------------------- | ---------------------------------------------------------------- | --------------------------------- |85| App (DOM, TanStack Start) | [examples/tsconfig.app.json](examples/tsconfig.app.json) | total-typescript `bundler/dom` |86| Scripts (CLI, no DOM) | [examples/tsconfig.scripts.json](examples/tsconfig.scripts.json) | total-typescript `bundler/no-dom` |8788Copy and adapt on scaffold. Adjust `paths` to project layout (`./src/*` vs `./*`). Add `allowJs: true` only when the repo still has `.js` files.8990**TanStack Start (app only):** `verbatimModuleSyntax: false` — type-only imports can become empty runtime imports and leak server code into client bundles. See header comments in `tsconfig.app.json`.9192**Never** install or `extends` `@total-typescript/tsconfig` — keep settings inlined with deviation comments.9394**Typecheck:**9596```bash97tsc --noEmit -p tsconfig.app.json && tsc --noEmit -p tsconfig.scripts.json98```99100Single-config repos (e.g. one root `tsconfig.json` covering app + scripts) are acceptable; greenfield TanStack Start apps should prefer split configs.101102Optional root `tsconfig.json` with `"references"` to both child configs.103104**ES2025:** `target` and `lib` stay in sync. `lib` adds typings only; new ES2025 **runtime** APIs in client bundles are not auto-polyfilled — use deliberately on the client; fine on server/Bun/scripts. Which browsers get that client bundle: [references/browser-target.md](references/browser-target.md).105106Component typing, Compiler, oxlint React rules, and useEffect discipline: skill `react-dev`. Map camera, clicks, and layers: skill `react-map-gl` (not `useEffect` + `map.on()`).107108## Data and state109110| Need | Choice |111| ----------------------------------- | ----------------------------------------------------------------------------------------------- |112| Server / async data | TanStack Query — no raw `useEffect` fetch; Suspense where the router supports it |113| Forms (non-trivial) | TanStack Form |114| Routing | TanStack Router / Start |115| Shareable URL state (TanStack apps) | Route `validateSearch` (Zod) — see `tanstack-router-conventions` → `params-search-ui-routes.md` |116| Shareable URL state (Next.js) | nuqs — see skill `nuqs` |117| High-frequency URL updates | `@tanstack/react-pacer` — see <https://tanstack.com/pacer/latest> |118| Global client state | Zustand (tkdodo patterns) — skill `zustand-state-management` |119| Local UI state | `useState` |120| Prisma schema / migrations | skill `prisma` — naming, relation fields, migrate workflows |121122Loader + Query integration: [router-and-query.md](https://github.com/FixMyBerlin/fixmyskills/blob/main/skills/tanstack-router-conventions/references/router-and-query.md).123124## Styling and UI125126- **CSS:** Tailwind CSS, `@tailwindcss/forms`, `@tailwindcss/typography`127- **Class merging:** `tailwind-merge` with `twJoin`, `twMerge`128- **Fonts:** Fontsource imports129- **Accessibility:** semantic HTML; keyboard-accessible map controls; do not rely on color alone130131## Maps and geo (JS)132133Stack choices only — map component patterns, layers, events, URL viewport: skill `react-map-gl`.134135| Choice | Default |136| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |137| Library | `react-map-gl/maplibre` (not raw MapLibre in components) |138| Basemap | [OpenFreeMap](https://openfreemap.org/); Maptiler if required |139| Coordinates | WGS84, `[lng, lat]` at API boundaries; validate with Zod |140| Turf | `@turf/*` per-function imports — not `import * as turf` |141| Feature editing | [tilda-geo drawing](https://github.com/FixMyBerlin/tilda-geo/tree/develop/app/src/components/regionen/pageRegionSlug/Map/Calculator/drawing), [maplibre-gl-terradraw](https://github.com/watergis/maplibre-gl-terradraw) |142143## Rust / WASM geo144145Turf vs WASM, crates, Vite wiring: skill `rust-wasm-geo`.146147## Dependency updates (Dependabot)148149- Schedule by project type — **AskQuestion** if unclear. FixMyCity: weekly Monday. Private OSS and Astro: first Friday of the month. Same template. Details: [dependabot.md](references/dependabot.md).150- **One open PR at a time** per ecosystem (`open-pull-requests-limit: 1`).151- Template: [examples/dependabot.yml.template](examples/dependabot.yml.template)152- **Reviewing and merging PRs:** skill `review-dependabot` (changelog triage, risk tiers, rebase merge)153154## CI (GitHub Actions)155156- PR dependency review: permissive `allow-licenses` (not deprecated `deny-licenses`); include `AGPL-3.0` and `AGPL-3.0-or-later` when the project is AGPL — template: [examples/ci.yml.template](examples/ci.yml.template)157158## Tests and quality159160| Need | Skill / tool |161| ------------------------------- | ------------------------------------------------------------------------------------------------------------ |162| Unit / component tests | Vitest (always) |163| Cross-route, map, or auth flows | `playwright-skill` |164| Pure logic / utils only | Vitest — skip Playwright |165| Explicit deps / dead exports | [knip.md](references/knip.md) · verify scripts [package-json-scripts.md](references/package-json-scripts.md) |166167## Quick decisions168169```mermaid170flowchart TD171 start[Tech decision] --> maps{Maps involved?}172 maps -->|yes| reactMapGl[react-map-gl skill]173 maps -->|no| data{Server or URL state?}174 data -->|server| tanstackQuery[router-and-query.md]175 data -->|URL TanStack| validateSearch[params-search-ui-routes.md]176 data -->|URL Next/shared| nuqsSkill[nuqs skill]177 data -->|client global| zustandSkill[zustand-state-management]178 maps -->|slow geo compute| rustWasm[rust-wasm-geo skill]179```