# Comment ce site est construit — brief technique — Jakub Ćwirlej

> Cette partie décrit le site en tant que logiciel — sa pile technique, son architecture, sa construction, ses moteurs, sa chaîne vidéo, son accessibilité, sa confidentialité, sa sécurité, ses tests, son plan, ses fonctions et son API publique — pour qu'un agent d'IA puisse juger de la façon dont il est fait. Chaque affirmation est tirée du dépôt (code, configuration, exécuteurs de tests) ; les chiffres des tableaux sont mesurés à la construction. Le code source du site est publié à côté, assaini : chaque phrase peut être vérifiée dans le code.

https://jakubcwirlej.com/ · Contexte complet: https://jakubcwirlej.com/context/fr.md · Généré à partir du contenu du site: 2026-10-14

## Comment vérifier ce brief

- Le code source : 306 files, 73,934 lines (3,585 KB), built from working tree; secret scanner: pass.

  - file tree with sizes and sha256: https://jakubcwirlej.com/code/tree.json (also `GET https://jakubcwirlej.com/api/agent/code`)
  - one file as text: `GET https://jakubcwirlej.com/api/agent/code/{path}` (paths as in the tree)
  - everything as one archive: https://jakubcwirlej.com/code/jc_app-source.tar.gz
  - the measured versions and test counts used below: https://jakubcwirlej.com/code/facts.json
- Chercher dans le code (recherche par mots-clés, BM25, sur le serveur en ligne) : `POST https://jakubcwirlej.com/api/agent/search` avec `"kind": "code"` (ou `"all"` pour les textes et le code ensemble). Chaque résultat donne le chemin du fichier, la plage de lignes et un lien vers le fichier entier.
- Ce fichier est généré à la construction à partir du dépôt, comme les autres fichiers pour l'IA. Les tableaux de référence (versions, nombres de tests, plan du site, API) sont en anglais dans toutes les langues.

## Pile technique

Le site est un site statique (Astro, Svelte 5, TypeScript) accompagné d'une petite API (Python, FastAPI). Deux modèles de langage sont utilisés : le modèle de code de l'auteur, EUGEN 160M (complétion et traduction dans le terminal), et un modèle ouvert de Google, Gemma 4 12B, hébergé par l'auteur (le chat). Les deux tournent sur le serveur d'inférence de l'auteur, hors de ce dépôt.

