You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
What the projects use, so a new project or a new part starts with the same choices. Pick from the
"Preferred" column; take something else only for a reason, and write the reason into the
project's CLAUDE.md.
Frontend
Preferred
Also
Notes
Framework
React
Solid (small apps, browser extensions), Svelte
React because of the TanStack libraries
Full-stack / SSR
TanStack Start
server functions instead of a separate API where the app owns its backend
Routing
TanStack Router
@solidjs/router
file-based routes, typed links
Server state
TanStack Query
caching, refetch, optimistic updates
Tables, long lists
TanStack Table, TanStack Virtual
Forms
plain controlled inputs; TanStack Form when a form grows
validation with Zod
Components
shadcn/ui style: own components in the repo, built on Radix (React) or Kobalte (Solid), variants with class-variance-authority
no component library as a dependency
CSS
Tailwind CSS 4
design tokens as CSS variables, light and dark
Icons
lucide (lucide-react, lucide-solid)
existing own icon sets (quadeck's Glyph) may stay
Charts
uPlot (22 KB gzip, fast with many points, any framework) with one small wrapper per framework that reads the colours from the theme's CSS variables
own SVG for sparklines and charts with a few dozen points
no big chart libraries (Recharts 3 adds about 104 KB gzip, Chart.js about 55 KB); existing own charts (ccu-addon-mui, quadeck) stay while they are enough
Localization
Paraglide JS (inlang)
messages in messages/{locale}.json, compiled, typed; browser extensions use _locales/ instead (see below)
PWA
when the app is used on a phone or tablet
installable, offline shell
Backend
Preferred
Also
Notes
Language
TypeScript when performance is no concern: one language front and back, shared types
Rust for performance, low latency, system and binary work; Go for small static binaries on devices (CCU)
Runtime (TypeScript)
Bun (one binary with bun build --compile)
Node and Deno only where a project still has them (see the end), not for new code
HTTP (TypeScript)
TanStack Start server routes and functions
Hono for a standalone API
HTTP (Rust)
axum on tokio
tracing for logs, thiserror in libraries, anyhow in binaries, serde, reqwest
Validation
Zod
at every boundary: requests, config, files
Database
SQLite for a single server; PostgreSQL when several users or services share it
Supabase for hosted customer projects
ORM / SQL
Drizzle (TypeScript), sqlx (Rust)
migrations checked in and embedded in the binary
Auth
own small login for one admin (argon2id, session cookie, CSRF), passkeys with SimpleWebAuthn; Better Auth for multi-user apps
Desktop apps
Preferred
Notes
Framework
Tauri 2: a Rust core with the web UI of the frontend stack above (React, TanStack, Tailwind) in the system's web view
as in fernsicht (React) and prefixr (Svelte); a few MB instead of Electron's ~100 MB; on Linux it needs WebKitGTK. A tool without a window (like haul-cnl) is a plain Rust binary
Structure
the desktop crate outside the main Cargo workspace, built and tested by its own CI job
keeps WebKitGTK out of the other builds (fernsicht)
Commands
small #[tauri::command] functions at the edge, the logic in pure Rust functions with unit tests
typed TypeScript bindings with tauri-specta once it is stable (2.x is still a release candidate)
Security
capabilities with only the permissions each window needs, a strict CSP in tauri.conf.json
no remote content in the app window
Plugins
the official ones as needed: updater, single-instance, window-state, store, dialog, notification, autostart, deep-link, log
Helper programs
sidecars (bundle.externalBin)
fernsicht ships its client and host agent this way
Packages
Linux: AppImage, plus .deb/.rpm or Flatpak where users expect them; Windows: NSIS installer; macOS: .dmg
macOS needs an Apple Developer account and notarization, Windows a code signing certificate; without them the systems warn on first start
Updates
tauri-plugin-updater with its own signing key (required), latest.json in the GitHub release
Release
tauri-action builds every platform and uploads the bundles and latest.json to the release
one version in Cargo.toml, tauri.conf.json and package.json, raised together by the bump workflow
Tests
Playwright, as everywhere: the UI from the Vite dev server in a browser, the Rust side mocked with mockIPC from @tauri-apps/api/mocks; this also gives the screenshots. Vitest for stores and logic (prefixr mocks @tauri-apps/api with vi.mock), Rust unit and integration tests for the core
Playwright cannot drive the real app window, since Tauri uses the system's web view (only WebView2 on Windows accepts connectOverCDP). Only when the finished app itself must be tested (window, menus, file dialogs): WebdriverIO's Tauri service; the official tauri-driver covers Linux and Windows only
Browser extensions
As chrome.sendtokodi and oneclickhistorycleaner do it:
Preferred
Notes
Build
Manifest V3, Solid with Vite and @crxjs/vite-plugin, Tailwind, Biome
Browsers
Chrome, Edge and Firefox from one code base; Firefox through its own build command (build:firefox)
Texts
_locales/{de,en}/messages.json with chrome.i18n
the stores take the extension's name and description from there too
Permissions
only what is needed; optional host permissions asked for when used; no tracking, a PRIVACY.md
Tests
Vitest for the logic; Playwright loads the popup and the options page as plain pages with a stand-in for the extension API (chrome-stub.ts), which also takes the screenshots
Store material
store-assets/ (icon, banners, screenshots, texts), outside public/ so it is not packaged
Release
the bump workflow and browser-extension-stores.yml: Chrome as a draft, Firefox with the source archive, Edge
TypeScript, bundled with Vite into one file; Biome; tsc --noEmit
Tests
Vitest for the logic, @vscode/test-electron for tests inside the editor
CI on Linux, Windows and macOS
Release
bump.yml (the shared bump workflow) tags vX.Y.Z; release.yml on the tag runs the CI, which also builds the .vsix, then github-release.yml with the .vsix and vscode-extension-publish.yml to the Visual Studio Marketplace and Open VSX
the extension is bundled into one file, so vsce package --no-dependencies (it would otherwise call npm list), built in the package script because vsce runs vscode:prepublish through npm; the tests inside the editor run in VS Code's own Electron, not in Bun. Exception: vscode-jest-runner stays on npm and Node, because its tests run Jest on Node anyway — Bun only pays off where Node goes away
Tooling
Preferred
Notes
Format and lint (TS/JS)
Biome
replaces ESLint and Prettier
Format and lint (Rust)
cargo fmt, cargo clippy -D warnings
Format and lint (Go)
gofmt, go vet
Type checking
TypeScript strict
tsc --noEmit in CI
Package manager
Bun, also for workspaces (workspaces in package.json, bun --filter)
bun.lock checked in, bun install --frozen-lockfile in CI; the Bun version in packageManager (bun@1.x.y)
Scripts and tools
Bun runs TypeScript directly: bun scripts/x.ts, bunx instead of npx
no ts-node, tsx, nodemon (bun --watch) or dotenv (Bun reads .env); several dev processes side by side (cargo and Vite) with bun run --parallel dev:server dev:web, preparation steps chained before it with &&; no own process supervisor (no detached, signal handlers or process groups): the terminal sends Ctrl+C, Ctrl+Z and hang-up to the whole process group by itself
CI and images
oven-sh/setup-bun with bun-version-file: package.json; build stages on oven/bun
no setup-node or pnpm/action-setup; in the Playwright image, which has Node but no unzip and no jq, npm install --global "bun@$(node -p "require('./package.json').packageManager.split('@')[1]")" instead of setup-bun
Build
Vite
Bun.build for small scripts and plugins instead of esbuild
Unit tests
Vitest; cargo test; go test
E2E and screenshots
Playwright in the official Playwright image, its runner on Bun too: bun --bun playwright test (as haben does)
screenshots via the "Update screenshots" workflow; under Bun, Playwright skips its own TypeScript loader, so the folder with the specs needs "type": "module" in its package.json (else Bun reads them as CommonJS and stops at type syntax) — in the test folder's own package.json when the root can't take it (ccu-addon-mui); Node only where a project has shown that a spec does not load under Bun, with the reason in its CLAUDE.md
Formatter: ESLint and Prettier in prefixr, Prettier in fernsicht, none in haul, ccu-addon-mui and
gaiser-lager, Biome only as linter in haben → Biome (quadeck has switched already).
Tailwind: haben and haul use their own CSS with CSS variables → Tailwind when the UI is reworked
anyway.
Browser extensions: haul/extension is plain TypeScript with esbuild → Solid, Vite and
@crxjs/vite-plugin like the others; the Firefox build variable is TARGET_PLATFORM=firefox in
chrome.sendtokodi and BROWSER=firefox in oneclickhistorycleaner → one name.
VS Code extensions: vscode-distrobox-reveal and vscode-speech-language-switch compile with tsc
only → bundle with Vite; vscode-jest-runner still has a .prettierrc next to Biome → remove;
vscode-distrobox-reveal has no formatter → Biome.
Charts: Chart.js in snapraid-ui → uPlot when the project is touched anyway.
Desktop releases: fernsicht builds only the AppImage with its own script; a reusable Tauri release
workflow (tauri-action, all platforms, updater) does not exist here yet.
Package manager and runtime → Bun:
pnpm workspaces in windfenster, haul, stellwerk, spielstand, fernsicht, keybund and
pd200x-control (here Node only builds the frontend, so CI and the node: build stage of the
Dockerfile become Bun); haul's esbuild for plugins and extension → Bun.build; fernsicht's
CI still uses Node 22.
npm in ccu-addon-mui, reactive-volcano-app, kachelwerk, Flugwetterdaten,
Gong-Second-Hand-Dashboard, the Astro sites (astro-cv, teufel-it-astro, ki-agent.ai;
withastro/action picks Bun from bun.lock), the browser extensions and the VS Code
extensions.
snom-xml runs with npx ts-node and dotenv → bun src/server.ts; check the Windows service
of node-windows first.
Node as runtime in gaiser-lager → Bun when the project is touched anyway (snapraid-ui moved
from Deno to Bun; Deno's --allow-* flags were unscoped there and protected nothing).
Node stays where a tool needs it: prefixr generates a package-lock.json from bun.lock for
flatpak-node-generator; vscode-jest-runner tests with Jest, because it is a Jest runner.