8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c4910889de |
feat(landing/changelog): glow rubis sur la dernière version, retire les pills sur les anciennes
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 52s
Le contexte "Nouveauté" sur 11 cartes à la suite devenait du bruit visuel (toutes les anciennes versions ont été des nouveautés à un moment, hein). Le pill de type est maintenant réservé à la version la plus récente — sur les autres on garde seulement la chip de version + la date. Pour compenser et ancrer le regard sur le dernier état du produit, la carte "latest" gagne un glow rubis multi-couches : - Ring serré 4 px en couleur rubis-glow (contour soft) - Drop shadow proche teintée rubis (profondeur) - Bloom large diffus (halo ambiant) - Animation "respiration" 5 s ease-in-out infini (variation subtile sur l'intensité du bloom) - Désactivée si `prefers-reduced-motion: reduce` côté user. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
fc0d13e955 |
feat(landing): changelog public /changelog + flux RSS
Page Astro prerendered qui liste les versions livrées en reverse-chrono. Contenu géré en MD files versionnés dans le repo via Astro content collections (`src/content.config.ts`), pas de DB — chaque release = 1 fichier `src/content/changelog/<x.y.z>.md` ajouté en PR à côté du bump de version applicatif. 11 entrées initiales (v1.0.0 → v1.10.0) couvrant le premier mois public : lancement (OCR Mistral + plans par défaut + mode démo + Stripe), saisie manuelle, SSO Google puis Microsoft, plans custom, templates email, réécriture IA, insights, blog, marque blanche, remerciement automatique. UI : - Hero centré aligné DA landing (eyebrow rubis + h1 display + sub muted) - 2 colonnes desktop : feed cartes (gauche) + sticky rail jump-nav (droite) - Sur mobile/tablette : pas de rail, juste le feed - Sticky rail : IntersectionObserver inline qui met en surbrillance la version courante quand l'user scrolle - Anchors `#1.4.0` partageables, cliquables depuis le chip de chaque carte - Type pills colorés : feature (rubis solid), improvement (cream-2), fix (line outline) - Bullets losanges ◆ rubis cohérents avec le gem brand SEO : - `prerender = true` → HTML figé au build, LCP minimum - JSON-LD WebPage avec mainEntity[TechArticle] par version → rich snippets Google - Flux RSS 2.0 à `/changelog/rss.xml` (prerendered aussi) - Auto-discovery RSS ajoutée au Layout (à côté de celle du blog) - Lien Changelog ajouté au SiteFooter à côté de Blog Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3052a7e909 |
feat(landing): brand "Rubis.pro" + corrections copy
- Brand : suffixe `sur l'ongle` → `.pro` (attaché, muted, non italique).
Propagé partout via `<Brand withSuffix>` — header/footer landing,
topbar/sidebar SPA, login/signup/onboarding. Renforce l'identification
brand requise par Google OAuth (gem seul jugé trop générique).
- Hero : dots groupés avec leur label (inline-flex) — ne se baladent plus
en début de ligne au flex wrap. Ajout d'un topbar mock "Rubis.pro ·
Tableau de bord" en haut de la carte hero pour identifier le mock comme
un vrai dashboard. `DSO` → `DSO*`.
- Stats : "Trois chiffres qui devraient vous fâcher" → "Trois chiffres
exorbitants". Sous-titre remplacé par "Et vous faites sûrement partie
intégrante de ces enquêtes." (plus direct, moins gratuit).
- Promise : "Votre temps vaut plus que ça" → "Votre temps est plus
précieux". Réécriture de l'amorce ("votre boîte / lundis soirs" →
"votre entreprise / journées"). Ajout d'une ligne italique "Parfois
moins, si votre plan par défaut est bien réglé".
- HowItWorks : Step 01 — suppression de "à la caisse" et de ", RIB"
(l'OCR ne lit pas le RIB en V1). Step 03 — "La machine fait le reste"
→ "L'algorithme fait le reste".
- Gamification : suppression de "Pas un PDF abscons" (jargon inutile) et
de "Et oui, on garde un classement amical" (le classement n'est pas en
V1). `DSO` → `DSO*`.
- Footnotes : ajout de la définition DSO (Days Sales Outstanding) sous
celle d'OCR.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
06a3aaf468 |
feat(landing): step 04 — remerciement automatique au client
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 58s
Ajoute une 4e étape dans la section « Comment ça marche » qui matérialise la fin heureuse du cycle : le client paye, Rubis envoie automatiquement un mot court de remerciement (« Merci, paiement bien reçu »). Pourquoi c'est important côté pitch : - Aligne le produit avec le principe brand « respectueux du client final » (cf. CLAUDE.md). On n'est pas qu'un outil de pression — on est aussi celui qui sait dire merci. - Crée une attente positive de fin de cycle, qui s'enchaîne mieux vers le compteur de rubis (déplacé du step 03 vers 04 pour servir de récompense narrative à la boucle complète). Modifs : - En-tête : « Trois étapes → Quatre étapes » + ajustement du sous-titre. - Step 03 : retitré « Vous validez. La machine fait le reste. » (le punchline « Et puis c'est tout » migre implicitement sur 04). - Step 04 (nouveau) : email de remerciement + encart rubis. - Nouveau ThankYouWidget : email card stylé Apple Mail (sender, sujet, corps, badge « Envoyé automatiquement ») en tokens rubis uniquement (pas de vert — interdit par brand). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
6993d80089 |
perf(landing): inline critical CSS + preload latin woff2
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 59s
Élimine ~130 ms du critical path LCP rapportés par l'audit Lighthouse : - `build.inlineStylesheets: "always"` dans astro.config.mjs : la feuille `_astro/Layout.<hash>.css` (~42 KiB) n'est plus une requête séparée render-blocking, elle est inlinée dans le HTML. Coût : +42 KiB par page prerenderée (≈10 KiB gzippé sur la wire). Gain : 80 ms FCP. `"auto"` aurait été ignoré (Layout.css > 4 KiB du seuil interne). - `<link rel="preload">` sur les 2 woff2 latin (Inter body + Bricolage Grotesque display) dans Layout.astro. Casse la chaîne HTML→CSS→fonts du network dependency tree (~50 ms gagnés). URLs résolues via Vite `?url` import → hashing préservé entre les builds. On ne preload que latin-wght-normal — les autres subsets (latin-ext, vietnamese, cyrillic, greek) sont chargés à la demande et ne valent pas le poids upfront pour un trafic FR/latin. Vérifié build : `<link rel="stylesheet">` disparu du HTML rendu, 1 seul `<style>` inline présent, 2 `<link rel="preload">` avec les bons hashes d'asset. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
eda5436d12 |
fix(seo): smart title suffix + OG image par défaut 1200×630
All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 1m1s
Audit SEO révélait deux pertes de valeur :
1. **Titres trop longs** (86 chars sur la home, 71 chars sur les articles).
Google tronque à ~60 chars dans le SERP. Le suffixe automatique
`— Rubis sur l'ongle` (20 chars) écrasait le message-clé.
Layout.astro fait maintenant un suffix smart :
* Si title <45 chars ET ne contient pas "Rubis" → suffix `— Rubis` (8 chars)
* Sinon → titre tel quel (la brand est déjà couverte par og:site_name +
JSON-LD publisher + le hostname rubis.pro visible dans la SERP).
Résultat : home 64 chars, article 51 chars, "Mentions légales" → 24 chars
avec suffix.
2. **Pas d'`og:image` sur les pages sans hero** (home, légal). Sur
LinkedIn/X/Slack, aucune preview image — perte d'engagement énorme.
Ajout d'un og-default.png 1200×630 (165 KB optimisé) dans
apps/landing/public/, monté par fallback par Layout.astro quand la
page ne fournit pas d'`ogImage` explicite. Twitter:card devient
toujours `summary_large_image`.
L'image a été générée via le nouvel outil HTML
docs/marketing/assets/og-default.html (clone de la mécanique du
linkedin-banner.html — clic sur "Télécharger PNG" et go).
Compose : brand + tagline + mock card 124 rubis + CTA rubis.pro.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
6dcae6956c |
feat(blog): admin CRUD + image upload + sidebar link
L'éditeur du blog (jusqu'ici limité au seeder) a maintenant une vraie
interface au-dessus de l'API.
Backend (apps/api) :
* Migration users.is_admin (boolean default false).
* Middleware admin (404 si user.is_admin=false après auth).
* Commande ace promote:admin --email=… [--revoke].
* AdminPostsController CRUD complet : list/show/store/update/publish/
unpublish/destroy + suggest-slug. Au save, contentHtml + wordCount +
readingTime sont re-calculés via blog_renderer. Au publish, durcit la
validation SEO (titre ≤60, excerpt 120-160, hero+alt requis, ≥600 mots),
flippe status='published' + publishedAt, ping Google+Bing pour le sitemap.
* BlogUploadsController :
- POST /api/v1/admin/uploads (multipart, JPEG/PNG/WebP, max 4MB)
→ MinIO clé uploads/blog/{uuid}.{ext}
→ renvoie URL relative /api/v1/uploads/blog/{filename}
- GET /api/v1/uploads/blog/:filename (public, cache immutable 1 an)
→ stream depuis MinIO, regex anti-traversal sur le nom.
* UserTransformer expose isAdmin (cf. shared/types/user).
* k3s/app/landing.yml : NodePort 30111 explicite (pour Traefik repo proxmox).
Frontend (apps/web) :
* Lib typée admin-blog (calls API, queryKeys, helpers URL).
* Route /admin/blog : liste filtrable avec status badge, ouverture
publique, dépublier, supprimer, "+ Nouveau brouillon".
* Route /admin/blog/:id : éditeur 2-colonnes
- Gauche : @uiw/react-md-editor (lazy import) avec preview live.
- Droite : hero image (drag&drop + alt), excerpt avec compteur
120-160, tags, aperçu Google snippet, validations bloquantes.
- Autosave debounce 2s + bouton Publier qui sauve d'abord.
- Hero image upload via MinIO (HeroImageUpload component).
* Sidebar : lien "Blog (admin)" si user.isAdmin.
* Gate côté client (beforeLoad redirect si non admin) + côté serveur
(middleware admin) — defense in depth.
Note : les requirements de publish miroir backend ↔ frontend (cf.
PUBLISH_REQUIREMENTS dans validators/post.ts et VALIDATION_RULES dans
admin.blog_.\$id.tsx). À synchroniser si un seuil bouge.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e5530930b3 |
feat: refactor frontend en stack React unifiée (Astro + packages/ui)
Trois surfaces partagent désormais le même design system, Tailwind v4
et React 19 — au lieu d'avoir landing en HTML vanilla, app en React, et
blog en Adonis SSR :
* packages/ui — design system partagé (tokens Tailwind v4 + composants
TSX) extrait depuis apps/web : Brand, Gem, Button, Card, Chip, Eyebrow,
EmptyState. apps/web migre 41 imports vers @rubis/ui.
* apps/landing — nouvelle app Astro 6 SSR (rubis.pro), remplace l'ancienne
landing nginx vanilla. Embarque :
- Landing complète portée en sections React (Hero, Stats, Promise,
HowItWorks, Gamification, Legal, Pricing, FAQ, FinalCTA, Footnotes)
- Pages légales (mentions, confidentialité, CGV) via LegalLayout.astro
- Blog SSR (/blog, /blog/:slug) qui consomme /api/v1/posts
- sitemap.xml, blog/rss.xml, robots.txt en endpoints Astro
- SEO complet (canonical, hreflang, OG, Twitter Card, JSON-LD
Article/BreadcrumbList/Blog/SoftwareApplication)
* apps/api — BlogController réduit à 2 endpoints JSON (GET /api/v1/posts
+ GET /api/v1/posts/:slug). Suppression des templates SSR Adonis
(apps/api/app/blog/), de l'alias #blog/*, des deps react-dom et
@types/react-dom. PostTransformer + PostSummaryTransformer ajoutés.
Le service blog_renderer + le seeder + les 3 articles fondateurs
restent intacts (réutilisés par futurs admin + cron IA).
* Infra :
- Dockerfile.landing (multi-stage Node 22 + tini, Astro standalone)
- k3s/app/landing.yml (Deployment + Service rubis-landing:4321 +
ConfigMap avec API_URL=http://rubis-api.rubis.svc.cluster.local:3333)
- .gitea/workflows/deploy.yml mis à jour pour build rubis-landing
- .gitea/workflows/deploy-web.yml + Dockerfile.web : prennent en
compte packages/ui/ comme dépendance
- Suppression du Dockerfile nginx legacy + k3s/{deployment,service}.yml
- Suppression de landing/ (assets favicons migrés vers
apps/landing/public/)
* Docs : architecture.md (vue d'ensemble + §4bis apps/landing complet,
§3 endpoints JSON blog, layout monorepo), CLAUDE.md (stack technique,
documents associés, déploiement).
Note infra : l'ancien Deployment "rubis" (nginx) et son Service ne sont
PAS supprimés par la CI — à nettoyer manuellement après validation que
Traefik a été repointé sur rubis-landing:4321 dans le repo proxmox.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|