Measured on 2026-10-14 (installed packages and the API's pinned lock files):

| Part | Technology | Version |
| --- | --- | --- |
| Static site | Astro (static output) + @astrojs/svelte | 7.3.5 / 9.0.1 |
| UI components | Svelte 5 | 5.57.1 |
| Language | TypeScript (strict) | 5.9.3 |
| Bundler | Vite (through Astro) | 8.3.1 |
| Scroll engine | GSAP + ScrollTrigger | 3.15.0 |
| Terminal editor | CodeMirror 6 (view / lang-python) | 6.43.13 / 6.2.1 |
| Charts | uPlot | 1.6.32 |
| Unit tests | Vitest | 4.1.11 |
| Browser checks | Playwright (Chromium, WebKit) + safaridriver | 1.63.0 |
| Lint / format | ESLint + typescript-eslint, Prettier | 10.11.0 / 8.70.1, 3.9.9 |
| Type check | @astrojs/check (astro check) | 0.9.10 |
| API language | Python (requires >=3.12) | 3.13.12 in development |
| API framework | FastAPI / Starlette / pydantic | 0.141.1 / 1.7.0 / 2.13.5 |
| API server, HTTP client | uvicorn, httpx | 0.54.0, 0.28.1 |
| Semantic search (optional, not deployed yet: the live server uses BM25 keyword search) | onnxruntime, sentencepiece, numpy | 1.23.2, 0.2.2, 2.5.3 |
| Build machine | Node.js (no version pinned in the repository) | 25.2.1 |

Ce qui compte côté navigateur :

- **Astro** en mode `output: 'static'` : chaque page est du HTML pré-rendu ; le JavaScript n'est ajouté que là où quelque chose bouge. Les changements de page passent par le routeur client d'Astro, avec un fondu.
- **Svelte 5** (runes) pour une seule « île » hydratée — le chat (`client:idle`, conservé d'une page à l'autre). Tout le reste est fait de modules TypeScript ordinaires chargés à la demande par `import()` dynamique.
- **GSAP ScrollTrigger** pilote le moteur de défilement (plus bas) ; **CodeMirror 6** est l'éditeur du terminal (mode Python, texte fantôme) ; **uPlot** dessine les graphiques d'évaluation.
- Les polices sont des fichiers WOFF2 hébergés par le site, réduits à la construction ; aucun CDN n'est nécessaire au fonctionnement.

Ce qui compte dans l'API :

- **FastAPI** + **pydantic** (validation des requêtes avec des limites de longueur explicites), **uvicorn**, **httpx** pour les appels au serveur d'inférence, **PyYAML** pour lire les mêmes fichiers de contenu que la construction du site.
- Recherche sémantique optionnelle : **multilingual-e5-small** (ONNX int8) exécuté sur le CPU avec **onnxruntime** et **sentencepiece** — aucune API externe n'est appelée. Sans lui, l'API revient au classement par mots-clés BM25 — c'est ce que fait aujourd'hui le serveur en ligne : l'index sémantique n'est pas encore déployé.
- Les dépendances sont épinglées avec le sha256 de chaque fichier (`api/requirements.lock`, installé avec `--require-hashes`) et vérifiées par `pip-audit`.

## Architecture

```text
 navigateur
   │  HTML / CSS / JS statiques, films, polices       ┌───────────────────────────────┐
   ├────────────────────────────────────────────────▶│ hébergement statique          │
   │                                                  │ (web/dist)                    │
   │                                                  └───────────────────────────────┘
   │  même origine /api/…  (JSON, server-sent events)
   ▼
 ┌──────────────────────────────────────────────────────────────────────────────────┐
 │ API (FastAPI)                                                                   │
 │  /chat  /complete  /compare  /translate  /results  /kernel   ← la page elle-même│
 │  /agent/…  search · interview · complete · translate · submit · showcase · code │
 │  gardes : taille du corps, limites par onglet et par IP, nettoyage des entrées, │
 │           refus des injections, contrôle des réponses, historique signé         │
 │  local : index de recherche (e5 / BM25), instantané du code, Bot Bar (SQLite)   │
 └──────────────────────────────┬───────────────────────────────────────────────────┘
                                │  HTTPS + un jeton tiré de l'environnement
                                ▼
                     ┌──────────────────────────────┐
                     │ serveur d'inférence (boîte   │  EUGEN 160M · Gemma 4 12B ·
                     │ noire), matériel de l'auteur │  les modèles de comparaison
                     └──────────────────────────────┘
```

- Le navigateur ne parle jamais au serveur d'inférence : chaque appel de modèle passe par l'API, qui ajoute les limites, les filtres et les garde-fous. L'adresse et le jeton du serveur n'existent que dans l'environnement de l'API, jamais dans le dépôt ni dans la page.
- L'API relaie aussi deux fichiers publics du serveur d'inférence (le flux des résultats d'évaluation et le code du kernel), les met en cache et les valide contre un schéma avant que la page ne les voie.
- La page fonctionne sans l'API : les textes, films et démos sont statiques ; le terminal affiche alors des continuations préparées, clairement signalées (« ce n'est pas une sortie d'EUGEN »), et le chat indique qu'il est hors ligne.

## Construction et hébergement

- `npm run build:safe` construit dans un dossier temporaire et ne le met en place que si la construction a réussi (deux renommages) : un serveur qui lit `web/dist` ne voit jamais un site à moitié écrit.
- Avant chaque construction : la version de la démo ViaSanto est vérifiée contre un fichier de verrouillage (taille et sha256 de chaque fichier) ; l'instantané assaini du code est reconstruit et analysé à la recherche de secrets (une trouvaille arrête la construction) ; les versions et les nombres de tests de ce brief sont mesurés.
- Options d'Astro qui comptent : CSS critique intégré (`inlineStylesheets: 'always'`), CSS minifié par Lightning CSS, préchargement au survol, un module de construction qui retire des paquets les marqueurs « à faire » de l'auteur.
- Hébergement : le site tourne à l'adresse https://jakubcwirlej.com sur un serveur OVH — nginx sert la version statique (adresses propres, en-têtes de sécurité, règles de cache, indication de langue selon le pays servie depuis `/i18n/…/geo.json` ; les règles sont tenues dans `infra/firebase.json` et traduites dans la configuration nginx, un test vérifie leur concordance) et relaie `/api` vers l'API sur la même machine, en remplaçant `X-Forwarded-For` par l'adresse du visiteur. Pour les mesures locales, `web/scripts/serve-dist.mjs` sert la version construite avec les mêmes en-têtes et relaie `/api`.
- Chaque adresse absolue vient d'une seule variable (`PUBLIC_SITE_URL`, https://jakubcwirlej.com au déploiement), si bien que les fichiers générés suivent le domaine d'eux-mêmes.

## Langues et modèle de contenu

- Quatre langues : anglais (par défaut, sans préfixe), polonais, français, néerlandais — `/`, `/pl/`, `/fr/`, `/nl/`. La langue est choisie une fois, d'après le pays du visiteur, son fuseau horaire et son navigateur, et seulement sur les adresses sans préfixe ; un choix explicite dans le sélecteur l'emporte toujours et est mémorisé.
- Les textes sont dans des collections de contenu YAML (`web/src/content/{lang}/`), validées par des schémas zod (`web/src/content.config.ts`) : un fichier par chapitre (`media`, `apps`, `ai-dev`), `windows.yaml` pour les fenêtres d'essais, `summaries.yaml` pour la couche éditoriale des fichiers pour l'IA. Les textes de l'interface sont en JSON par langue, superposés à l'anglais.
- Chaque fenêtre et chaque paragraphe ont un identifiant stable, commun à toutes les langues. Une fenêtre incomplète dans une langue est remplacée en entier par l'anglais, puis par le polonais — les langues ne se mélangent jamais dans une fenêtre, et l'attribut `lang` de la fenêtre indique la langue du texte.
- Les mêmes fichiers de contenu alimentent les pages, les fichiers pour l'IA (celui-ci compris), l'index de recherche de l'API et les connaissances du chat : ils ne peuvent pas diverger.

## Le moteur de défilement

- Chaque chapitre est du HTML rendu côté serveur ; un contrôleur de chapitre (`web/src/engine/chapter.ts`) l'observe et l'anime, créé au chargement de la page et démonté avant que la suivante ne la remplace.
- GSAP ScrollTrigger est le seul moteur du défilement : une fenêtre de texte collante dont le texte avance au rythme exact du défilement ; une étape devient active quand elle franchit une ligne de lecture à 40 % de la hauteur de la fenêtre ; aucune lecture de mise en page à chaque image.
- Chaque chapitre a un adaptateur de scène chargé à la demande : `video` (les films de Médias), `device` (le téléphone du chapitre Apps), `desktop` (les fenêtres de terminal d'AI). Les adaptateurs partagent une interface (état, morphose depuis le hero, affichage des médias, fondu, destruction).
- Le chapitre Médias s'ouvre sur une morphose, pilotée par le défilement, du hero vers la mise en page de lecture. Entre les sections, la page saute en une image au lieu de faire défiler du vide (molette, toucher, ↓ / PgDn / Espace, ↑ / PgUp).
- Un bus d'événements typé relie les modules ; l'état partagé utilise les runes de Svelte.

## Chaîne vidéo

- Les films sont encodés à partir des originaux de l'auteur par `web/scripts/build-showreel.py` (ffmpeg) en une échelle à trois niveaux, tout en MP4 :
  - **haut** — 1920×1080 à la cadence de la source : versions HEVC (`hvc1`), AV1 et H.264 ;
  - **moyen** — 1280×720 : HEVC et H.264, plus un recadrage 720×1280 pour les téléphones en portrait ;
  - **bas** — 960×540 à 25 i/s, H.264 seulement, débit plafonné à environ 1 Mbit/s (et un recadrage portrait 540×960).
- Choix du codec dans le navigateur : HEVC, puis AV1, puis H.264, selon `canPlayType`.
- Le niveau de départ dépend de l'appareil et de la connexion : les téléphones commencent en 720p, les ordinateurs en 1080p ; l'économiseur de données, une connexion lente ou inconnue, peu de mémoire ou peu de cœurs font commencer en 540p (Safari n'expose aucun de ces signaux : c'est alors la taille de la fenêtre qui décide). Le choix vaut pour la session.
- Pendant la lecture, le film est échantillonné toutes les 250 ms ; un long blocage, des blocages courts répétés ou un tampon qui se vide font descendre d'un niveau en cours de lecture (directement au plus bas quand la livraison s'effondre), et le fichier inférieur reprend au même instant. Il ne remonte qu'après plusieurs films sans incident.
- Au niveau le plus bas, une trame statique de fines lignes verticales recouvre l'image (CSS), pour que l'image plus douce se lise comme une texture voulue.
- Les affiches sont en AVIF ; les clips des applications en H.264 + AV1 avec affiches AVIF, et des images WebP de secours pour les captures.

## Accessibilité

- `prefers-reduced-motion` est respecté dans le CSS et dans le moteur : pas de morphose pilotée par le défilement, sauts instantanés, aucun mouvement automatique dans les fenêtres du bureau, graphiques dans leur état final.
- Les fenêtres d'essais sont des boîtes de dialogue modales natives : Échap et un clic sur le fond les ferment, le focus passe au bouton de fermeture et revient à la fermeture ; elles sont étiquetées (`aria-labelledby`).
- Clavier : les menus du bureau gèrent les flèches, Début / Fin, Échap et Tab ; le terminal et le chat s'utilisent entièrement au clavier ; les sauts entre sections réagissent au clavier comme à la molette.
- Des régions dynamiques (`aria-live="polite"`) annoncent les réponses du chat, la sortie du terminal et les notifications ; le journal du terminal a `role="log"`. La bulle de l'accueil ne prend jamais le focus.
- Chaque page définit `lang`, avec des alternatives `hreflang` et `x-default`.
- Un lien « aller au contenu » est le premier élément focalisable de chaque page.

## Performance

- Budgets vérifiés par `web/scripts/budget.mjs` (Playwright Chromium, CPU de téléphone + 4G lente simulés, et ordinateur) : JavaScript ≤ 80 Ko et CSS ≤ 25 Ko transférés, LCP ≤ 1,8 s, CLS ≤ 0,02, INP ≤ 100 ms. `web/scripts/lighthouse.mjs` lance Lighthouse (préréglages mobile et ordinateur) sur les trois chapitres.
- Une seule île hydratée ; chaque scène, le bureau, les graphiques et l'hôte des démos sont des morceaux séparés, chargés quand il le faut.
- Polices : cinq fichiers WOFF2 variables hébergés par le site, axes fixés et réduits au latin + latin étendu à la construction, polices de secours ajustées en métrique (aucun décalage de mise en page), seule la police du plus grand texte est préchargée.
- Images en AVIF avec WebP de secours ; films des fenêtres en `preload="none"` ; une étape de média ne prépare jamais plus d'une étape à l'avance.
- La mémoire de la démo en direct sur la durée est mesurée dans un vrai Chrome (`web/scripts/memory-real.mjs`) et un vrai Safari (`web/scripts/safari-perf.mjs`).

## Confidentialité

- Pas d'analytique, pas de traceurs, pas de gestionnaire de balises, pas de scripts tiers, pas de cookies pour les visiteurs.
- Stockage de session uniquement pour l'état de la visite (identifiant de session aléatoire du chat, niveau vidéo, identifiant d'onglet du terminal, état des fenêtres du bureau) ; stockage local uniquement pour un choix de langue explicite.
- Les polices et les films du site sont servis par le site lui-même. Les seuls médias tiers sont les reportages de l'auteur à TVP : l'API cherche l'adresse du flux d'un épisode dans la liste de l'auteur, et le navigateur le lit depuis TVP.
- L'API n'enregistre pas les messages du chat (seulement des tailles et des codes d'état) ; une trace de diagnostic optionnelle des requêtes de complétion existe pour le débogage de l'auteur et est désactivée par défaut. Les contributions au Bot Bar sont conservées en privé, ne sont jamais données à un modèle ni exécutées.

## Sécurité

Static site — headers on every response (quoted from `infra/firebase.json`; the preview server sends the same, and a unit test keeps the two equal):

- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `X-Frame-Options: SAMEORIGIN`
- `Permissions-Policy: geolocation=(), payment=(), usb=(), serial=(), hid=(), bluetooth=()`

The site's own pages:

```text
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; media-src 'self' blob: https:; connect-src 'self'; frame-src 'self'; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'
```

The demo apps carry their own policies (the Orson Rec demo: nothing loaded from outside the site, no forms, `noindex`). `'unsafe-inline'` is allowed for scripts and styles because the pages inline their critical CSS and a few small scripts; there are no third-party scripts to allow.

API (`api/app/webguard.py` and the endpoints):

- every answer: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Content-Security-Policy: default-src 'none'; frame-ancestors 'none'`, `Referrer-Policy: no-referrer`; CORS limited to the site's own origins, GET and POST only; no public OpenAPI schema;
- a request body over 256 KB is refused with 413 before it is parsed (chunked uploads included); every field has an explicit length limit;
- rate limits per browser tab and per IP for every model call, in-flight caps per tab and per IP, a shared cap toward the inference server; the client IP is taken from `X-Forwarded-For` only behind the hosting front end;
- the chat: control and invisible characters stripped; injection, extraction, role-switch and encoded attempts answered with a fixed refusal without calling the model; the model's output scanned while it streams (a per-deploy canary, verbatim copies of the instructions, internal names, system paths, IP addresses) and links outside an allow-list removed; every answer signed (HMAC-SHA256) so a forged history is dropped;
- the API never runs visitors' code: the completion harness only parses and filters text; translations are compiled and tested on the inference side, not in the API; Bot Bar submissions are data, never executed and never shown to a model;
- secrets (the inference server address and token, the signing key, the panel token) come only from the environment; the repository has none, and the published code snapshot is scanned for them on every build.

## Tests et contrôles de qualité

The counts are what the test runners collect at build time (`vitest list`, `pytest --collect-only`), not hand-written numbers:

| Suite | Files | Test cases | Counted by |
| --- | --- | --- | --- |
| Web unit tests (Vitest) | 33 | 307 | `vitest list` |
| API tests (pytest) | 19 | 471 | `pytest --collect-only` |

- **Tests unitaires (Vitest)** : les parties pures du moteur, le terminal (règles du texte fantôme, mode comparaison, traduction), le client du chat (historique signé, rendu en texte — un test XSS rend le composant Svelte), les décisions de niveau vidéo, les replis de langue, les en-têtes de sécurité (ceux du serveur de prévisualisation doivent égaler `infra/firebase.json`) et ces fichiers pour l'IA eux-mêmes.
- **Tests de l'API (pytest)** : chaque endpoint, les garde-fous (refus des injections en cinq langues, contrôle des réponses, historique signé, taille du corps, limites par onglet et par IP, traitement de `X-Forwarded-For`), le harnais de complétion et ses filtres, le schéma du flux de résultats, le relais du kernel, le stockage du Bot Bar et son barème, l'index de recherche et le détecteur de secrets de l'instantané du code.
- **Contrôles dans le navigateur** : `web/scripts/verify.mjs` pilote le site construit avec Playwright dans Chromium et WebKit (assertions de mise en page, erreurs de console et de page, captures). `web/scripts/safari-verify.mjs` pilote le vrai Safari de macOS par safaridriver (W3C WebDriver), parce que le WebKit de Playwright a manqué deux bogues propres à Safari ; il vérifie le défilement par un vrai geste de molette, la taille des textes, la disponibilité des vidéos et la mise en page.
- **Contrôles statiques** : ESLint (JavaScript, typescript-eslint, règles Svelte et Astro ; l'injection de HTML brut dans Svelte est une erreur) et Prettier ; `astro check` (diagnostics TypeScript et Svelte) ; ruff (avec règles de sécurité) pour l'API.
- Lacunes connues : le dépôt n'a pas de configuration d'intégration continue (les contrôles sont lancés à la main avant un commit) ; il n'y a pas d'exécuteur de tests de bout en bout au-delà des scripts ci-dessus.

## Plan du site

**Pages** — 12 static pages, 4 languages × 3 chapters (English unprefixed):

| Chapter | Stage | EN | PL | FR | NL |
| --- | --- | --- | --- | --- | --- |
| Media | video | https://jakubcwirlej.com/ | https://jakubcwirlej.com/pl/ | https://jakubcwirlej.com/fr/ | https://jakubcwirlej.com/nl/ |
| Apps | device | https://jakubcwirlej.com/apps/ | https://jakubcwirlej.com/pl/apps/ | https://jakubcwirlej.com/fr/apps/ | https://jakubcwirlej.com/nl/apps/ |
| AI | desktop | https://jakubcwirlej.com/ai-dev/ | https://jakubcwirlej.com/pl/ai-dev/ | https://jakubcwirlej.com/fr/ai-dev/ | https://jakubcwirlej.com/nl/ai-dev/ |

**Chapters and their steps** (step ids; → the windows a step links to):

- Media: hero → about-me · step-1 · step-2 → reportages · step-3 · step-4 · step-5 → approach · step-6 → ai-journalism
- Apps: step-1 → viasanto · step-2 · step-3 · step-4 · step-5 → orsonai · step-6 → hdk-tech, hdk-library, hdk-hf · step-7 → orson-rec
- AI: step-0 → how-i-started · step-1 → training-lessons · step-2 → training-data · step-3 → kernel-hf · step-4 → tethergate, tethergate-hf · step-5 → ai-safety · step-6 · step-7 → ai-safety, coherence-over-truth, hdk-audit-trails, quantum-barriers, gsi-reanalysis

**Windows** (native modal dialogs, id `doc-{id}`; each text also as Markdown):

- `about-me` — À propos de moi (FR) — https://jakubcwirlej.com/fr/articles/about-me.md
- `approach` — Ma façon de travailler (FR) — https://jakubcwirlej.com/fr/articles/approach.md
- `ai-journalism` — L'IA dans le journalisme (FR) — https://jakubcwirlej.com/fr/articles/ai-journalism.md
- `training-lessons` — Ce que j'ai appris en entraînant mon propre modèle (FR) — https://jakubcwirlej.com/fr/articles/training-lessons.md
- `ai-safety` — Quelques réflexions sur la sécurité de l'IA (FR) — https://jakubcwirlej.com/fr/articles/ai-safety.md
- `training-data` — Données d’entraînement (FR) — https://jakubcwirlej.com/fr/articles/training-data.md
- `how-i-started` — Mes débuts (FR) — https://jakubcwirlej.com/fr/articles/how-i-started.md
- `hdk-tech` — La technologie HDK (FR) — https://jakubcwirlej.com/fr/articles/hdk-tech.md
- `reportages` — the list of the author's 48 TVP episodes with a player inside the window

**Other windows and panels:**

- terminal windows `buffer-{n}.py` (AI, up to 4, "EUGEN 160M coder") inside the section `#terminal`;
- the suggestion window (a translation beside its source, with Apply);
- the module window `eval.py` with its scenes: evaluation card (code / reasoning), scatter against public models, checkpoint history, kernel;
- the chat panel (avatar "Ask me anything" in the corner, docks into the footer at the end of a chapter);
- the home bubble "Don't have time? Your AI does." (Media chapter);
- the full-screen app overlay on phones (ViaSanto).

**Footer** (every chapter): the chat dock, then five columns of text links — Site map (the three chapters), Apps (ViaSanto, OrsonAI), Social (LinkedIn, Substack, Hugging Face), Contact (e-mail) and CV (the CV page `/cv/`, `/{lang}/cv/`, and its PDF, printed from the page by scripts/build-cv-pdf.mjs); a profile or address not published yet shows as muted text, not a link. On phones the language switch sits there too; then the copyright line. The context for AI is in the home bubble, the kernel file in the AI desktop.

**Machine-readable files:**

- https://jakubcwirlej.com/llms.txt — the index for AI agents (llmstxt.org); https://jakubcwirlej.com/llms-full.txt — every text in full, in English, with this technical part
- https://jakubcwirlej.com/context/{lang}.md — the whole site per language (key facts, every text in full, this technical part, FAQ); https://jakubcwirlej.com/context/{lang}-tech.md — this technical part alone (lang: en, pl, fr, nl)
- https://jakubcwirlej.com/articles/{id}.md (English) and https://jakubcwirlej.com/{lang}/articles/{id}.md — one text each, with YAML front matter and an editorial summary
- https://jakubcwirlej.com/code/tree.json, https://jakubcwirlej.com/code/jc_app-source.tar.gz, https://jakubcwirlej.com/code/facts.json — the site's own code and the measured facts
- https://jakubcwirlej.com/sitemap.xml (pages with hreflang alternates, every Markdown file); https://jakubcwirlej.com/robots.txt — the site is in preparation: indexing is currently disabled (robots.txt `Disallow: /`, `X-Robots-Tag: noindex`); AI agents may still use the public API
- JSON-LD on every page: a `Person` (the author) and a `SoftwareApplication` (EUGEN 160M); `hreflang` alternates and a `text/markdown` alternate link to the context file

## Fonctions

### La bulle de l'accueil : « Pas le temps ? Votre IA, si. »

Une petite bulle qui se déplie depuis l'avatar du chat sur la page d'accueil (le chapitre Médias), environ une seconde après le chargement — seulement si elle ne couvre pas le texte du hero. Elle a deux boutons :

- **Brief pour chatbot** copie tout ce fichier (tous les textes en entier + cette partie technique) dans le presse-papiers ;
- **Fichier pour les agents IA** est un lien vers le même fichier Markdown avec l'attribut `download` : une personne le télécharge, un agent d'IA qui lit la page l'ouvre comme une page. Si la copie échoue, la bulle propose le téléchargement par ce bouton.

Elle ne prend jamais le focus ; Échap ou × la ferme pour cet affichage de la page.

### L'agent du chat

- Un assistant d'IA qui répond aux questions sur Jakub et le site en son nom, à la troisième personne, dans la langue de la page (polonais, anglais, français, néerlandais). Chaque conversation commence par la mention qu'il s'agit d'une IA.
- Modèle : Gemma 4 12B, modèle ouvert de Google, hébergé par l'auteur. Fenêtre de contexte : 4096 jetons ; réponses de 2 à 4 phrases (400 jetons au plus), température 0,3.
- Recherche : chaque prompt contient un noyau fixe (règles, faits clés, biographie, plan du site, résumé TVP, EUGEN et la ligne d'évaluation en direct) et, pour chaque question, jusqu'à quatre passages des textes de l'auteur trouvés par l'index de recherche du site, les résumés éditoriaux des textes correspondants, de courtes notes écrites uniquement à partir des textes du site, et autant d'historique récent que possible.
- Il peut : répondre à partir des textes du site, indiquer les textes utilisés, et proposer des boutons qui ouvrent une fenêtre, montrent le terminal ou changent de chapitre (c'est le visiteur qui les presse ; rien ne s'exécute tout seul).
- Il ne peut pas : naviguer sur le web, exécuter du code, utiliser des outils ni répondre à des questions générales sans rapport avec le travail de Jakub ; il refuse les devoirs et l'écriture de code. Les tentatives d'injection reçoivent un refus fixe sans atteindre le modèle.
- Limites : 500 caractères par question, 12 tours d'historique acceptés (6 envoyés au modèle), 20 questions par onglet et 40 par IP en 10 minutes, une question à la fois par onglet.

### Le terminal et EUGEN 160M

EUGEN 160M est le petit modèle de langage pour le code de l'auteur (environ 160 millions de paramètres, entraîné de zéro, à peu près au quart de l'entraînement prévu). Dans le terminal du chapitre AI :

- `code` ouvre un éditeur où EUGEN propose du code en texte fantôme quand le visiteur s'arrête en fin de mot ou de ligne : **Autocomplete** (la fin de la ligne) ou **Next line** (des lignes entières), à basculer avec Maj+Tab ou `mode`. Tab accepte, ⌘→ prend le mot suivant, Échap ignore, ⌘Z annule.
- Remplissage au milieu (FIM) : l'API n'envoie au modèle le code situé après le curseur que lorsque cela devrait aider (règle mesurée : le FIM était moins bon pour les insertions simples et meilleur quand un nom nécessaire se trouve sous le curseur) ; il est désactivé en mode comparaison.
- Chaque suggestion passe les mêmes filtres avant d'être affichée : règles d'arrêt, restes du format du prompt, prose, appels destructeurs, répétitions, espaces réservés, parenthèses non fermées, chevauchement avec le code en dessous et noms qui n'existent pas.
- `translate --to cpp|java|rust|go|js` traduit depuis Python la fonction sous le curseur ; une fenêtre latérale montre la traduction et ce qui a été vérifié (pour C++ et Rust : compilation, tests réussis, sorties égales à celles de Python, vitesse).
- Sans le serveur d'inférence, le terminal affiche des continuations préparées, signalées comme ne venant pas d'EUGEN.

L'aide du terminal, mot pour mot :

```text
help | /help              this list (works in the editor too)
whoami                    who is typing to you
ls projects               the projects on this page
results                   where the evaluation results are (the section below)
model info                what is behind the ghost text
ask {question}            hand the question to my chat agent
code                      open the editor — EUGEN 160M coder suggests: Tab accepts, ⌘→ the next word, Esc dismisses, ⌘Z undoes, :q leaves
                          suggestions come on a pause at the end of a word or line, not mid-word
shift+tab | /mode         Autocomplete (rest of the line) → Next line (whole lines)
translate --to {lang}     suggest a translation (cpp | java | rust), one function at a time — EUGEN's own; the window says what was checked; /translate cpp in the editor
/revert                   undo an applied translation (Replace in buffer — the one under the cursor, or the latest); accepted ghost text: ⌘Z
clear                     clear the screen
```

### Le mode comparaison

L'éditeur peut montrer, à côté du texte fantôme d'EUGEN, ce qu'un autre petit modèle de code public propose pour exactement la même pause : **Falcon-H1-Tiny-Coder-90M** (TII), **Qwen2.5-Coder-0.5B** (Qwen Team, Alibaba Cloud) ou **tiny_starcoder_py** (BigCode), chacun avec l'attribution exigée par sa licence. Seule la suggestion d'EUGEN peut être acceptée. Les règles (« jamais de conditions moins bonnes pour le modèle comparé ») :

- la même pause, envoyée aux deux côtés dans le même instant ; aucun n'attend l'autre ;
- la même forme de requête et le même harnais : choix du mode, contexte, nombre maximal de jetons, règles d'arrêt et chaque filtre ; le contrôle de noms supplémentaire de la passerelle est désactivé pour chaque côté ; une requête non diffusée en flux par côté ;
- température 0 (décodage glouton) pour chaque côté, donc aucune graine d'échantillonnage n'intervient ; FIM désactivé pour chaque côté ;
- le même comportement du client (cache, frappe « à travers » une suggestion, pause après « occupé ») et des limites séparées mais égales ;
- la latence mesurée de chaque côté, telle que le navigateur l'a vue ; un décompte par session des caractères acceptés et des caractères qui auraient correspondu à ce que le visiteur a tapé.

### La carte d'évaluation

Une fenêtre du chapitre AI avec les résultats d'EUGEN aux benchmarks, lus à l'exécution dans le flux de résultats de l'API (validés, en cache ; rien n'est écrit en dur dans la page) :

- deux écrans — code (HumanEval, HumanEval C++, HumanEval Java, MBPP) et raisonnement (CRUXEval-I, CRUXEval-O, GSM8K assisté par programme) ;
- chaque benchmark est une capsule allant de **greedy pass@1** (« une réponse déterministe par tâche — toujours le jeton le plus probable ; compte si elle passe les tests de la tâche à l'exécution ») à **top-10 pass@10** (« 10 réponses échantillonnées par tâche ; résolu si l'une d'elles passe les tests »), avec le point **EUGEN stack** entre les deux quand il est mesuré (« d'abord greedy ; si la réponse échoue à un contrôle d'exécution visible, nouvel échantillonnage, jusqu'à 5 essais ; notation sur des tests cachés ») ;
- pour chaque benchmark, la carte montre la meilleure valeur mesurée à n'importe quel checkpoint ; une valeur venant d'un checkpoint antérieur est signalée comme telle ; ce qui n'est pas encore mesuré affiche « pending », jamais un chiffre inventé ;
- un nuage de points situe EUGEN parmi les modèles publics de moins de 0,5 milliard de paramètres (HumanEval+, MBPP+, CRUXEval-I/O, GSM8K, BigCodeBench), chaque point de référence avec sa source.

### Résultats face à face

Les tests de l'auteur, tels que l'agent du chat du site les rapporte (les rapports complets ne sont pas publiés) : sur le code privé de l'auteur, qu'aucun modèle n'a vu à l'entraînement (400 + 400 tâches), EUGEN 160M, à environ un quart de son entraînement, termine correctement une ligne dans 42,0 % des cas (tiny_starcoder_py 42,5 %, Falcon-H1-Tiny-Coder-90M 40,2 %, Qwen2.5-Coder-0.5B 39,0 %) et prédit la ligne suivante dans 24,0 % (Qwen 25,2 %). Remplissage au milieu (100 tâches) : Qwen 38 %, EUGEN 24 %, Falcon 8 %. Traduction de 100 fonctions, compilées et exécutées sur 6 tests chacune : Python → Rust 15–17 % pour EUGEN contre 10–14 % pour Qwen ; Python → C++ par la chaîne vérifiée d'EUGEN : 77 sur 100 compilent et 53 sur 100 passent tous les tests (le chiffre du site ; dans le test face à face, à un checkpoint antérieur, à égalité avec Qwen) ; Java 6 % au premier essai, 25 % après un tour d'auto-réparation, Qwen 47 %. Dans les quatre modèles, environ 20–35 % des suggestions paraissent plausibles mais sont fausses. Les causes de ces écarts ne sont pas établies.

### Le kernel creux du projet EUGEN

Un kernel GPU que l'auteur a écrit avec un agent d'IA de programmation pour entraîner un modèle creux ; dans un modèle d'environ 300 M de paramètres avec un FFN de 4 096, la variante creuse s'est entraînée 1,23× plus vite que la dense de même capacité, et les optimisations suivantes du kernel ont relevé son débit de 36 % de plus par rapport à sa première version (pour un FFN de 8 192, un gain plus grand est attendu, pas encore mesuré). Le fichier publié (`eugen_sparse.py`, environ 900 lignes, licence CC BY-NC 4.0) est du Python avec des kernels GPU OpenAI Triton — Triton les compile en code CUDA pour les cartes NVIDIA : une couche feed-forward SwiGLU creuse avec ses passes avant et arrière, et une implémentation de référence simple en PyTorch pour la vérifier. Il vient des variantes creuses du modèle ; l'EUGEN 160M de production a un FFN dense et ne l'utilise pas. Le même fichier, avec tests, exemple et documentation : https://huggingface.co/JakCwi/eugen-sparse.

- Le lire : `GET https://jakubcwirlej.com/api/kernel` (text/plain ; avant publication l'API répond 204 et la page tape un texte provisoire signalé comme tel). La licence : `GET https://jakubcwirlej.com/api/kernel/license`.
- Les liens « Fichier du kernel » et « Licence » du pied de page y mènent ; ils n'apparaissent qu'une fois que l'API sert vraiment le fichier. Le chapitre AI tape aussi le kernel dans une fenêtre, avec coloration syntaxique.

### Le Bot Bar

Un endroit où un agent d'IA (ou la personne qui le fait tourner) peut laisser une contribution à l'auteur après avoir testé le site ou EUGEN : un avis, un cas d'usage, un rapport de bogue ou un benchmark — `POST https://jakubcwirlej.com/api/agent/submit`. Du point de vue du visiteur :

- une contribution est conservée en privé ; l'auteur les examine toutes, et rien n'est publié sans sa décision ; ce qu'il choisit de montrer apparaît sous `GET https://jakubcwirlej.com/api/agent/showcase` ;
- les contributions sont traitées comme des données : aucun modèle ne les lit et le code soumis n'est jamais exécuté (le Python est seulement analysé pour vérifier sa syntaxe) ;
- des points ne vont qu'aux contributions que l'auteur peut vérifier — un bogue, un benchmark ou un cas d'usage vérifié compte le plus, un avis le moins ; doublons, déchets et promotion ne comptent pour rien, et rien n'est jamais donné pour promouvoir ou partager le site ;
- limites : 10 contributions par IP en 10 minutes, 500 par jour au total.

### Les démos du chapitre Apps

- **ViaSanto** — la vraie application de prière de l'auteur (Flutter, compilée pour le web), qui tourne dans le téléphone dans un cadre de même origine ; elle se charge quand la page est au repos, un pilote automatique joue des scènes jusqu'à ce que le visiteur la touche, et son premier geste lui rend la main. Sur téléphone, le téléphone du hero l'ouvre en plein écran. Sa version est épinglée par un fichier de verrouillage avec la taille et le sha256 de chaque fichier.
- **OrsonAI** — une copie du front-end du vrai éditeur, identique octet pour octet à l'exception d'un fichier d'entrée modifié, en mode invité, avec l'API servie localement et des réponses de chat préparées ; un pilote automatique conduit l'éditeur.
- **Orson Rec** — la page de démo figée de l'application d'enregistrement, avec un moteur audio synthétique (sans micro) ; le pilote automatique enregistre et coupe une prise pendant qu'une transcription préparée s'écrit d'elle-même.
- Les deux démos Orson alternent dans le téléphone environ toutes les 34 secondes ; un seul cadre est actif à la fois.

## API publique

Base: https://jakubcwirlej.com/api. JSON in, JSON out, no authentication, no streaming. The self-description with every field and the current limits: `GET https://jakubcwirlej.com/api/agent`. Only the endpoints below are meant for agents; the page's own streaming endpoints (chat, ghost text) are for the page, and the author's private panel is not public.

| Method and path | Request | Answer |
| --- | --- | --- |
| `GET /agent` | — | every endpoint, its fields, the limits |
| `POST /agent/search` | `{"query": 2–500 chars, "lang"?: "pl" \| "en" \| "fr" \| "nl", "k"?: 1–8, "kind"?: "text" \| "code" \| "all"}` | `{"engine", "kind", "results": [...]}` — text hits: `{kind, id, title, section, lang, url, score, words, text}`; code hits: `{kind, id, path, lines: [first, last], url, score, text}` |
| `GET /agent/search/info` | — | engine (BM25 keyword search on the live server; e5 semantic only where that index is deployed), model, chunks per language, code chunks, index date |
| `POST /agent/interview` | `{"question": 2–500 chars, "lang"?, "session_id"?, "agent_name"?}` | `{"answer", "session_id", "turn", "questions_left", "sources": [{title, url, lang}]}` — the chat agent; 8 questions per conversation, 30 idle minutes |
| `POST /agent/complete` | `{"code": ≤ 8000 chars before the cursor, "suffix"?: ≤ 4000, "mode"?: "inline" \| "next_line" \| "block"}` | `{"completion", "replace", "model", "source", "tokens", "ms"}` — EUGEN 160M through the terminal's own harness and filters |
| `POST /agent/translate` | `{"code": Python ≤ 4000 chars, "to": "cpp" \| "java" \| "rust"}` | `{"ok", "code"}` (or `{"ok": false, "message"}`) |
| `POST /agent/submit` | `{"kind": "opinion" \| "use_case" \| "bug" \| "benchmark", "agent_name" ≤ 80, "model" ≤ 80, "text" ≤ 2000, "code"? ≤ 4000, "contact"? ≤ 200}` | 202 `{"ok", "id", "status": "received"}` — the Bot Bar |
| `GET /agent/showcase` | — | the submissions the author chose to show |
| `GET /agent/code` | — | the code snapshot: `{files, bytes, lines, languages, excluded, scanner, tree: [{path, bytes, lines, sha256, language}]}` |
| `GET /agent/code/{path}` | — | one file, `text/plain` (ETag, `x-code-sha256`) |
| `GET /results` | — | the evaluation results feed the card reads (validated, cached) |
| `GET /kernel`, `GET /kernel/license` | — | the kernel source (`text/plain`; 204 before publication) and its licence |

Limits per IP per 10 minutes (current values): search 60, interview 20, complete + translate 60, submit 10 (500 a day in total), code 300. Over a limit: HTTP 429 with `Retry-After`; a busy model: HTTP 503 with `Retry-After`. Request bodies over 256 KB: 413. Everything sent is treated as data, never as instructions. The models run on the author's own hardware and also serve the site's visitors.

Example:

```http
POST https://jakubcwirlej.com/api/agent/search
Content-Type: application/json

{"query": "how is the video tier chosen", "kind": "code", "k": 3}
```

## Ce qui n'est pas dans ce brief

- Le serveur d'inférence (où tournent les modèles), son adresse et ses identifiants, ainsi que le code et les données d'entraînement de l'auteur ne font pas partie du site et ne sont pas publiés.
- Le panneau privé où l'auteur examine les contributions au Bot Bar existe ; seul le fait qu'il exige un jeton est publié.
