From 77d52ea95c83be9b4230ae73f0f9ee9a3ba7e7cf Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Mon, 11 May 2026 00:49:37 +0200 Subject: [PATCH] docs: aligner top-level + produit + technique + decisions sur le changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les commits récents ont introduit le changelog public, le toast SPA, et la convention de release (bump version.ts + ajout du .md dans le même commit). Les docs reflètent maintenant ce qui est en prod : - CLAUDE.md : V1 IN gagne la mention `/changelog` avec le mécanisme MD versionné + toast SPA. Table "Documents associés" gagne 3 lignes (`apps/landing/src/content/changelog/`, `apps/web/src/version.ts`, `.claude/skills/push/`). - produit.md : nouvelle §4.9 "Changelog public et toast de version" qui couvre le ton produit-only, le mécanisme du toast, la première visite silencieuse, le RSS et le SEO. - tech/architecture.md : ajoute `/changelog` à la table de stratégie de rendu (SSG), met à jour l'arbre de fichiers `apps/landing/` avec `content.config.ts` + `content/changelog/` + `pages/changelog/`, et ajoute une sous-section "Mécanique du changelog (release workflow)" qui décrit le couplage `version.ts` ↔ `.md`. Côté SPA, ajoute la sous-section "Versionnage SPA + toast de release" avant la partie auth. - decisions.md : ADR-022 nouvelle entrée — Changelog en Markdown versionné (pas en DB) avec rationale (release-coupled, pas d'admin à maintenir, review en PR, SSG = LCP optimal, schéma Zod). Co-Authored-By: Claude Opus 4.7 --- CLAUDE.md | 4 ++++ docs/decisions.md | 22 ++++++++++++++++++++++ docs/produit.md | 9 +++++++++ docs/tech/architecture.md | 37 +++++++++++++++++++++++++++++++++---- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 82d4b50..14869c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,6 +78,7 @@ Direct, concret, chaleureux, précis, empathique. *On parle comme un bon associ - Mode démo (sandbox in-app sans engagement) - App mobile (web responsive) - **Blog `rubis.pro/blog`** — SSR par `apps/landing` (Astro 6), contenu en DB (`posts`) servi par `apps/api` via `/api/v1/posts/*`, admin de validation côté `app.rubis.pro/admin/blog`, génération hebdomadaire IA via cron (Sonnet 4.6) avec review humaine obligatoire. Détails dans `/docs/tech/architecture.md`. +- **Changelog `rubis.pro/changelog`** — SSG par `apps/landing`, contenu en MD versionné dans `apps/landing/src/content/changelog/.md` (Astro content collections, schéma Zod), RSS à `/changelog/rss.xml`. Toast SPA "Nouvelle version" déclenché par `apps/web/src/components/version-toast.tsx` quand `APP_VERSION` (`apps/web/src/version.ts`) diffère de `localStorage["rubis:last-seen-version"]`. Workflow release : `/push` (cf. `.claude/skills/push/SKILL.md`) bump la version + crée le .md + commit + push. ### OUT (V2 ou plus tard) @@ -153,6 +154,9 @@ Voir `/docs/decisions.md` pour le log complet avec rationale. | `/Dockerfile` | Build nginx-alpine servant `/landing/` sur port 80 | | `/k3s/` | Manifests Kubernetes (namespace, deployment, service) | | `/.claude/deploy-memory.md` | Procédure de déploiement (Gitea CI ou manuel) | +| `/.claude/skills/push/SKILL.md` | Skill Claude Code `/push` — release automatisée (bump version + entrée changelog + commit + push) | +| `/apps/landing/src/content/changelog/` | Une entrée Markdown par version (source de vérité du changelog public `/changelog`) | +| `/apps/web/src/version.ts` | Constante `APP_VERSION` — déclenche le toast "Nouvelle version" dans la SPA | ## Déploiement diff --git a/docs/decisions.md b/docs/decisions.md index b7bd1c7..08c1ba1 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -4,6 +4,28 @@ --- +## ADR-022 · Changelog en Markdown versionné (pas en DB) + +- **Date** : 2026-05-11 +- **Statut** : ✅ Validée +- **Contexte** : on a besoin d'un changelog public (SEO + tenir les users au courant) avec un toast in-app qui linke vers l'entrée correspondante. Question : où vit la donnée ? +- **Décision** : MD files versionnés dans le repo (`apps/landing/src/content/changelog/.md`), chargés par les content collections d'Astro (schéma Zod). Pas de DB, pas d'admin. +- **Rationale** : + - **Le changelog change avec le code, pas indépendamment.** Une nouvelle entrée = une release = une PR. Versionner dans Git aligne naturellement les deux. + - **Pas d'admin à maintenir.** Le blog a une UI admin parce qu'il est généré par IA hebdomadaire + needs human review. Le changelog est écrit par le dev qui ship — il n'a pas besoin d'une UI séparée. + - **Review en PR** — l'entrée passe sous les yeux comme tout autre code, attrape les fautes et le ton off-brand avant publication. + - **SSG = LCP optimal.** Contenu figé au build, pas d'appel API au render. + - **Schéma typé** (`version`/`date`/`type`/`highlights[]`) validé à build-time par Zod → impossible de pusher un .md mal formé. +- **Alternatives écartées** : + - Mêmes tables `posts` que le blog → forçait un admin UI inutile, et la cadence (chaque release vs chaque semaine) n'a rien à voir + - JSON unique avec toutes les versions → difficile à diff en PR, conflits de merge sur les fichiers volumineux + - GitHub Releases / Gitea Releases → pas indexable Google, pas brandé, pas dans la DA +- **Conséquences** : + - Le bump de `apps/web/src/version.ts` et l'ajout du `.md` doivent être **dans le même commit**, sinon le toast SPA pointe sur une ancre absente + - Skill `/push` (cf. `.claude/skills/push/SKILL.md`) automatise ce ritual + +--- + ## ADR-001 · Conversion 1 rubis = 10 minutes libérées - **Date** : 2026-05-05 diff --git a/docs/produit.md b/docs/produit.md index 92aa4a9..cc78027 100644 --- a/docs/produit.md +++ b/docs/produit.md @@ -117,6 +117,15 @@ Rubis Sur l'Ongle libère le temps des dirigeants de TPE-PME en automatisant la - Tab bar 4 entrées max : Accueil · Factures · Plans · Réglages - Le rubis reste hero du mobile +### 4.9 Changelog public et toast de version + +- **Page publique** `rubis.pro/changelog` — timeline reverse-chrono des versions livrées, une carte par version (chip `v1.x.0` + date + highlights bulletés + body markdown narrative). Sticky rail desktop avec jump-nav, glow rubis sur la version la plus récente. +- **Tonalité** : produit-only, jamais tech (pas de "refactor", "stack", "monorepo"). On parle de ce que **l'utilisateur peut faire de nouveau**, pas de ce qu'on a codé. Une phrase d'attitude max par entrée. +- **Toast in-app** : à l'ouverture de la SPA, si `APP_VERSION` (constante versionnée dans `apps/web/src/version.ts`) diffère de la version stockée en `localStorage["rubis:last-seen-version"]`, on affiche un toast Sonner persistant `Nouvelle version v1.x.0 — Voir les nouveautés ↗`. Clic → ouvre `/changelog#1.x.0` dans un nouvel onglet. +- **Première visite** : silencieux, on enregistre la version sans toast (pas d'onboarding pollué). +- **RSS** : flux 2.0 dispo à `/changelog/rss.xml`, auto-discovered depuis le `` de toutes les pages. +- **SEO** : `prerender = true`, JSON-LD `WebPage` + `mainEntity[TechArticle]` par version pour les rich snippets Google. + ## 5. Fonctionnalités OUT (V2+) ### V2 (planifié) diff --git a/docs/tech/architecture.md b/docs/tech/architecture.md index 759ce15..ade7fcd 100644 --- a/docs/tech/architecture.md +++ b/docs/tech/architecture.md @@ -207,6 +207,13 @@ Cf. `apps/api/app/controllers/blog_controller.ts` + `apps/api/app/transformers/p - **Lucide React** pour les icônes - **Bricolage Grotesque + Inter** via Google Fonts (cohérent landing) +### Versionnage SPA + toast de release + +- `apps/web/src/version.ts` exporte `APP_VERSION` (semver string) et `CHANGELOG_URL` (`https://rubis.pro/changelog`). +- Bump manuel à chaque release, dans la même PR que l'ajout de l'entrée changelog côté `apps/landing`. +- `` (mount-once dans `__root.tsx`) compare cette constante à `localStorage["rubis:last-seen-version"]`. Toast persistant Sonner si différent. Cf. `apps/web/src/components/version-toast.tsx`. +- localStorage indisponible (private mode) → fail silent, pas de toast et pas de crash. + ### Auth côté SPA (cf. ADR-017) - **Access token** stocké en mémoire (variable de module / state Query) — pas localStorage pour éviter XSS - **Refresh token** en cookie httpOnly + SameSite=Strict @@ -258,6 +265,7 @@ Landing publique + blog, en Astro avec rendu hybride statique/SSR. Image Docker | `/blog` | SSR | Doit refléter immédiatement les publications admin | | `/blog/:slug` | SSR | Idem + 404 dynamique | | `/blog/rss.xml`, `/sitemap.xml`, `/robots.txt` | SSR (endpoints `.ts`) | Liste à jour | +| `/changelog`, `/changelog/rss.xml` | SSG (`prerender = true`) | Contenu MD versionné, regénéré à chaque release | **Cache HTTP** : pages SSR avec `Cache-Control: public, max-age=300, stale-while-revalidate=86400`. Absorbe les pics, et un publish admin propage en ≤5 min sans purge active. @@ -266,6 +274,9 @@ Landing publique + blog, en Astro avec rendu hybride statique/SSR. Image Docker apps/landing/ ├── astro.config.mjs # output: server, adapter Node, tailwindcss plugin ├── src/ +│ ├── content.config.ts # collections Astro (changelog) +│ ├── content/changelog/ # 1 .md par version livrée (frontmatter Zod) +│ │ ├── 1.0.0.md ... 1.10.0.md │ ├── pages/ │ │ ├── index.astro # / (SSG) │ │ ├── mentions-legales.astro # SSG @@ -273,10 +284,13 @@ apps/landing/ │ │ ├── cgv.astro # SSG │ │ ├── sitemap.xml.ts # SSR endpoint │ │ ├── robots.txt.ts # SSR endpoint -│ │ └── blog/ -│ │ ├── index.astro # /blog (SSR) -│ │ ├── [slug].astro # /blog/:slug (SSR) -│ │ └── rss.xml.ts # SSR endpoint +│ │ ├── blog/ +│ │ │ ├── index.astro # /blog (SSR) +│ │ │ ├── [slug].astro # /blog/:slug (SSR) +│ │ │ └── rss.xml.ts # SSR endpoint +│ │ └── changelog/ +│ │ ├── index.astro # /changelog (SSG) +│ │ └── rss.xml.ts # SSG endpoint │ ├── layouts/ │ │ ├── Layout.astro # SEO complet, Header + Footer │ │ └── LegalLayout.astro # wrapper prose pour pages légales @@ -290,6 +304,21 @@ apps/landing/ │ └── public/ # favicons, manifest ``` +### Mécanique du changelog (release workflow) + +À chaque release, **deux fichiers doivent être modifiés en même temps**, dans le même commit : + +1. `apps/web/src/version.ts` — bump de la constante `APP_VERSION` (semver, sans préfixe `v`) +2. `apps/landing/src/content/changelog/.md` — nouvelle entrée Markdown avec frontmatter Zod-validé (`version`, `date`, `title`, `type`, `highlights[]`) + +Le composant `` monté au root de la SPA (`apps/web/src/routes/__root.tsx`) compare `APP_VERSION` à `localStorage["rubis:last-seen-version"]` au mount : + +- Clé absente (1re visite) → on enregistre `APP_VERSION` en silence, pas de toast +- Clé identique → rien +- Clé différente → toast Sonner persistant avec action `Voir les nouveautés ↗` qui ouvre `https://rubis.pro/changelog#` dans un nouvel onglet. localStorage est mis à jour à l'affichage (donc l'user ne reverra plus le toast pour cette version même s'il ferme sans cliquer). + +Pour automatiser le ritual des deux fichiers, le skill Claude Code `/push` (cf. `.claude/skills/push/SKILL.md`) bump la version, crée le `.md` correspondant, commit avec un message conventional, et push sur `gitea/main`. + ### Connexion à l'API En SSR, Astro fetch directement le service Adonis via le DNS K3s (interne, pas de TLS) — cf. `API_URL` dans le ConfigMap `rubis-landing-config` :