docs(concept): passer les 59 fiches de doctrine en anglais
Convention du projet pour la documentation projet. Traduction fidèle, sans changement de fond : mêmes fiches, mêmes sections, mêmes liens. Le lint est identique à la baseline (59 leaves, 0 nouveau lien cassé, wikilinks bit-à-bit inchangés) et aucun `.feature` n'a été touché. Le `summary:` du frontmatter est traduit lui aussi — c'est ce que le hook affiche dans l'index, il porte autant que le corps. RESTENT EN FRANÇAIS, délibérément : - les fichiers .feature (convention explicite du projet : Etant donné/Quand/Alors) et le bloc Gherkin cité dans brief_2026-07-06 ; - les libellés d'interface cités en prose (« Entrer », « ✓ Je participe », « Voir tous les participants », « participant inconnu »…) : ce sont des chaînes réelles de l'app, pas de la prose ; - les noms de scénarios BDD ; - les `triggers.keywords` des _overview : jetons de matching du hook, et la conversation reste en français — les traduire aurait cassé la livraison. EFFET SECONDAIRE UTILE : relire intégralement a fait remonter des contradictions et des péremptions que personne ne voyait section par section. Notées, non corrigées (hors périmètre de la traduction) : - rule_document-per-entity décrit la lecture via readEntities/readUnion/ registerDoc/bumpRead, que rule_app-uses-sdk-surface-only déclare SUPPRIMÉS au profit de watchShape/useShapeQuery. Une règle qui décrit des APIs retirées est activement trompeuse — à traiter en priorité. - brief_2026-07-06 §P3 réaffirme « prouvé par l'e2e D.2, sans reload » juste après l'encadré qui déclare cette phrase fausse et sur-cadrée. - knowledge_data-modes cite useShapeWithDefaults() là où useShapeQuery est documenté ailleurs. - knowledge_stack-and-commands : build:orm pointe ./src/shapes/* alors que les shapes vivent sous src/shared/shapes/. - knowledge_screens / knowledge_routing : ConnectScreen décrit comme absent du registre mais présent en route. - brief_2026-05-18 : « identifié si connecté » était ambigu en français (session ouverte vs est une connexion) ; l'anglais a forcé à trancher — rendu par « if a connection », à confirmer côté produit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
type: _overview
|
||||
summary: Stack et outillage — Bun-first (runtime, bundler, APIs natives), build pipeline, et commandes du projet
|
||||
summary: Stack and tooling — Bun-first (runtime, bundler, native APIs), build pipeline, and the project's commands
|
||||
triggers:
|
||||
keywords: [bun, bunx, build, bundler, vite, webpack, jest, npm, storybook, "bun.serve", hmr, tailwind, package.json]
|
||||
paths: ["build.ts", "package.json", "bunfig.toml", "tsconfig.json", "src/index.ts", "src/index.html", ".storybook/**", "scripts/**"]
|
||||
@@ -8,14 +8,14 @@ triggers:
|
||||
|
||||
# Tech stack
|
||||
|
||||
Stack et outillage du projet. Principe directeur : **Bun-first** — Bun remplace Node/npm/vite/webpack/jest et fournit les APIs serveur natives.
|
||||
The project's stack and tooling. Guiding principle: **Bun-first** — Bun replaces Node/npm/vite/webpack/jest and provides the native server APIs.
|
||||
|
||||
**À lire en premier :** [[rule_bun-first]] — la convention qui décide quel outil utiliser.
|
||||
**Read this first:** [[rule_bun-first]] — the convention that decides which tool to use.
|
||||
|
||||
## Liens
|
||||
## Links
|
||||
|
||||
- [[rule_bun-first]] — utiliser Bun, pas Node/npm/vite/jest/express/ws/pg…
|
||||
- [[knowledge_bun-apis]] — APIs natives Bun (serve, sqlite, redis, sql, file, shell)
|
||||
- [[knowledge_build-pipeline]] — build.ts, bundler, serveur, harness buildé à part, Storybook
|
||||
- [[knowledge_stack-and-commands]] — composants de la stack + scripts réels (+ quirks)
|
||||
- [[knowledge_deployment]] — Dockerfile, prod depuis src/, pas de CI, `portless` en dev
|
||||
- [[rule_bun-first]] — use Bun, not Node/npm/vite/jest/express/ws/pg…
|
||||
- [[knowledge_bun-apis]] — native Bun APIs (serve, sqlite, redis, sql, file, shell)
|
||||
- [[knowledge_build-pipeline]] — build.ts, bundler, server, harness built separately, Storybook
|
||||
- [[knowledge_stack-and-commands]] — stack components + the real scripts (plus quirks)
|
||||
- [[knowledge_deployment]] — Dockerfile, prod served from src/, no CI, `portless` in dev
|
||||
|
||||
@@ -1,41 +1,26 @@
|
||||
---
|
||||
type: caveat
|
||||
summary: Firefox 151+ bloque (Local Network Access) le broker hébergé qui embarque l'app de dev locale dans son iframe → iframe blanche, zéro log app, aucune erreur. Ce n'est PAS un bug de code. Fix navigateur — about:config network.lna.enabled=false.
|
||||
summary: Firefox 151+ blocks (Local Network Access) the hosted broker embedding the local dev app in its iframe → blank iframe, zero app logs, no error at all. This is NOT a code bug. Browser-side fix — about:config network.lna.enabled=false.
|
||||
last_checked: 2026-07-13
|
||||
---
|
||||
|
||||
# Firefox LNA bloque l'iframe app du broker en dev local
|
||||
# Firefox LNA blocks the broker's app iframe in local dev
|
||||
|
||||
## Symptôme
|
||||
## Symptom
|
||||
|
||||
En dev local, l'app tourne DANS l'iframe du broker hébergé (`nextgraph.eu`/`nextgraph.net`
|
||||
en HTTPS embarque `festipod.localhost` → `127.0.0.1`). Sur **Firefox 151+**, l'iframe reste
|
||||
**blanche** : **aucun log `[FestipodData]`/`[NG]`** (l'app JS n'est jamais exécutée), et
|
||||
**aucune erreur** rouge (le blocage est une décision de politique réseau, pas un throw). Facile
|
||||
à prendre pour un crash de rendu Festipod — ce n'en est PAS un.
|
||||
In local dev, the app runs INSIDE the hosted broker's iframe (`nextgraph.eu`/`nextgraph.net` over HTTPS embeds `festipod.localhost` → `127.0.0.1`). On **Firefox 151+**, the iframe stays **blank**: **no `[FestipodData]`/`[NG]` log at all** (the app's JS never runs), and **no red error** either (the block is a network policy decision, not a throw). Easy to mistake for a Festipod rendering crash — it is NOT one.
|
||||
|
||||
## Cause
|
||||
|
||||
**Local Network Access (LNA)** : Firefox 151+ (activé par défaut, cf. rollout 149→151) interdit
|
||||
à un **site public** (le broker HTTPS) d'atteindre une **ressource du réseau local**
|
||||
(`127.0.0.1`) — y compris l'embarquer en iframe. Le log révélateur (console) :
|
||||
**Local Network Access (LNA)**: Firefox 151+ (on by default, see the 149→151 rollout) forbids a **public site** (the HTTPS broker) from reaching a **local network resource** (`127.0.0.1`) — including embedding it in an iframe. The telltale console log:
|
||||
`Local Network Access detected: ... accessing target "…festipod.localhost…" (127.0.0.1) … prompt action: auto_deny`.
|
||||
|
||||
Deux corollaires qui trompent :
|
||||
- **Le top-level charge très bien** : ta navigation directe vers `https://festipod.localhost:1355`
|
||||
(la barrière AccessGateScreen) n'est PAS soumise à LNA. Seul l'**embarquement iframe** par le
|
||||
broker l'est. Donc « le cert est déjà accepté / l'app se lance » avant l'iframe ≠ l'iframe passera.
|
||||
- **HTTPS n'y change rien** : LNA vise l'**adresse locale cible**, pas le protocole. Passer
|
||||
`portless proxy start --https` (app en `https://festipod.localhost`) ne débloque pas.
|
||||
Two corollaries that mislead:
|
||||
- **The top level loads just fine**: navigating directly to `https://festipod.localhost:1355` (the AccessGateScreen barrier) is NOT subject to LNA. Only **iframe embedding** by the broker is. So "the cert is already accepted / the app starts up" before the iframe does not mean the iframe will go through.
|
||||
- **HTTPS changes nothing**: LNA targets the **local destination address**, not the protocol. Switching to `portless proxy start --https` (app on `https://festipod.localhost`) does not unblock it.
|
||||
|
||||
## Fix (navigateur, pas code)
|
||||
## Fix (browser, not code)
|
||||
|
||||
`about:config` → **`network.lna.enabled` = `false`** (drapeau maître : désactive tous les
|
||||
contrôles LNA). Alternative ciblée : **`network.lna.skip-domains`** avec `nextgraph.eu`,
|
||||
`nextgraph.net` (garde la protection ailleurs). Autres prefs LNA : `network.lna.blocking`,
|
||||
`network.lna.block_trackers`.
|
||||
`about:config` → **`network.lna.enabled` = `false`** (master flag: disables every LNA check). Targeted alternative: **`network.lna.skip-domains`** with `nextgraph.eu`, `nextgraph.net` (keeps the protection everywhere else). Other LNA prefs: `network.lna.blocking`, `network.lna.block_trackers`.
|
||||
|
||||
Ne PAS chasser un bug de rendu Festipod tant qu'il n'y a **aucun log `[FestipodData]`** dans la
|
||||
console : sans logs app, l'app n'a pas tourné → c'est l'environnement (LNA, cert non approuvé,
|
||||
serveur dev éteint), pas le code. Le smoke `@e2e` ne peut PAS attraper ça : Playwright n'applique
|
||||
pas LNA comme un vrai Firefox.
|
||||
Do NOT go hunting for a Festipod rendering bug as long as there is **no `[FestipodData]` log** in the console: with no app logs, the app never ran → it is the environment (LNA, untrusted cert, dev server down), not the code. The `@e2e` smoke test CANNOT catch this: Playwright does not enforce LNA the way a real Firefox does.
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Dev en bun --hot, build prod via build.ts (bundler Bun + plugin Tailwind) vers dist/, alias @/* → ./src/*
|
||||
summary: Dev runs on bun --hot, prod builds through build.ts (Bun bundler + Tailwind plugin) into dist/, path alias @/* → ./src/*
|
||||
---
|
||||
|
||||
# Build pipeline
|
||||
|
||||
- **Dev** : `bun --hot src/index.ts` (via `bun run dev`) — HMR, port 3000.
|
||||
- **Prod** : `bun run build` → `build.ts` (bundler Bun + plugin Tailwind) → `dist/`.
|
||||
- **Alias de chemin** : `@/* → ./src/*` (déclaré dans `tsconfig.json`).
|
||||
- **Dev**: `bun --hot src/index.ts` (through `bun run dev`) — HMR, port 3000.
|
||||
- **Prod**: `bun run build` → `build.ts` (Bun bundler + Tailwind plugin) → `dist/`.
|
||||
- **Path alias**: `@/* → ./src/*` (declared in `tsconfig.json`).
|
||||
|
||||
Le serveur sert `src/index.html`, qui charge `src/app/frontend.tsx` (voir `app-architecture` §app-shell). Le bundler transpile le TSX et bundle le CSS sans outil externe — pas de Vite/webpack/esbuild (cf. [[rule_bun-first]]).
|
||||
The server serves `src/index.html`, which loads `src/app/frontend.tsx` (see `app-architecture` §app-shell). The bundler transpiles the TSX and bundles the CSS without any external tool — no Vite/webpack/esbuild (see [[rule_bun-first]]).
|
||||
|
||||
## Détails de `build.ts` et du serveur
|
||||
## Details of `build.ts` and of the server
|
||||
|
||||
- `build.ts` scanne `src/**/*.html` comme entrypoints (aujourd'hui un seul : `src/index.html`), `target: 'browser'`, minify + sourcemap linked, plugin `bun-plugin-tailwind`. Ajouter un 2e `.html` créerait un 2e bundle.
|
||||
- `src/index.ts` (`Bun.serve`) sert : `/reports/cucumber` (rapport HTML), des stubs `/api/hello*`, `/festipod-config.json` + `/shared-wallet.ngw` (config runtime, voir ci-dessous), et un **catch-all `/*` → `src/index.html`** (routing SPA, doit rester en dernier). HMR si `NODE_ENV !== 'production'`, port via `PORT`.
|
||||
- `build.ts` scans `src/**/*.html` for entrypoints (today just one: `src/index.html`), `target: 'browser'`, minify plus linked sourcemap, `bun-plugin-tailwind` plugin. Adding a second `.html` would create a second bundle.
|
||||
- `src/index.ts` (`Bun.serve`) serves: `/reports/cucumber` (HTML report), `/api/hello*` stubs, `/festipod-config.json` plus `/shared-wallet.ngw` (runtime config, see below), and a **catch-all `/*` → `src/index.html`** (SPA routing, which must stay last). HMR when `NODE_ENV !== 'production'`, port from `PORT`.
|
||||
|
||||
## Globals de build vs config runtime (piège du wallet partagé)
|
||||
## Build-time globals vs runtime config (the shared wallet pitfall)
|
||||
|
||||
`build.ts` injecte des **globals à la compilation** via `define` (p. ex. `__FESTIPOD_SHARED_WALLET_PASSWORD__` depuis `FESTIPOD_SHARED_WALLET_PASSWORD`, `__FESTIPOD_ACCESS_GATE_DISABLED__`, et `__FESTIPOD_AUTO_SEED__` depuis `FESTIPOD_AUTO_SEED` — l'auto-seed de dev, OFF si absent). **Piège** : le serveur `src/index.ts` (utilisé par `bun run dev` ET `bun run start`) bundle `index.html` via l'import HTML de Bun, qui **n'applique aucun `define`** — ni `bun --define` ni `process.env` ne s'y propagent (vérifié). Donc une variable d'env passée à `bun run dev` n'atteint pas le bundle frontend par ce chemin.
|
||||
`build.ts` injects **compile-time globals** through `define` (e.g. `__FESTIPOD_SHARED_WALLET_PASSWORD__` from `FESTIPOD_SHARED_WALLET_PASSWORD`, `__FESTIPOD_ACCESS_GATE_DISABLED__`, and `__FESTIPOD_AUTO_SEED__` from `FESTIPOD_AUTO_SEED` — the dev auto-seed, OFF when absent). **Pitfall**: the `src/index.ts` server (used by `bun run dev` AND `bun run start`) bundles `index.html` through Bun's HTML import, which **applies no `define`** — neither `bun --define` nor `process.env` propagates there (verified). So an environment variable passed to `bun run dev` never reaches the frontend bundle along that path.
|
||||
|
||||
Pour ces chemins servis depuis `src/`, la config passe donc au **runtime** : `src/index.ts` expose `/festipod-config.json` (lu depuis l'env), et l'entrée `src/app/frontend.tsx` la **fetch d'abord**, pose le global, **puis importe l'app dynamiquement** (`await import('./App')`) — ainsi `sharedWallet.ts` lit la valeur à son évaluation. Dans un bundle `build.ts` la valeur est déjà inline par `define`, donc le fetch est court-circuité (`NODE_ENV === 'production'`). Conséquence pratique : pour exercer le flux « portefeuille partagé » en dev **de bout en bout** (téléchargement + import qui fonctionne), passer le VRAI mot de passe du wallet e2e **et** le fichier — le mot de passe affiché à l'écran doit correspondre au `.ngw` importé, sinon l'import échoue (une valeur factice comme `1` fait juste apparaître l'écran) :
|
||||
For those paths served from `src/`, the configuration therefore goes through the **runtime**: `src/index.ts` exposes `/festipod-config.json` (read from the environment), and the `src/app/frontend.tsx` entry **fetches it first**, sets the global, **then imports the app dynamically** (`await import('./App')`) — so that `sharedWallet.ts` reads the value when it is evaluated. In a `build.ts` bundle the value is already inlined by `define`, so the fetch is skipped (`NODE_ENV === 'production'`). Practical consequence: to exercise the "shared wallet" flow in dev **end to end** (download plus a working import), pass the REAL password of the e2e wallet **and** the file — the password shown on screen must match the imported `.ngw`, otherwise the import fails (a dummy value such as `1` merely makes the screen appear):
|
||||
|
||||
```
|
||||
FESTIPOD_SHARED_WALLET_PASSWORD=festipod-e2e-tests \
|
||||
@@ -28,10 +28,10 @@ FESTIPOD_SHARED_WALLET_FILE=./festipod-e2e-tests.ngw \
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Le harness de test est buildé à part
|
||||
## The test harness is built separately
|
||||
|
||||
⚠️ `build.ts` ne build **pas** les harness de test. Les hooks Cucumber (`src/shared/support/hooks.ts`) lancent un `bun build` **à la demande** pour `src/shared/test-harness/harness.tsx` (et `harness-ng.tsx`) → `dist/test-harness*.js`. C'est un entrypoint séparé du build app — voir concept `bdd-testing`.
|
||||
⚠️ `build.ts` does **not** build the test harnesses. The Cucumber hooks (`src/shared/support/hooks.ts`) run a `bun build` **on demand** for `src/shared/test-harness/harness.tsx` (and `harness-ng.tsx`) → `dist/test-harness*.js`. It is an entrypoint separate from the app build — see concept `bdd-testing`.
|
||||
|
||||
## Storybook
|
||||
|
||||
`storybook dev -p 6006` — **webpack5 + SWC** (pas Vite). Les décorateurs (`.storybook/`) injectent la pile complète de providers (Theme > NextGraph > FestipodData > Router) et importent `src/index.css` ; viewport mobile par défaut. Couplage dur au contexte projet (pas réutilisable hors Festipod).
|
||||
`storybook dev -p 6006` — **webpack5 + SWC** (not Vite). The decorators (`.storybook/`) inject the full provider stack (Theme > NextGraph > FestipodData > Router) and import `src/index.css`; mobile viewport by default. Tightly coupled to the project context (not reusable outside Festipod).
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: APIs natives Bun utilisées par le projet — Bun.serve (HTTP/WS/routes), HTML imports bundlés, bun:sqlite, Bun.redis, Bun.sql, Bun.file, Bun.$
|
||||
summary: Native Bun APIs used by the project — Bun.serve (HTTP/WS/routes), bundled HTML imports, bun:sqlite, Bun.redis, Bun.sql, Bun.file, Bun.$
|
||||
---
|
||||
|
||||
# APIs natives Bun
|
||||
# Native Bun APIs
|
||||
|
||||
Référence des APIs Bun à privilégier (cf. [[rule_bun-first]]). Doc complète : `node_modules/bun-types/docs/**.mdx`.
|
||||
Reference for the Bun APIs to favour (see [[rule_bun-first]]). Full documentation: `node_modules/bun-types/docs/**.mdx`.
|
||||
|
||||
## Serveur — `Bun.serve()`
|
||||
## Server — `Bun.serve()`
|
||||
|
||||
Supporte WebSockets, HTTPS et routes. Pas besoin d'`express`/`ws`.
|
||||
Supports WebSockets, HTTPS and routes. No need for `express`/`ws`.
|
||||
|
||||
```ts
|
||||
import index from "./index.html"
|
||||
@@ -23,19 +23,19 @@ Bun.serve({
|
||||
})
|
||||
```
|
||||
|
||||
C'est le mécanisme de `src/index.ts` (voir concept `app-architecture` §app-shell).
|
||||
This is the mechanism behind `src/index.ts` (see concept `app-architecture` §app-shell).
|
||||
|
||||
## HTML imports (frontend)
|
||||
|
||||
`Bun.serve()` sert des HTML imports ; le bundler Bun transpile/bundle automatiquement `.tsx`/`.jsx`/`.js` et le CSS (Tailwind inclus). Un `<script type="module" src="./frontend.tsx">` dans le HTML suffit — pas de Vite.
|
||||
`Bun.serve()` serves HTML imports; the Bun bundler automatically transpiles and bundles `.tsx`/`.jsx`/`.js` and the CSS (Tailwind included). A `<script type="module" src="./frontend.tsx">` in the HTML is enough — no Vite.
|
||||
|
||||
## Stockage & shell
|
||||
## Storage & shell
|
||||
|
||||
- **`bun:sqlite`** pour SQLite (pas `better-sqlite3`)
|
||||
- **`Bun.redis`** pour Redis (pas `ioredis`)
|
||||
- **`Bun.sql`** pour Postgres (pas `pg`/`postgres.js`)
|
||||
- **`WebSocket`** intégré (pas `ws`)
|
||||
- **`Bun.file`** plutôt que `node:fs` readFile/writeFile
|
||||
- **`Bun.$\`ls\`** plutôt qu'`execa`
|
||||
- **`bun:sqlite`** for SQLite (not `better-sqlite3`)
|
||||
- **`Bun.redis`** for Redis (not `ioredis`)
|
||||
- **`Bun.sql`** for Postgres (not `pg`/`postgres.js`)
|
||||
- **`WebSocket`** built in (not `ws`)
|
||||
- **`Bun.file`** rather than `node:fs` readFile/writeFile
|
||||
- **`Bun.$\`ls\`** rather than `execa`
|
||||
|
||||
Bun charge `.env` automatiquement → ne pas utiliser `dotenv`.
|
||||
Bun loads `.env` automatically → do not use `dotenv`.
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Déploiement — Dockerfile multi-stage Bun Alpine ; install via pnpm (git+node dans l'image) mais runtime bun ; lance `bun run start` depuis src/ (pas dist/), EXPOSE 3000, env PORT/NODE_ENV ; aucun CI/CD committé ; dev passe par le wrapper portless
|
||||
summary: Deployment — multi-stage Bun Alpine Dockerfile; install through pnpm (git+node inside the image) but bun at runtime; runs `bun run start` from src/ (not dist/), EXPOSE 3000, env PORT/NODE_ENV; no CI/CD committed; dev goes through the portless wrapper
|
||||
last_checked: 2026-07-14
|
||||
---
|
||||
|
||||
# Déploiement & infra
|
||||
# Deployment & infra
|
||||
|
||||
## Dockerfile
|
||||
|
||||
Un `Dockerfile` existe (multi-stage Bun Alpine). **L'installation passe par pnpm, mais le runtime/build/test restent bun** (cf. [[knowledge_stack-and-commands]]) :
|
||||
- `FROM oven/bun:1-alpine`, stage `install` : `apk add --no-cache git nodejs npm` puis `npm install -g pnpm@10.26.0` (l'image bun n'a ni Node ni pnpm ; l'`apk nodejs` d'Alpine n'embarque pas corepack), `COPY package.json pnpm-lock.yaml`, puis `pnpm install --frozen-lockfile`. `git` est requis car `@ng-eventually/client` est une dépendance **git+https** publique (Gitea, sans auth). Stage `release` : copie `node_modules` + source.
|
||||
A `Dockerfile` exists (multi-stage Bun Alpine). **Installation goes through pnpm, but runtime/build/test stay on bun** (see [[knowledge_stack-and-commands]]):
|
||||
- `FROM oven/bun:1-alpine`, `install` stage: `apk add --no-cache git nodejs npm` then `npm install -g pnpm@10.26.0` (the bun image has neither Node nor pnpm; Alpine's `apk nodejs` does not ship corepack), `COPY package.json pnpm-lock.yaml`, then `pnpm install --frozen-lockfile`. `git` is required because `@ng-eventually/client` is a public **git+https** dependency (Gitea, no auth). `release` stage: copies `node_modules` plus the source.
|
||||
- `ENV NODE_ENV=production`, `USER bun`, `EXPOSE 3000/tcp`, `ENTRYPOINT ["bun","run","start"]`.
|
||||
|
||||
**Piège `bun` peer** : `bun-plugin-tailwind` déclare `bun` en peerDependency → pnpm matérialise le paquet npm `bun` et **crée un shim `node_modules/.bin/bun`** qui shadow le `bun` du PATH sous `bun run`/`pnpm run`. Son postinstall est ignoré par défaut → shim cassé → `bun run start` échoue. Corrigé en approuvant le build : `pnpm.onlyBuiltDependencies: ["bun"]` dans `package.json` (le postinstall télécharge le vrai binaire). Sans ça, toute la migration pnpm casse le démarrage.
|
||||
**`bun` peer pitfall**: `bun-plugin-tailwind` declares `bun` as a peerDependency → pnpm materializes the npm `bun` package and **creates a `node_modules/.bin/bun` shim** that shadows the `bun` from the PATH under `bun run`/`pnpm run`. Its postinstall is ignored by default → broken shim → `bun run start` fails. Fixed by approving the build: `pnpm.onlyBuiltDependencies: ["bun"]` in `package.json` (the postinstall then downloads the real binary). Without that, the whole pnpm migration breaks startup.
|
||||
|
||||
**Quirk** : `start` = `NODE_ENV=production bun src/index.ts` → le conteneur **exécute la source TypeScript directement** (Bun transpile à la volée), il **n'utilise pas `dist/`**. Le `bun run build` (→ `dist/`) n'est donc **pas** sur le chemin de prod par défaut. Si on veut servir le build, il faut changer l'entrypoint.
|
||||
**Quirk**: `start` = `NODE_ENV=production bun src/index.ts` → the container **runs the TypeScript source directly** (Bun transpiles on the fly), it **does not use `dist/`**. `bun run build` (→ `dist/`) is therefore **not** on the default production path. Serving the build would require changing the entrypoint.
|
||||
|
||||
## CI/CD
|
||||
|
||||
**Aucun** pipeline committé (`.github/workflows/` absent, pas de config Coolify dans le repo). Angle mort assumé. Pour héberger l'app Bun, le skill `coolify-hosting` s'applique.
|
||||
**No** pipeline is committed (`.github/workflows/` absent, no Coolify config in the repo). A knowingly accepted blind spot. To host the Bun app, the `coolify-hosting` skill applies.
|
||||
|
||||
## Variables d'environnement
|
||||
## Environment variables
|
||||
|
||||
- `PORT` (défaut 3000), `NODE_ENV` (active/désactive HMR et l'auto-seed dev — cf. concept `data-layer`).
|
||||
- Aucun `.env*` committé (`.env` est gitignored). Pas de gestion de secrets dans le repo.
|
||||
- `PORT` (default 3000), `NODE_ENV` (enables/disables HMR and the dev auto-seed — see concept `data-layer`).
|
||||
- No `.env*` is committed (`.env` is gitignored). No secret management in the repo.
|
||||
|
||||
## Dev
|
||||
|
||||
`bun run dev` = **`portless festipod bun --hot src/index.ts`** — passe par le wrapper **`portless`** (outil externe de gestion de port), pas un `bun --hot` nu. HMR actif hors production.
|
||||
`bun run dev` = **`portless festipod bun --hot src/index.ts`** — it goes through the **`portless`** wrapper (an external port-management tool), not a bare `bun --hot`. HMR is active outside production.
|
||||
|
||||
**Lien local réactif du polyfill** : en prod la dépendance `@ng-eventually/client` vient de Gitea (git+https, figée par `pnpm-lock.yaml`). Pour éditer le polyfill localement et voir les changements en direct, `pnpm run link:polyfill` (script `scripts/link-polyfill.ts`, stratégie S2) remplace `node_modules/@ng-eventually/client` par une **copie réelle** de la source locale (`…/ng-eventually-js/packages/client`) — **sans** son propre `node_modules/@ng-org` — et resynchronise `src/` à chaque édition. C'est ce qui garantit **une seule instance `@ng-org/web`** (un seul verifier) : un symlink vers le checkout monorepo, lui, embarque son `@ng-org` → 2ᵉ instance → SDK cassé. Revenir à l'état committé : `pnpm install`.
|
||||
**Reactive local link to the polyfill**: in production the `@ng-eventually/client` dependency comes from Gitea (git+https, pinned by `pnpm-lock.yaml`). To edit the polyfill locally and see the changes live, `pnpm run link:polyfill` (script `scripts/link-polyfill.ts`, strategy S2) replaces `node_modules/@ng-eventually/client` with a **real copy** of the local source (`…/ng-eventually-js/packages/client`) — **without** its own `node_modules/@ng-org` — and resyncs `src/` on every edit. That is what guarantees **a single `@ng-org/web` instance** (a single verifier): a symlink to the monorepo checkout would carry its own `@ng-org` → a 2nd instance → broken SDK. To go back to the committed state: `pnpm install`.
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
---
|
||||
type: knowledge
|
||||
summary: Composants de la stack (Bun runtime/build/test, install via pnpm, React, NextGraph, Storybook, Cucumber, Tailwind-dans-le-build) et liste réelle des scripts package.json, dont les quirks (cucumber via node+tsx, build:orm au chemin périmé, build:ng pour le fork local, link:polyfill pour le lien local réactif)
|
||||
summary: Stack components (Bun runtime/build/test, install through pnpm, React, NextGraph, Storybook, Cucumber, Tailwind-inside-the-build) and the real list of package.json scripts, quirks included (cucumber through node+tsx, build:orm pointing at a stale path, build:ng for the local fork, link:polyfill for the reactive local link)
|
||||
---
|
||||
|
||||
# Stack & commandes
|
||||
# Stack & commands
|
||||
|
||||
## Composants
|
||||
## Components
|
||||
|
||||
| Couche | Techno |
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Runtime / bundler / test | **Bun** (cf. [[rule_bun-first]]) |
|
||||
| **Installation des deps** | **pnpm** (`pnpm install`, `pnpm-lock.yaml`) — **seule** l'install passe à pnpm ; runtime/build/test restent bun. Motif : `@ng-eventually/client` est résolu depuis Gitea en **git+https** (pnpm gère proprement `git+…#main&path:/packages/client` + le dédoublonnage des peers `@ng-org`). Ne pas rebasculer l'install vers bun/npm. |
|
||||
| UI | **React** (mobile-first, largeur max 768px — style dans concept `app-architecture`) |
|
||||
| Données | **NextGraph** P2P local-first (concept `data-layer`) |
|
||||
| Build CSS | **Tailwind** (`tailwindcss` + `bun-plugin-tailwind`) — présent dans le build, mais les écrans stylent via `app-*`/inline, pas d'utilitaires Tailwind (cf. concept `app-architecture`) |
|
||||
| Exploration UI | **Storybook** (webpack5 + SWC, port 6006) |
|
||||
| Tests | **Cucumber/Gherkin** FR multi-couches + Playwright + happy-dom + chai (concept `bdd-testing`) |
|
||||
| Runtime / bundler / test | **Bun** (see [[rule_bun-first]]) |
|
||||
| **Dependency installation** | **pnpm** (`pnpm install`, `pnpm-lock.yaml`) — **only** installation moves to pnpm; runtime/build/test stay on bun. Reason: `@ng-eventually/client` is resolved from Gitea over **git+https** (pnpm handles `git+…#main&path:/packages/client` cleanly, along with deduplication of the `@ng-org` peers). Do not switch installation back to bun/npm. |
|
||||
| UI | **React** (mobile-first, max width 768px — styling covered by concept `app-architecture`) |
|
||||
| Data | **NextGraph** P2P local-first (concept `data-layer`) |
|
||||
| CSS build | **Tailwind** (`tailwindcss` + `bun-plugin-tailwind`) — present in the build, but the screens style themselves with `app-*`/inline, no Tailwind utilities (see concept `app-architecture`) |
|
||||
| UI exploration | **Storybook** (webpack5 + SWC, port 6006) |
|
||||
| Tests | **Cucumber/Gherkin** in French, multi-layer, plus Playwright, happy-dom and chai (concept `bdd-testing`) |
|
||||
|
||||
## Scripts `package.json` (réels)
|
||||
## `package.json` scripts (the real ones)
|
||||
|
||||
| Script | Commande / rôle |
|
||||
| Script | Command / role |
|
||||
|---|---|
|
||||
| `dev` | `portless festipod bun --hot src/index.ts` — dev HMR via wrapper `portless` (cf. [[knowledge_deployment]]) |
|
||||
| `start` | `NODE_ENV=production bun src/index.ts` — prod, depuis `src/` (pas `dist/`) |
|
||||
| `build` | `bun run build.ts` — bundler Bun + Tailwind → `dist/` ([[knowledge_build-pipeline]]) |
|
||||
| `test:cucumber` | enchaîne `cucumber:run` → `cucumber:report` → `features:parse` → `steps:extract` |
|
||||
| `cucumber:run` | `node --import tsx/esm node_modules/@cucumber/cucumber/bin/cucumber.js` — **via Node+tsx, pas Bun** (compat plugins Playwright/happy-dom), et via l'**entrée JS réelle du paquet**, pas le shim `.bin/` (voir Pièges) |
|
||||
| `test:data` | idem `--tags @data` |
|
||||
| `test:auth-setup` | `bun scripts/setup-test-auth.ts` — bootstrap wallet de test persistant |
|
||||
| `dev` | `portless festipod bun --hot src/index.ts` — dev with HMR through the `portless` wrapper (see [[knowledge_deployment]]) |
|
||||
| `start` | `NODE_ENV=production bun src/index.ts` — production, served from `src/` (not `dist/`) |
|
||||
| `build` | `bun run build.ts` — Bun bundler + Tailwind → `dist/` ([[knowledge_build-pipeline]]) |
|
||||
| `test:cucumber` | chains `cucumber:run` → `cucumber:report` → `features:parse` → `steps:extract` |
|
||||
| `cucumber:run` | `node --import tsx/esm node_modules/@cucumber/cucumber/bin/cucumber.js` — **through Node+tsx, not Bun** (Playwright/happy-dom plugin compatibility), and through the package's **actual JS entry**, not the `.bin/` shim (see Pitfalls) |
|
||||
| `test:data` | same, with `--tags @data` |
|
||||
| `test:auth-setup` | `bun scripts/setup-test-auth.ts` — bootstraps the persistent test wallet |
|
||||
| `cucumber:report` | `bun scripts/parse-test-results.ts` — `cucumber-report.json` → HTML |
|
||||
| `features:parse` | `bun scripts/parse-features.ts` → `features.ts` |
|
||||
| `steps:extract` | `bun scripts/extract-step-definitions.ts` |
|
||||
| `build:orm` | `rdf-orm build --input ./src/shapes/shex --output ./src/shapes/orm` |
|
||||
| `build:ng` | `bash scripts/build-ng-packages.sh` — (re)build des paquets NextGraph depuis une source locale (outil optionnel) |
|
||||
| `link:polyfill` | `bun scripts/link-polyfill.ts` — lien local **réactif** du polyfill `@ng-eventually/client` (stratégie S2 : copie-overlay + watcher), préserve l'instance `@ng-org` unique. Détails dans [[knowledge_deployment]]. |
|
||||
| `storybook` / `build-storybook` | Storybook dev (6006) / build statique |
|
||||
| `build:ng` | `bash scripts/build-ng-packages.sh` — (re)builds the NextGraph packages from a local source (optional tool) |
|
||||
| `link:polyfill` | `bun scripts/link-polyfill.ts` — **reactive** local link to the `@ng-eventually/client` polyfill (strategy S2: copy-overlay + watcher), preserving the single `@ng-org` instance. Details in [[knowledge_deployment]]. |
|
||||
| `storybook` / `build-storybook` | Storybook dev (6006) / static build |
|
||||
|
||||
## Pièges
|
||||
## Pitfalls
|
||||
|
||||
- **`cucumber:run`/`test:data` tournent sous Node+tsx**, pas Bun — les plugins de test ne chargent pas en import Bun natif. Ne pas « bunifier » ces scripts.
|
||||
- **Ne jamais faire pointer un script sur `node_modules/.bin/*`.** L'install passe par pnpm ([[rule_bun-first]] §exception), qui y place des **shims shell** et non des entrées JS : `node --import tsx/esm node_modules/.bin/cucumber-js` échoue. Invoquer l'**entrée JS réelle du paquet** (`node_modules/@cucumber/cucumber/bin/cucumber.js`). Vaut pour tout script npm qui lancerait un binaire de dépendance sous `node`.
|
||||
- **`build:orm` cible `./src/shapes/shex` et `./src/shapes/orm`**, alors que les shapes réelles vivent sous **`src/shared/shapes/`** — le chemin du script est vraisemblablement **périmé** (à corriger ou exécuter avec les bons chemins ; vérifier avant de régénérer l'ORM).
|
||||
- **`cucumber:run`/`test:data` run under Node+tsx**, not Bun — the test plugins do not load under a native Bun import. Do not "bunify" these scripts.
|
||||
- **Never point a script at `node_modules/.bin/*`.** Installation goes through pnpm ([[rule_bun-first]] §exception), which puts **shell shims** there rather than JS entries: `node --import tsx/esm node_modules/.bin/cucumber-js` fails. Invoke the package's **actual JS entry** (`node_modules/@cucumber/cucumber/bin/cucumber.js`). This holds for any npm script that would launch a dependency's binary under `node`.
|
||||
- **`build:orm` targets `./src/shapes/shex` and `./src/shapes/orm`**, whereas the real shapes live under **`src/shared/shapes/`** — the script's path is most likely **stale** (either fix it or run it with the right paths; check before regenerating the ORM).
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
---
|
||||
type: rule
|
||||
summary: Par défaut utiliser Bun et ses APIs natives, jamais les équivalents Node — bun au lieu de node/ts-node, bun test/build, bunx, et pas d'express/ws/pg/dotenv. EXCEPTION : l'installation des paquets passe par pnpm (les deux repos), pas bun install
|
||||
summary: By default use Bun and its native APIs, never the Node equivalents — bun instead of node/ts-node, bun test/build, bunx, and no express/ws/pg/dotenv. EXCEPTION — package installation goes through pnpm (in both repos), not bun install
|
||||
---
|
||||
|
||||
# Règle : Bun-first
|
||||
# Rule: Bun-first
|
||||
|
||||
Par défaut, utiliser **Bun** et ses APIs natives plutôt que les équivalents Node.js.
|
||||
By default, use **Bun** and its native APIs rather than the Node.js equivalents.
|
||||
|
||||
| Au lieu de… | Utiliser |
|
||||
| Instead of… | Use |
|
||||
|---|---|
|
||||
| `node <file>`, `ts-node` | `bun <file>` |
|
||||
| `jest`, `vitest` | `bun test` |
|
||||
| `npm/yarn install`, `bun install` | **`pnpm install`** (voir exception ci-dessous) |
|
||||
| `npm/yarn install`, `bun install` | **`pnpm install`** (see the exception below) |
|
||||
| `npm run <script>` | `bun run <script>` |
|
||||
| `npx <pkg>` | `bunx <pkg>` |
|
||||
| `webpack`, `esbuild`, `vite` | `bun build` / bundler Bun (HTML imports) |
|
||||
| `webpack`, `esbuild`, `vite` | `bun build` / the Bun bundler (HTML imports) |
|
||||
| `express` | `Bun.serve()` |
|
||||
| `better-sqlite3` | `bun:sqlite` |
|
||||
| `ioredis` | `Bun.redis` |
|
||||
| `pg`, `postgres.js` | `Bun.sql` |
|
||||
| `ws` | `WebSocket` (intégré) |
|
||||
| `ws` | `WebSocket` (built in) |
|
||||
| `node:fs` readFile/writeFile | `Bun.file` |
|
||||
| `execa` | `Bun.$\`...\`` |
|
||||
| `dotenv` | (inutile — Bun charge `.env` automatiquement) |
|
||||
| `dotenv` | (pointless — Bun loads `.env` automatically) |
|
||||
|
||||
Détail des APIs : [[knowledge_bun-apis]].
|
||||
API details: [[knowledge_bun-apis]].
|
||||
|
||||
## Exception : l'installation des paquets passe par pnpm
|
||||
## Exception: package installation goes through pnpm
|
||||
|
||||
**L'installation des dépendances se fait avec `pnpm install` — pas `bun install` — dans les DEUX repos** (Festipod *et* le polyfill `@ng-eventually/client`). Tout le reste reste Bun : **runtime, build, test, scripts** (`bun run dev`, `bun build`, `bun test`, `bunx`). Seule l'étape d'installation change de gestionnaire.
|
||||
**Dependencies are installed with `pnpm install` — not `bun install` — in BOTH repos** (Festipod *and* the `@ng-eventually/client` polyfill). Everything else stays on Bun: **runtime, build, test, scripts** (`bun run dev`, `bun build`, `bun test`, `bunx`). Only the installation step changes package manager.
|
||||
|
||||
**Pourquoi.** Le polyfill est installé en prod depuis un dépôt Gitea comme dépendance git à **sous-répertoire** : `git+https://…/ng-eventually.git#main&path:/packages/client`. pnpm (≥ 10.26) résout ce format `#<ref>&path:/…` et garantit une **seule** instance de `@ng-org/*` (un seul verifier) ; `bun install` ne couvre pas ce workflow proprement. Le lockfile de référence est donc `pnpm-lock.yaml`, et le lien local réactif du polyfill passe par `pnpm run link:polyfill` (voir [[knowledge_deployment]]).
|
||||
**Why.** In production the polyfill is installed from a Gitea repository as a **subdirectory** git dependency: `git+https://…/ng-eventually.git#main&path:/packages/client`. pnpm (≥ 10.26) resolves that `#<ref>&path:/…` format and guarantees a **single** instance of `@ng-org/*` (a single verifier); `bun install` does not handle this workflow cleanly. The reference lockfile is therefore `pnpm-lock.yaml`, and the reactive local link to the polyfill goes through `pnpm run link:polyfill` (see [[knowledge_deployment]]).
|
||||
|
||||
**Conséquence pratique.** Les scripts npm qui reposaient sur `node_modules/.bin/*` peuvent casser (pnpm y place des shims shell, pas des entrées JS) — appeler l'entrée JS réelle du paquet (ex. `node_modules/@cucumber/cucumber/bin/cucumber.js`) plutôt que le shim `.bin/`.
|
||||
**Practical consequence.** npm scripts that relied on `node_modules/.bin/*` may break (pnpm puts shell shims there, not JS entries) — call the package's actual JS entry (e.g. `node_modules/@cucumber/cucumber/bin/cucumber.js`) rather than the `.bin/` shim.
|
||||
|
||||
## Pourquoi (Bun pour tout le reste)
|
||||
## Why (Bun for everything else)
|
||||
|
||||
Le projet est tout-Bun (runtime, bundler, test, serveur). Réintroduire un outil Node redondant ajoute une dépendance, divergerait des conventions du repo, et casse l'intégration native (HMR, transpilation TS automatique, chargement `.env`). C'est un choix de cohérence, pas une préférence cosmétique. L'exception d'installation ci-dessus est le seul écart, et il est motivé par la dépendance git à sous-répertoire.
|
||||
The project is all-Bun (runtime, bundler, test, server). Reintroducing a redundant Node tool adds a dependency, diverges from the repo's conventions, and breaks the native integration (HMR, automatic TS transpilation, `.env` loading). This is a consistency choice, not a cosmetic preference. The installation exception above is the only deviation, and it is motivated by the subdirectory git dependency.
|
||||
|
||||
Reference in New Issue
Block a user