- TypeScript 93.4%
- CSS 3.4%
- Shell 2.7%
- JavaScript 0.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| design | ||
| docs | ||
| patches | ||
| public | ||
| scripts | ||
| server | ||
| spikes/001-univer-sheet | ||
| src | ||
| tests | ||
| .gitignore | ||
| .oxlintrc.json | ||
| drizzle.config.pg.ts | ||
| drizzle.config.sqlite.ts | ||
| index.html | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| ROADMAP.md | ||
| tsconfig.app.json | ||
| tsconfig.frontend-tests.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| tsconfig.server.json | ||
| vite.config.ts | ||
| vitest.config.ts | ||
React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
Currently, two official plugins are available:
- @vitejs/plugin-react uses Oxc
- @vitejs/plugin-react-swc uses SWC
React Compiler
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see this documentation.
Expanding the Oxlint configuration
If you are developing a production application, we recommend enabling type-aware lint rules by installing oxlint-tsgolint and editing .oxlintrc.json:
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"options": {
"typeAware": true
},
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
See the Oxlint rules documentation for the full list of rules and categories.
Plusieurs stacks en parallèle (script dev-worktree.sh)
scripts/dev-worktree.sh gère une stack par cible : master (ce checkout, backend 3002 + vite 5173) ou <sujet> (.worktrees/<sujet>, branche feat/<sujet>, ports suivants).
scripts/dev-worktree.sh setup <sujet> # branche + worktree + deps + db
scripts/dev-worktree.sh start [cible] # backend + vite sur les ports de la cible
scripts/dev-worktree.sh stop [cible] # arrête ce que le script a lancé
scripts/dev-worktree.sh adopt [cible] # reprend une stack lancée à la main
scripts/dev-worktree.sh status # toutes les cibles + ports sans state
scripts/dev-worktree.sh doctor # ENCRYPTION_KEY ↔ dev.db, dérives, orphelins
scripts/dev-worktree.sh prune [--yes] # worktrees mergés + states périmés
La clé ENCRYPTION_KEY de chaque cible est générée une fois dans .dev-env (gitignoré) et réutilisée : ne pas la régénérer, sinon la clé API OpenRouter enregistrée dans dev.db devient indéchiffrable (doctor le détecte). stop ne tue que les process qu'il connaît ; start n'écrase jamais une stack en cours — il refuse et propose adopt.
Conversion de documents (scripts/convert-worker.py, qui importe markitdown) : start exporte ARDOISE_CONVERT_PYTHON vers l'interpréteur qui a le module — valeur déjà exportée, sinon .venv-convert/ de la cible, sinon celui du checkout principal (un worktree n'a pas de venv propre). preflight affiche l'interpréteur retenu. Créer le venv une fois dans le checkout principal : python3 -m venv .venv-convert && .venv-convert/bin/pip install 'markitdown[docx,xlsx,xls,pptx,pdf]==0.1.7'.
Dépendances patchées
Ce projet patche localement une dépendance (@atomic-editor/editor) via patch-package — voir patches/ et docs/superpowers/specs/2026-09-13-tableaux-wikilinks-sync-design.md. Le patch se réapplique automatiquement à chaque npm install/npm ci (hook postinstall) ; ne lancez jamais l'installation avec --ignore-scripts.
Backend server
The backend is an Express + Drizzle server (server/) providing vaults and files with stable ids, backed by SQLite (dev) or Postgres (prod).
Environment variables
| Variable | Default | Notes |
|---|---|---|
DATABASE_URL |
./dev.db |
A SQLite file path, or a postgres:///postgresql:// URL to use Postgres instead. |
VAULTS_ROOT |
./vaults |
Directory where vault file trees are stored on disk. |
HOST |
127.0.0.1 |
Interface the server binds to. See the security note below before changing this. |
PORT |
3001 |
Port the server listens on. |
ALLOW_DEV_AUTH |
off | Set to 1 to allow the x-dev-user header to stand in for a real identity header. Never enable in production. |
JWT_SECRET |
(required, no default) | Signing secret for the short-lived WebSocket authentication tokens (Hocuspocus). Generate with openssl rand -hex 32. The server refuses to start without it. |
ENCRYPTION_KEY |
(required, no default) | 32-byte hex secret (openssl rand -hex 32) used to encrypt each user's OpenRouter API key at rest (AES-256-GCM). The server refuses to start without it. |
First boot
Before starting the server for the first time (or after a schema change), push the schema to your database:
npm run db:push:sqlite # dev, uses DATABASE_URL or ./dev.db
npm run db:push:pg # prod, requires DATABASE_URL pointing at Postgres
npx tsx scripts/history-cleanup.ts # dry-run: scan for orphaned history entries (omit --apply to preview)
npx tsx scripts/history-cleanup.ts --apply # apply: delete orphaned entries (after reviewing dry-run output)
Then start the server:
npm run dev:server # local development, restarts on file changes
npm start # plain run, no file-watching
⚠️ Security note
This server trusts the remote-user HTTP header as the authenticated user's identity, with no further verification. It is only safe to run behind a reverse proxy (YunoHost's SSOwat in production) that is configured to unconditionally overwrite any client-supplied remote-user header on every request — never merely add to it — before forwarding to this server. If a client can set remote-user itself, it can impersonate any user.
The server must never be reachable except through that proxy. HOST defaults to 127.0.0.1 specifically to enforce this — do not change it to 0.0.0.0 or any other externally-reachable interface without the reverse-proxy guarantee described above already in place.
Les imports de fichiers (images, PDF) sont limités à 25 Mo côté serveur. Si l'application tourne derrière un reverse proxy (nginx/YunoHost), son client_max_body_size doit être réglé à au moins 25 Mo, sinon le proxy rejette la requête avant qu'Express ne la voie.