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.jsonscripts - 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 --bunfor Vite,.nvmrcfor Prisma/Playwright; Docker image install and Netlify function packaging) - Build: latest Vite (8+)
- Lint / format: oxlint and oxfmt with fix flags; Prettier-compatible defaults:
- class sorting, import sorting,
package.jsonsorting printWidth100, semicolonsasNeeded, single quotes'typescript/switch-exhaustiveness-check': 'error'- React Compiler: oxlint
reactplugin +'react/unsupported-syntax': 'error'; Vite viaviteReact({ 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
- class sorting, import sorting,
- Client browser target:
browserslistinpackage.jsondrives Vite clientbuild.targetandeslint-plugin-compatin oxlint — references/browser-target.md
React and TypeScript
- UI: React 19
- TypeScript (default):
typescript@^7(native port; 7.0 release). Typecheck withtsc --noEmitviabun run type-check. The workspacetypescriptpackage 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-previewpackage). - Editor/CLI alignment:
js/ts.tsdk.pathmust resolve to the samedevDependencies.typescriptinstall astsc(package root, notlib/). Misalignment makes IDE diagnostics disagree withbun run type-check. - Scaffold (TS 7):
package.json—devDependencies.typescript^7, script"type-check": "tsc --noEmit"(add-pwhen using split tsconfigs); pin@typescript/typescript-darwin-arm64/@typescript/typescript-linux-x64inoptionalDependenciesat the same version when CI runs on a different platform than dev (thetypescriptpackage 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.pathto that package’snode_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.jsononly — 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.mdon 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-devfor memoization and typing conventions - GeoJSON:
@types/geojsonfor 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 |
Required type packages whenever compilerOptions.types includes bun-types / web — these names are npm packages, not built-in. Install as direct devDependencies (Knip and globalStore will not treat a transitive dep as yours — knip.md / bun-install.md):
@types/bunpinned to the Bun floor (1.3.14)bun-typesas a direct dep (^that floor) —typeslooks up the package namebun-types;@types/bunalone is not enough@types/webif the config lists"web"— settingtypesturns off automatic@types/*inclusion, so DOM globals disappear unlesswebis installed and listed
Scripts profile (types: ["bun-types"]) still needs both @types/bun and bun-types even with no DOM. Bun scripts should use Glob / Bun.file(); that is only type-safe after these deps — otherwise agents fall back to node:fs.
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.
Astro / single-config: keep extends: astro/tsconfigs/strict. Set types to ["bun-types", "web"]. Do not add vite/client (Astro client types come from .astro/types.d.ts). Scripts in the same tsconfig still need Bun types (same packages as above).
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-mergewithtwJoin,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 | @osm-editor-kit/react-map-gl-draw (controlled value / onChange, styles as layer styles); example: tilda-geo drawing. Used before, still a possible alternative: TerraDraw with 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 deprecateddeny-licenses); includeAGPL-3.0andAGPL-3.0-or-laterwhen 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]