From fc0d13e955d3653af598b32a812592b989b91bf6 Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Mon, 11 May 2026 00:05:45 +0200 Subject: [PATCH] feat(landing): changelog public /changelog + flux RSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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/.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 --- apps/landing/src/components/SiteFooter.tsx | 3 + apps/landing/src/content.config.ts | 31 ++ apps/landing/src/content/changelog/1.0.0.md | 28 ++ apps/landing/src/content/changelog/1.1.0.md | 14 + apps/landing/src/content/changelog/1.10.0.md | 14 + apps/landing/src/content/changelog/1.2.0.md | 14 + apps/landing/src/content/changelog/1.3.0.md | 14 + apps/landing/src/content/changelog/1.4.0.md | 17 + apps/landing/src/content/changelog/1.5.0.md | 14 + apps/landing/src/content/changelog/1.6.0.md | 16 + apps/landing/src/content/changelog/1.7.0.md | 15 + apps/landing/src/content/changelog/1.8.0.md | 14 + apps/landing/src/content/changelog/1.9.0.md | 14 + apps/landing/src/layouts/Layout.astro | 4 +- apps/landing/src/pages/changelog/index.astro | 337 +++++++++++++++++++ apps/landing/src/pages/changelog/rss.xml.ts | 77 +++++ 16 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 apps/landing/src/content.config.ts create mode 100644 apps/landing/src/content/changelog/1.0.0.md create mode 100644 apps/landing/src/content/changelog/1.1.0.md create mode 100644 apps/landing/src/content/changelog/1.10.0.md create mode 100644 apps/landing/src/content/changelog/1.2.0.md create mode 100644 apps/landing/src/content/changelog/1.3.0.md create mode 100644 apps/landing/src/content/changelog/1.4.0.md create mode 100644 apps/landing/src/content/changelog/1.5.0.md create mode 100644 apps/landing/src/content/changelog/1.6.0.md create mode 100644 apps/landing/src/content/changelog/1.7.0.md create mode 100644 apps/landing/src/content/changelog/1.8.0.md create mode 100644 apps/landing/src/content/changelog/1.9.0.md create mode 100644 apps/landing/src/pages/changelog/index.astro create mode 100644 apps/landing/src/pages/changelog/rss.xml.ts diff --git a/apps/landing/src/components/SiteFooter.tsx b/apps/landing/src/components/SiteFooter.tsx index 4a82c3d..8b1e03e 100644 --- a/apps/landing/src/components/SiteFooter.tsx +++ b/apps/landing/src/components/SiteFooter.tsx @@ -23,6 +23,9 @@ export function SiteFooter() { Blog + + Changelog + Mentions légales diff --git a/apps/landing/src/content.config.ts b/apps/landing/src/content.config.ts new file mode 100644 index 0000000..f3827ad --- /dev/null +++ b/apps/landing/src/content.config.ts @@ -0,0 +1,31 @@ +import { defineCollection, z } from "astro:content"; +import { glob } from "astro/loaders"; + +/** + * Collection `changelog` — une entrée Markdown par version livrée. + * + * Versionné dans le repo : pas de DB, pas d'admin. Pour ajouter une entrée + * lors d'une release, on crée `apps/landing/src/content/changelog/.md` + * avec le frontmatter ci-dessous + un body markdown court qui raconte le + * pourquoi (le quoi est déjà dans `highlights`). + * + * Le toast SPA (apps/web) lit la version courante via `apps/web/src/version.ts` + * — pensez à le bumper en même temps que vous ajoutez le .md. + */ +const changelog = defineCollection({ + loader: glob({ pattern: "**/*.md", base: "./src/content/changelog" }), + schema: z.object({ + /** Version sémantique, ex. "1.4.0". Sert d'ancre URL : `/changelog#1.4.0`. */ + version: z.string().regex(/^\d+\.\d+\.\d+$/, "Format attendu : x.y.z"), + /** Date de release. ISO ou string YYYY-MM-DD. */ + date: z.coerce.date(), + /** Titre court visible dans la carte (≤ 60 caractères idéal). */ + title: z.string().min(3).max(80), + /** Type pour la pastille colorée — feature (rubis), improvement (muted), fix (line). */ + type: z.enum(["feature", "improvement", "fix"]), + /** 1 à 8 bullets de surface — affichés dans la carte avant le body markdown. */ + highlights: z.array(z.string()).min(1).max(8), + }), +}); + +export const collections = { changelog }; diff --git a/apps/landing/src/content/changelog/1.0.0.md b/apps/landing/src/content/changelog/1.0.0.md new file mode 100644 index 0000000..670d3a8 --- /dev/null +++ b/apps/landing/src/content/changelog/1.0.0.md @@ -0,0 +1,28 @@ +--- +version: "1.0.0" +date: 2026-04-10 +title: "Lancement de Rubis" +type: feature +highlights: + - "Glissez-déposez vos factures, l'OCR Mistral AI fait le reste" + - "4 plans de relance prêts (Standard B2B, Rapide, Patient, Ferme)" + - "Confirmations par email avant chaque relance" + - "Compteur rubis : 1 rubis = 10 minutes libérées" + - "Dashboard avec KPIs : à relancer, encaissé, DSO" + - "Mode démo sans engagement, sans carte bancaire" + - "Stripe + 30 jours gratuits" + - "Tout fonctionne sur mobile" +--- + +Aujourd'hui Rubis est ouvert au public. + +L'idée est simple : vos factures impayées se relancent toutes seules pendant que vous travaillez. Vous déposez la facture, vous choisissez un plan, et c'est tout. + +Quelques choix qu'on assume dès le départ : + +- **Le ton monte avec le retard, jamais avant.** Pas d'agressivité par défaut. +- **L'OCR tourne sur Mistral AI**, donc tout est hébergé en France. Vos données ne quittent pas l'Europe. +- **La mise en demeure passe toujours par votre validation manuelle.** On ne joue pas avec la relation client. +- **L'unité dans l'app, c'est le rubis**, pas le DSO. 1 rubis = 10 minutes que vous n'avez pas passées à relancer. + +30 jours gratuits, sans carte bancaire, pour démarrer. diff --git a/apps/landing/src/content/changelog/1.1.0.md b/apps/landing/src/content/changelog/1.1.0.md new file mode 100644 index 0000000..0765368 --- /dev/null +++ b/apps/landing/src/content/changelog/1.1.0.md @@ -0,0 +1,14 @@ +--- +version: "1.1.0" +date: 2026-04-13 +title: "Saisie manuelle des factures" +type: feature +highlights: + - "Créez une facture au clavier en quelques secondes" + - "Pratique pour les avoirs, les factures perdues, ou les PDF que l'OCR n'arrive pas à lire" + - "Tous les champs en saisie libre, vous gardez le contrôle" +--- + +L'OCR couvre l'écrasante majorité des cas, mais pas tous. Un PDF mal scanné, une facture papier qu'on n'a plus, un avoir qu'on tape rapidement — il fallait un fallback propre. + +Vous renseignez le client, le numéro, le montant, l'échéance. C'est exactement la même facture pour Rubis qu'une facture importée — mêmes plans de relance, mêmes confirmations, même timeline. diff --git a/apps/landing/src/content/changelog/1.10.0.md b/apps/landing/src/content/changelog/1.10.0.md new file mode 100644 index 0000000..ba6d7f0 --- /dev/null +++ b/apps/landing/src/content/changelog/1.10.0.md @@ -0,0 +1,14 @@ +--- +version: "1.10.0" +date: 2026-05-08 +title: "Le client est remercié, automatiquement" +type: feature +highlights: + - "Quand vous validez « Payée », un mot court part au client" + - "Optionnel, configurable depuis vos settings" + - "Un client remercié est un client qui revient" +--- + +Vous validez « Payée » dans Rubis, et Rubis envoie un mot bref au client : « Merci, paiement bien reçu. Belle journée. » C'est court, c'est sincère, c'est optionnel. + +Quelques utilisateurs en bêta nous ont dit que ce simple geste valait un petit cadeau de fin d'année. On l'a poussé en prod par défaut activé, désactivable en deux clics. diff --git a/apps/landing/src/content/changelog/1.2.0.md b/apps/landing/src/content/changelog/1.2.0.md new file mode 100644 index 0000000..f6c14a9 --- /dev/null +++ b/apps/landing/src/content/changelog/1.2.0.md @@ -0,0 +1,14 @@ +--- +version: "1.2.0" +date: 2026-04-16 +title: "Connexion en un clic avec Google" +type: feature +highlights: + - "SSO via votre compte Google Workspace" + - "Plus de mot de passe à retenir" + - "Création de compte instantanée pour les nouveaux" +--- + +La majorité de nos utilisateurs ont déjà un compte Google Workspace pour leur boîte. Au lieu de leur faire créer un énième mot de passe, on branche directement le SSO. + +Un bouton « Continuer avec Google » sur la page de connexion et le tour est joué. Vos credentials Google ne transitent jamais par nos serveurs — c'est de l'OAuth standard. diff --git a/apps/landing/src/content/changelog/1.3.0.md b/apps/landing/src/content/changelog/1.3.0.md new file mode 100644 index 0000000..64b1c72 --- /dev/null +++ b/apps/landing/src/content/changelog/1.3.0.md @@ -0,0 +1,14 @@ +--- +version: "1.3.0" +date: 2026-04-19 +title: "Connexion en un clic avec Microsoft" +type: feature +highlights: + - "SSO via votre compte Microsoft 365" + - "Pour les boîtes côté Outlook plutôt que Gmail" + - "Création de compte instantanée" +--- + +Beaucoup de TPE-PME tournent sur Microsoft 365. On a ajouté le même flow que pour Google : un bouton « Continuer avec Microsoft » sur la page de connexion, et c'est parti. + +Même principe qu'avec Google — vos credentials Microsoft restent chez Microsoft, on récupère juste votre email pour créer le compte côté Rubis. diff --git a/apps/landing/src/content/changelog/1.4.0.md b/apps/landing/src/content/changelog/1.4.0.md new file mode 100644 index 0000000..b7c3765 --- /dev/null +++ b/apps/landing/src/content/changelog/1.4.0.md @@ -0,0 +1,17 @@ +--- +version: "1.4.0" +date: 2026-04-22 +title: "Plans de relance sur mesure" +type: feature +highlights: + - "Les 4 plans par défaut ne suffisent pas toujours — créez les vôtres" + - "Cadence à la carte : J+3, J+7, J+15, J+30, comme vous voulez" + - "Choix du ton à chaque étape : cordial, ferme, sérieux" + - "Aucune limite sur le nombre d'étapes" +--- + +Standard B2B, Rapide, Patient, Ferme — les 4 plans fournis couvrent l'essentiel. Mais chaque secteur a ses habitudes. Un cabinet de conseil ne relance pas comme un fournisseur d'imprimerie. + +L'éditeur de plan vous laisse construire le vôtre : chaque étape a sa date relative à l'échéance, son ton, son intention. Vous nommez le plan, vous le sauvegardez, vous l'appliquez à vos factures. + +Les plans personnalisés cohabitent avec les plans par défaut. Vous pouvez les dupliquer, les modifier, les archiver. diff --git a/apps/landing/src/content/changelog/1.5.0.md b/apps/landing/src/content/changelog/1.5.0.md new file mode 100644 index 0000000..7f25a3c --- /dev/null +++ b/apps/landing/src/content/changelog/1.5.0.md @@ -0,0 +1,14 @@ +--- +version: "1.5.0" +date: 2026-04-25 +title: "Templates d'email avec variables" +type: feature +highlights: + - "Tous les textes d'email sont éditables, dans tous les plans" + - "Variables disponibles : `{{client.nom}}`, `{{facture.montant}}`, `{{facture.numero}}`, `{{retard.jours}}`" + - "Aperçu en temps réel avec les vraies valeurs de votre prochaine relance" +--- + +Les modèles d'email fournis par défaut sont écrits par des gens qui connaissent le sujet. Mais votre voix, c'est la vôtre. + +Chaque étape de chaque plan a son template éditable. Les variables sont remplacées au moment de l'envoi par les vraies valeurs de la facture en cours. Pas de surprise : un aperçu vous montre le résultat avec votre prochaine facture en attente. diff --git a/apps/landing/src/content/changelog/1.6.0.md b/apps/landing/src/content/changelog/1.6.0.md new file mode 100644 index 0000000..a628ac8 --- /dev/null +++ b/apps/landing/src/content/changelog/1.6.0.md @@ -0,0 +1,16 @@ +--- +version: "1.6.0" +date: 2026-04-28 +title: "Réécrivez vos relances avec l'IA" +type: feature +highlights: + - "Un bouton sur chaque template : « Reformule plus ferme », « plus chaleureux », « plus court »" + - "L'IA propose, vous validez, vous gardez la main" + - "Basée sur Mistral AI, hébergée en France" +--- + +Réécrire un email plus ferme sans devenir agressif, ou plus chaleureux sans être obséquieux — c'est un exercice subtil. On a branché un assistant IA sur l'éditeur de templates : un bouton, une consigne, une nouvelle version. + +Vous gardez toujours le dernier mot. La proposition de l'IA s'affiche à côté de votre texte original — vous adoptez, vous mixez, vous ignorez. + +Comme l'OCR, l'IA tourne sur Mistral, donc en France. Aucune relance n'est envoyée à un provider américain. diff --git a/apps/landing/src/content/changelog/1.7.0.md b/apps/landing/src/content/changelog/1.7.0.md new file mode 100644 index 0000000..dd590a3 --- /dev/null +++ b/apps/landing/src/content/changelog/1.7.0.md @@ -0,0 +1,15 @@ +--- +version: "1.7.0" +date: 2026-05-01 +title: "Insights et graphiques" +type: feature +highlights: + - "Évolution de votre DSO sur 6 mois" + - "Rubis cumulés par mois, et au total depuis le début" + - "Taux d'encaissement par client et par plan de relance" + - "Détection des plans qui marchent moins bien sur vos clients" +--- + +Avant, le dashboard montrait l'instant T. On a ajouté l'histoire : votre DSO d'il y a 3 mois, vos rubis du mois dernier, votre taux d'encaissement par segment. + +Vous repérez en un coup d'œil les plans qui sous-performent, les clients qui demandent un suivi serré, et les mois où vous avez vraiment gagné du temps. diff --git a/apps/landing/src/content/changelog/1.8.0.md b/apps/landing/src/content/changelog/1.8.0.md new file mode 100644 index 0000000..2ea20a4 --- /dev/null +++ b/apps/landing/src/content/changelog/1.8.0.md @@ -0,0 +1,14 @@ +--- +version: "1.8.0" +date: 2026-05-04 +title: "Le blog des relances qui marchent" +type: feature +highlights: + - "`rubis.pro/blog` — bonnes pratiques, modèles d'email, retours du terrain" + - "Un nouvel article par semaine, sans bullshit" + - "Tout est gratuit, RSS dispo pour les power users" +--- + +On entend les mêmes questions revenir : comment relancer un client qui ignore les emails ? Quel ton au J+30 ? Et la mise en demeure, légalement, c'est encadré comment ? + +On a ouvert un blog pour y répondre. Pas de listicles, pas d'AIDA, juste des articles utiles écrits par des gens qui ont gratté le sujet. diff --git a/apps/landing/src/content/changelog/1.9.0.md b/apps/landing/src/content/changelog/1.9.0.md new file mode 100644 index 0000000..666c571 --- /dev/null +++ b/apps/landing/src/content/changelog/1.9.0.md @@ -0,0 +1,14 @@ +--- +version: "1.9.0" +date: 2026-05-06 +title: "Marque blanche (plan Business)" +type: feature +highlights: + - "Vos relances partent avec votre logo, vos couleurs, votre signature" + - "Aucune mention « envoyé via Rubis » dans le footer des emails" + - "Disponible sur le plan Business" +--- + +Vos clients n'ont pas à savoir que vos relances sont automatisées. Sur le plan Business, tout est marqué à votre image : logo dans l'en-tête, couleur principale sur les liens, signature personnalisable, et bien sûr, votre nom comme expéditeur. + +Le footer « envoyé via Rubis » disparaît. C'est votre marque, du début à la fin. diff --git a/apps/landing/src/layouts/Layout.astro b/apps/landing/src/layouts/Layout.astro index 048d6ba..c00e76a 100644 --- a/apps/landing/src/layouts/Layout.astro +++ b/apps/landing/src/layouts/Layout.astro @@ -133,8 +133,10 @@ const jsonLdArray = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : []; - {/* RSS auto-discovery */} + {/* RSS auto-discovery — exposés sur toutes les pages pour que les + lecteurs RSS détectent les deux flux quel que soit le point d'entrée. */} + {/* JSON-LD structured data */} { diff --git a/apps/landing/src/pages/changelog/index.astro b/apps/landing/src/pages/changelog/index.astro new file mode 100644 index 0000000..8640f69 --- /dev/null +++ b/apps/landing/src/pages/changelog/index.astro @@ -0,0 +1,337 @@ +--- +/** + * /changelog — Liste reverse-chrono des versions livrées. + * + * Stratégie de rendu : `prerender = true` — le contenu vient de fichiers .md + * versionnés dans le repo (cf. `src/content.config.ts`), donc tout est figé au + * build. À chaque release, on bumpe `apps/web/src/version.ts` + on ajoute un + * nouveau .md dans `src/content/changelog/`, puis le redéploiement régénère + * le HTML. + * + * Design : + * - Hero centré, eyebrow rubis + h1 display + * - 2 colonnes desktop : feed cartes (gauche) + sticky rail (droite) + * - 1 colonne mobile/tablette : pas de rail, juste le feed + * - Anchors `#1.4.0` par version → partageables, scrollables + * - JSON-LD : un `WebPage` avec `mainEntity` listant chaque release comme + * `TechArticle`. Rich snippets dans Google. + */ +export const prerender = true; + +import Layout from "../../layouts/Layout.astro"; +import { getCollection, render } from "astro:content"; + +const entries = (await getCollection("changelog")).sort( + (a, b) => b.data.date.getTime() - a.data.date.getTime(), +); + +const rendered = await Promise.all( + entries.map(async (entry) => { + const { Content } = await render(entry); + return { data: entry.data, Content }; + }), +); + +const dateLong = new Intl.DateTimeFormat("fr-FR", { + day: "numeric", + month: "long", + year: "numeric", +}); +const dateShort = new Intl.DateTimeFormat("fr-FR", { + day: "2-digit", + month: "short", +}); + +const typeLabel: Record = { + feature: "Nouveauté", + improvement: "Amélioration", + fix: "Correction", +}; + +const title = "Changelog — ce qui change dans Rubis"; +const description = + "Toutes les nouveautés, améliorations et corrections livrées sur Rubis. Régulier, transparent, sans superlatifs."; + +const jsonLd = { + "@context": "https://schema.org", + "@type": "WebPage", + name: title, + description, + url: "https://rubis.pro/changelog", + publisher: { + "@type": "Organization", + name: "Rubis sur l'ongle", + url: "https://rubis.pro", + }, + mainEntity: entries.map((e) => ({ + "@type": "TechArticle", + headline: e.data.title, + datePublished: e.data.date.toISOString(), + url: `https://rubis.pro/changelog#${e.data.version}`, + version: e.data.version, + })), +}; +--- + + + {/* ============ Hero ============ */} +
+
+

+ + Tout ce qui change +

+

+ Changelog +

+

+ Les nouveautés, les améliorations, les corrections livrées sur Rubis. + Régulier, transparent, sans superlatifs. +

+ +
+
+ + {/* ============ Body : feed + rail ============ */} +
+
+ {/* Feed des versions */} +
+ { + rendered.length === 0 ? ( +
+

Aucune entrée publiée pour l'instant.

+
+ ) : ( +
    + {rendered.map(({ data, Content }) => ( +
  1. + {/* Header : chip version + type + date */} +
    + + v{data.version} + + + {typeLabel[data.type]} + + +
    + + {/* Titre */} +

    + {data.title} +

    + + {/* Highlights — bullets losanges rubis */} +
      + {data.highlights.map((h) => ( +
    • + + $1')} /> +
    • + ))} +
    + + {/* Body markdown — narrative courte */} +
    + +
    +
  2. + ))} +
+ ) + } +
+ + {/* Sticky rail — jump nav versions, desktop only */} + +
+
+ + {/* Active state du rail via IntersectionObserver — vanilla JS, inline */} + + + +
diff --git a/apps/landing/src/pages/changelog/rss.xml.ts b/apps/landing/src/pages/changelog/rss.xml.ts new file mode 100644 index 0000000..66d5212 --- /dev/null +++ b/apps/landing/src/pages/changelog/rss.xml.ts @@ -0,0 +1,77 @@ +import type { APIRoute } from "astro"; +import { getCollection } from "astro:content"; + +const SITE = "https://rubis.pro"; + +function escapeXml(s: string): string { + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function toRfc2822(d: Date): string { + return d.toUTCString(); +} + +/** + * Flux RSS 2.0 du changelog — toutes les versions livrées en reverse-chrono. + * Le titre + les highlights sont concaténés dans `` pour que les + * lecteurs RSS aient le résumé complet sans avoir à fetch la page. + * + * Auto-discovered depuis /changelog via . + * Prerendered au build comme la page parente. + */ +export const prerender = true; + +export const GET: APIRoute = async () => { + const entries = (await getCollection("changelog")).sort( + (a, b) => b.data.date.getTime() - a.data.date.getTime(), + ); + + const lastBuild = + entries[0]?.data.date + ? toRfc2822(entries[0].data.date) + : new Date().toUTCString(); + + const items = entries + .map((e) => { + const url = `${SITE}/changelog#${e.data.version}`; + // Description = titre + highlights bulletés, pour lecteurs RSS textuels. + const desc = + `${e.data.title}

` + + `
    ${e.data.highlights.map((h) => `
  • ${h}
  • `).join("")}
]]>`; + return ` + v${escapeXml(e.data.version)} — ${escapeXml(e.data.title)} + ${escapeXml(url)} + ${escapeXml(url)} + ${toRfc2822(e.data.date)} + ${desc} + ${escapeXml(e.data.type)} + `; + }) + .join("\n"); + + const xml = ` + + + Rubis — Changelog + ${SITE}/changelog + + Toutes les nouveautés, améliorations et corrections livrées sur Rubis. + fr-FR + ${lastBuild} +${items} + +`; + + return new Response(xml, { + status: 200, + headers: { + "Content-Type": "application/xml; charset=utf-8", + "Cache-Control": "public, max-age=300, stale-while-revalidate=86400", + }, + }); +};