19 — Style : Mantine et CSS modules
Statut : fait · Phase : transverse · Mis à jour : 30/10/2026.
Décidé le 29/10/2026. Tout le style de Récit passe sous Mantine avec une seule forme de style
dans le code : un fichier *.module.css à côté de chaque composant. Plus aucune classe
utilitaire écrite dans le JSX, plus aucun style={…} dans le JSX, plus de Tailwind, plus de
shadcn/ui, plus de Radix. Le plan commence par l'inventaire complet de ce qui existe (§1), fixe la
cible (§2) et la loi (§0.3), découpe le travail en lots exécutables par plusieurs agents (§10), et
encadre le tout par des captures d'écran avant/après de chaque page et de chaque composant (§7). Ce
plan ne s'implémente pas avant la demande explicite de l'utilisateur, et s'exécute sur une
branche à part (§11).
0. Décision, portée et lecture de la demande
0.1 La demande, telle qu'elle est lue
- « Aucune classe en inline, tout mon style doit être fait en Mantine » : les chaînes de classes
utilitaires (
className="flex items-center gap-2") disparaissent du JSX ; les composants d'interface viennent de Mantine ; leur apparence est réglée par le thème Mantine et par des modules CSS. - « Que du
module.css, aucun style dans du JSX » : la seule façon de styler un composant est son module, importé enclasses;style={{…}},style={variable},cn(),cva(), et les props de style de Mantine (p,m,c,bg,style,styles) sont interdits. - « Reprendre toutes les dépendances à shiku » : lu comme la pile d'interface héritée du
framework d'origine et de shadcn/ui (Tailwind, Radix,
cn/cva,tw-animate-css,next-themes), à reprendre et à remplacer ; et, pour servir l'autre lecture possible du mot, Shiki est traité aussi : ses thèmes et ses variables CSS (--shiki-light,--shiki-dark…) doivent suivre le schéma de couleur de Mantine (§2.8). - « Bien lister tous les usages avant, vérifier tous les screenshots pour avoir du comparatif avant/après » : §1 et §7.
- « Créer des SKILL, revoir les agents, la typo » : §9 et §2.6.
- « Sur une branche à part » : §11. La règle globale interdit de créer une branche sans demande explicite : c'est l'utilisateur qui la demande, le plan dit seulement comment elle vit.
0.2 Ce que « style » recouvre ici
| Domaine | Aujourd'hui | Après |
|---|---|---|
| CSS | Tailwind v4 (@apply, @theme, utilitaires), CSS global, objets inline | Modules CSS par composant, CSS global limité à @recit/theme et à Mantine |
| Tokens | Variables HSL de @recit/theme/tokens.css, variables oklch shadcn du site | Un seul jeu de variables, exposé à Mantine par cssVariablesResolver (§2.4) |
| Typographie | @tailwindcss/typography (prose), Heading en cva, Geist, Inter | TypographyStylesProvider, theme.headings, mêmes polices (§2.6) |
| Icônes | Lucide (lucide-react) | Lucide, inchangé : Mantine n'embarque pas d'icônes (§2.7) |
| Animation | motion/react (deck, navigation), tw-animate-css, Remotion | motion/react gardé ; Transition de Mantine pour l'interface ; Remotion |
| Mode de couleur | next-themes + classe .dark sur <html> | ColorSchemeScript + data-mantine-color-scheme (§2.5) |
| Thème public | Section theme: du recit.config (couleurs, polices, rayon) | Même schéma, interprété par createTheme (§2.4) |
0.3 La loi
Une seule règle, applicable par un script (§8) et par les agents (§9). Elle porte sur tout fichier
.ts/.tsx de packages/, sites/*/src, sites/storybook/.storybook et examples/*/components.
Permis.
className={classes.x}oùclassesvient du moduleX.module.cssplacé à côté deX.tsx; plusieurs classes se composent dans le module (composes:), jamais par concaténation dans le JSX. Un seul cas de concaténation est admis : une classe d'état choisie par condition,className={open ? classes.open : classes.closed}, sans chaîne littérale.- Un composant Mantine avec ses props sémantiques du thème :
variant,size,radius,color(un nom de couleur du thème, jamais une valeur),justify,align,gapsur les composants de mise en page (Group,Stack,Flex,Grid,SimpleGrid) parce qu'ils sont l'équivalent sémantique d'un module de mise en page. Tout le reste passe parclassNames. - L'API
classNamesde Mantine branchée sur un module :classNames={{ root: classes.root, label: classes.label }}. - Une valeur dynamique passe par une variable CSS lue dans le module, posée de l'une de ces
trois façons et d'aucune autre : (a) un attribut
data-*pour un ensemble fini de valeurs (data-columns="3",data-level="2"), (b) levarsd'un composant Mantine réglé dans le thème (Button.extend({ vars })dans le fichier de thème, pas dans le JSX), (c) pour une valeur continue calculée au vol (échelle d'une diapositive, position d'un indicateur), le hookuseCssVariable(ref, "--name", value)fourni par@recit/site/hookset par@recit/components, qui écrit la propriété sur l'élément dans un effet. Ce hook est le seul chemin impératif. Sa limite : un effet s'exécute après l'hydratation, donc une valeur nécessaire au premier rendu ne peut pas passer par lui. Les sites inline de §1 sensibles au premier rendu et leur chemin : l'échelle et la taille deSlideStage→ calcul CSS (container-type: sizesur le viewport du deck,aspect-ratiodepuisdata-aspect, ensemble fini16:9/4:3, etscale: min(tan(atan2(100cqw, var(--deck-design-width))), tan(atan2(100cqh, var(--deck-design-height)))), les dimensions de design étant émises parbuildThemeCssdans le<style>de données ; ou, plus simple, une diapositive dimensionnée encqw/cqhsans mise à l'échelle) ; le fond deSlideFrame→ variable par scène émise parbuildThemeCss([data-scene=\"…\"] { --slide-bg: … }, lue par le module) ; les{width, height}du mode impression → les mêmes variables de design ;--columnsduFooter→data-columns(1 à 6) ; la barre de progression → le propvaluedeProgress(Mantine écrit sa propre variable) ; l'indicateur deToc→useCssVariable, parce qu'il n'existe qu'après le défilement. Les autres (Header,Banner,BrowserWindow,LanguageIcon,SidebarToggle,NavigationWrapper,Callout) sont classés à la lecture par STY4 : fini →data-*, continu → calcul CSS, après interaction → hook ; aucun ne reste inline. - Le CSS global n'existe qu'à trois endroits : les feuilles de Mantine
(
@mantine/core/styles.layer.css,@mantine/spotlight/styles.layer.css),@recit/theme(tokens.css,fonts.css,deck.css,page.css,print.css: les tokens et le contrat de classes sémantiques du HTML compilé, §5.4 ;reset.css, le preflight que compilait Tailwind, partagé par les sites, les decks et Storybook depuis STY5c), les deux feuilles de base du framework (packages/site/src/app/base.css: reset, variables de mise en page,html/body, barre de défilement, STY5b et STY5c ;app/defaultTheme.css: les jetons par défaut que sautetheme.noDefaultTheme) et une feuille de base par site (sites/*/styles.css) réservée aux classes qu'une page MDX écrit elle-même. - Le
<style>qui injectestory.themeCss(packages/integrations/site-stories/src/StoryViewer.tsx:138,StoryDocument.tsx:49,mdx-components.tsx:101) et le<style id="recit-theme">du gabarit de deck (packages/cli/src/render/html-template.ts) restent : ce sont des données (les variables d'un récit), pas du style de composant.
Interdit.
- Toute chaîne littérale dans
className(guillemets ou gabarit de chaîne), y compris une classe sémantique dedeck.css: un composant qui veut la classeslide-cardécrit dans son module.card { composes: slide-card from global; }et utiliseclasses.card. cn,clsx,twMerge,cva,class-variance-authority,tailwind-merge.style={{…}}etstyle={variable}dans le JSX,style:dans uncreateElement, le propstyle,styles,sx,varset les props de style de Mantine (p,px,m,mt,c,bg,fz,fw,w,h,maw,pos,display,ta,lh,bd,opacity…).@apply,@theme,@custom-variant,@source,@plugin,@utilityet tout import detailwindcss.<style>inline etdangerouslySetInnerHTMLportant du CSS, hors les deux cas de données ci-dessus.
Exceptions, nommées et limitées.
| Exception | Pourquoi | Où |
|---|---|---|
La couche d'animation des blocs motion : un objet style calculé par image est fusionné dans l'élément | Une valeur par image (Remotion) ou par pas (Framer) ne peut pas vivre dans un module statique | packages/formats/motion-blocks/src/engine.tsx (StaticAnim, FramerAnim, AnimContext), packages/formats/motion-render/src/RemotionAnim.tsx, packages/formats/motion-blocks/src/player.tsx:158-226, src/ui/render.tsx (valeurs échantillonnées par temps de scène). Liste d'exclusion de §8, fichier par fichier |
| Les variables de thème d'un film posées sur la racine de la scène | Le thème d'un film arrive par inputProps (Remotion), pas par une feuille : il doit devenir des variables CSS une fois, à la racine | Un composant ThemeVars unique dans motion-blocks (§5.6), dans la même liste d'exclusion |
Les deux <style> de données | Variables d'un récit, générées par buildThemeCss | §0.3 permis 6 |
Ce que Mantine écrit lui-même (style interne de Progress, Grid, Transition) | C'est le code de Mantine, pas le nôtre ; la loi porte sur notre JSX | — |
Tout ce qui n'est pas dans ce tableau est un module.
0.4 Non-objectifs
- Aucun changement de contenu, de navigation, de routes ni de comportement : la migration est iso-fonctionnelle ; une différence visuelle est acceptée consciemment dans la galerie (§7), jamais subie.
- Pas de refonte graphique (nouvelle palette, nouveaux espacements) : les tokens actuels sont reportés tels quels dans le thème Mantine. Une refonte, si on la veut, est un plan suivant.
- Pas de changement du schéma public
theme:durecit.configni des classes sémantiques que@recit/corecompile dans le HTML des diapositives (packages/core/src/compile/rehype-plugins.ts,src/parser/unified-pipeline.ts) : les récits existants restent valides. - Le rendu MP4 (
recit render, Remotion) garde son moteur ; seule la partie statique de ses styles change (§5.6).
1. Inventaire avant
Relevés du 29/10/2026 sur main (commit « Claude limit » et suivants), par trois agents en lecture
seule. Les chiffres sont la référence « avant » ; §8 les fait descendre à zéro, lot par lot.
1.1 Le framework de site (packages/site)
| Mesure | Valeur |
|---|---|
Occurrences de className | 947, dans 110 des 171 fichiers .tsx hors tests |
className={cn(…)} / appels cn( / cva( | 219 / 227 / 14 (9 fichiers ui/, Heading.tsx, Framed.tsx) |
style={{…}} et style={var} | 17 au total (PageProgress, LanguageIcon ×2, Header ×2, Banner, BrowserWindow ×2, Footer ×3 --columns, SidebarToggle, Toc, NavigationWrapper, Callout ×4 --callout-color, ToggleGroup --gap, Progress) |
Variantes dark: / prose / classes animate-* et data-state | 62 (21 fichiers) / 18 / 36 |
| Modules CSS | 0 |
| Chaînes de classes hors JSX | HIGHLIGHT_CODE_BLOCK_CLASS (src/lib/shiki-constants.ts), ProseClasses (src/lib/components/Typography.tsx:5) |
Composants ui/ (export ./ui/*, src/lib/ui/*.tsx) | 54 + util.tsx ; 27 sans aucun importeur |
| Paquets Radix déclarés | 21 (react-slot importé par 7 fichiers, react-dialog 3, react-visually-hidden 3, collapsible/label/tabs 2, 15 autres par 1 ; react-toggle et react-toggle-group déclarés, jamais importés) |
| Autres bibliothèques d'interface | @base-ui/react (Toggle, ToggleGroup), cmdk, vaul, embla-carousel-react, next-themes, @tanem/react-nprogress, react-hook-form, motion (navigation) |
| Tailwind | tailwindcss, @tailwindcss/vite, @tailwindcss/typography, tw-animate-css ; configuré en CSS seul (src/app/main.css, 471 lignes : @theme inline, @utility typography, @utility prose, @layer base, @layer utilities, 40 @apply) ; defaultTheme.css (99 lignes, zinc oklch) |
| Thème | src/vite/plugin-theme.ts (394 lignes) émet virtual:recit-theme.css : polices Google (liste GOOGLE_FONTS, 25 familles), registre shadcn (src/vite/shadcn-registry.ts), :root/.dark depuis theme.light/theme.dark (20 clés), --font-*, --shiki-* |
| Mode sombre | next-themes <ThemeProvider attribute="class"> (src/lib/components/SiteRoot.tsx:75) ; aucun script de tête maison ; ThemeSwitch.tsx |
| SSR | src/vite/css/plugin.ts + collect.ts : parcourt le graphe SSR et garde isCSSRequest(url) ; src/vite/build.ts getEagerCssEntries ; aucun CSS critique |
| Polices | @fontsource-variable/geist et geist-mono dans main.css:1-2 ; défauts "Geist Variable" / "Geist Mono Variable" |
Les quinze fichiers les plus chargés : LandingPage 34, ui/DropdownMenu 31, ui/Command 30,
plugins/markdown/MdxPage 27, ui/Select 25, ui/NavigationMenu 25, ui/Item 25,
ui/AlertDialog 25, ui/Pagination 19, ui/Dialog 19, ui/Carousel 19, ui/Card 19,
components/Header 19, navigation/NavigationItem 18, MobileTopNavigationDrawer 18.
Par dossier (className littéral / cn( / style) : lib/ui 79 / 173 / 6, lib/components 166 /
41 / 11 (dont navigation/ 41 / 15 / 2), lib/plugins/markdown 20 / 4 / 0,
lib/plugins/search-pagefind 19 / 0 / 0, lib/errors 3 / 1 / 0, app 1 / 0 / 0. Les dossiers
cli, vite, config, lib/core, lib/hooks, lib/navigation n'ont rien.
Le seul consommateur de @recit/site/ui hors du framework est
packages/tools/site-kit/src/components/SourceFile.tsx (SyntaxHighlight). Aucun site ni exemple
ne l'importe.
1.2 Les paquets Récit
| Paquet | Mécanisme | className=" | className={ | style={{ | style={v} | style: { | CSS |
|---|---|---|---|---|---|---|---|
components (11 tsx, 9 stories) | Tailwind dans le JSX + classes sémantiques de deck.css (slide-card, slide-callout-*…) | 26 | 21 | 2 | 0 | 0 | 0 |
theme | CSS global : tokens.css 67, deck.css 951, page.css 17, print.css 51, fonts.css 39, tailwind.css 22 (pont @theme inline) | – | – | – | – | – | 6 fichiers |
formats/deck (13 tsx) | Sémantique (deck-*, slide-*, print-*) + Tailwind dans PresenterView (26/28 chaînes, palette slate codée en dur), MobileRemoteView, RemoteQrCodeModal, AnnotationCanvas ; inline pour échelle et tailles | 75 | 8 | 12 | 1 | 0 | 0 |
formats/motion-blocks (13 tsx) | Inline seulement : src/styles.ts makeStyles(theme) → CSSProperties ; src/ui/render.tsx 1 490 lignes | 0 | 0 | 27 | 31 | 60 | 0 |
formats/motion-render (5 tsx) | Inline (Film.tsx:30) ; le bundle Remotion n'importe que @recit/theme/fonts.css (src/entry.tsx:4) | 0 | 0 | 1 | 0 | 1 | 0 |
integrations/site-stories (8) | src/site-stories.css 293 lignes (classes recit-wrapper, recit-embed, recit-gallery…) compilée par scripts/build-css.mjs avec @tailwindcss/node et un plugin PostCSS qui préfixe tout par .recit-wrapper ; <style>{story.themeCss}</style> | 25 | 6 | 1 | 1 | 0 | 1 |
tools/site-kit (6, privé) | Tailwind dans le JSX, compilé par le site consommateur ; Tutorial.tsx utilise prose prose-sm dark:prose-invert | 24 | 1 | 0 | 0 | 0 | 0 |
cli | src/bundle/css.ts : @import "tailwindcss" + @recit/theme/{tokens,tailwind,page,deck,print}.css + @source sur @recit/components et @recit/deck + customCss, puis KaTeX et polices ; src/bundle/js.ts : esbuild, loader pour médias seulement ; src/render/html-template.ts : script de démarrage .dark, <style id=THEME_STYLE_ID> ; src/motion/client.tsx objet styles | 0 | 0 | 2 | 8 | 0 | 0 |
core | Pas d'interface ; 30 className: sont des nœuds hast des plugins rehype qui émettent des classes dans le HTML compilé | – | – | – | – | – | – |
Aucun module CSS, aucun CSS-in-JS, aucun cn/cva hors du framework. @recit/theme déclare
tailwindcss en peer optionnel (packages/theme/package.json:51-57). Le thème est un objet
RecitTheme (packages/theme/src/types.ts : 11 tokens HSL COLOR_TOKENS, polices, data,
motion), deux thèmes nommés warm-editorial et saas-product ; buildThemeCss
(packages/core/src/config/theme-css.ts) produit story.themeCss ; le deck lit les variables, le
motion passe par toMotionTheme() et les inputProps de Remotion.
1.3 Sites et exemples
Tous les sites passent par createSiteConfig (packages/tools/site-kit/src/config.tsx), qui fixe
theme.light.primary #7c3aed et le dark ; aucun site ne déclare de polices ; tous sauf storybook
importent @recit/site-stories/styles.css.
| Site / exemple | Composants .tsx et classes | CSS propre | MDX avec classes |
|---|---|---|---|
www | Hero 9, FormatGrid 11 ([&_pre]:max-h-[460px], text-[10px]), OneSource 2 | – | 0 |
docs | StatusBadge 1 | – | 0 |
motion | VideoDownloads 1 | styles.css 24 lignes (.motion-grid, .typography) | 3 (motion-grid) |
components | FormatSupport 1 (emerald, dark:) | src/stories.css 6 lignes (.metric-value) | 1 (media-demo, sans règle CSS) |
presentation | – | styles.css 8 lignes | 0 |
plans, vision | – | – | 0 |
storybook | .storybook/preview.tsx : 2 classes, 2 style (décorateur withSlide) | preview.css : @import "tailwindcss" + thème + @source | – |
site-kit | Cards 9, ExampleHeader 9, Tutorial 6, SourceFile 1 | – | – |
deck-mdx | components/index.tsx : Counter, Timeline, 4 classes | – | – |
deck-showcase | components/index.tsx : Metric, 4 classes (emerald) ; réutilisé par docs, components, presentation, site-i18n | styles.css 10 lignes (customCss) | – |
deck-pitch | – | – (police de titre Georgia) | – |
Aucun public/*.css, aucun style= dans une page MDX.
1.4 Typographie et mode sombre
- Sites :
@tailwindcss/typographyet--tw-prose-*surchargés (main.css:57-108),.proseen@utility(main.css:110-164),Typography.tsx(prose dark:prose-invert typography),Heading.tsxencva(text-4xl font-extrabold…text-md),MdxComponents.tsx: h1–h6 →Heading,a→AnchorLink,imgrounded-lg,table→ScrollableTable,prenot-prose my-4,code→InlineCode/CodeBlock, douze alias deCallout; pas dep. - Decks :
deck.css.slide h13.5rem, h2 2.5rem, h3 1.5rem (L81-97, surcharges L565, L582) ; policesfonts.css(Inter, JetBrains Mono, Space Grotesk, Space Mono, Yeseva One, Josefin Sans). - Mode sombre : la classe
.darksur<html>est lue par 20 fichiers, dontpackages/site/src/vite/plugin-theme.ts,ThemeSwitch.tsx,Header.tsx,Footer.tsx,Mermaid.tsx,search-inkeep/index.tsx,packages/formats/deck/src/hooks/useThemeMode.ts:61,Presentation.tsx,packages/cli/src/render/html-template.ts:34-38(script de démarrage),packages/cli/src/commands/export-pdf.ts,packages/components/src/slides/{CodeBlock,Mermaid}.tsx,tools/mermaid-render.ts,packages/core/src/config/{schema,validate,theme-css}.ts,compile/compile-story.ts:177-255(colorMode→defaultMode),sites/storybook(withThemeByClassName).
1.5 Captures et tests
playwright.config.ts: un projetchromium1280×720,maxDiffPixelRatio: 0.02,animations: "disabled", baselinese2e/__screenshots__/<spec>/<nom>-<platform>.png; 213-linux.pnget 198-darwin.pngdans git ; Docker viascripts/e2e-linux.{js,sh}(mcr.microsoft.com/playwright:v1.64.1-noble,linux/amd64) ;test:e2e:update:linuxseul à écrire une baseline Linux.- Couverture :
deck-render91 (six exemples, clair et sombre),site-docs38,site-components29,site-presentation19,motion-dev11,site-motion11,site-vision6,site-www3,site-embed3,site-i18n1,deck-features1 (console présentateur) ;site-plans,site-app-bundleetaccessibility(axe sur 3 URL) sans capture. - Non couverts : les trois pages
app*de la doc, les 28 pages deplans,/present/bridge, l'index et le récit anglais desite-i18n, le bundle Tauri, les 9 stories de Storybook, toutes les pages de site en mode sombre, tout viewport mobile, la page 404, la boîte de recherche ouverte, l'en-tête et le pied de page isolés,visionsur darwin. - Storybook (
sites/storybook) : addons docs, a11y (test: "error"), themes ; aucun test-runner ni capture. Goldens motion :packages/formats/motion-rendergoldens:check.
1.6 Outillage des agents et conventions
.claude/skills/:e2e-baselines,recit-import. Pas d'agents/.CLAUDE.md(=AGENTS.md) L108 « Core tech : … TailwindCSS … Radix UI », L133-137 « UI : from@recit/site/ui(shadcn/ui) and icons from@recit/site/icons(Lucide) », L177-197 « Bundle Size ». Rien sur le CSS,classNameou le style inline.biome.jsonc:css.parser.tailwindDirectives,noUnknownAtRulesignoreplugin,theme,custom-variant,apply;noImportantStylescoupé pourmain.css;noRestrictedImportsinterdit@recit/sitesaufsite,site-stories,site-kit; aucune règle sur les classes..oxfmtrc.json: les.csssont formatés, un.module.cssle sera aussi ;lefthook.yml:biome lint --writeetpnpm fmtsur les fichiers indexés.NOTICE.md:packages/site/« site framework » d'origine, sans mention de shadcn, Radix ni Lucide ; polices en « Third-party assets ».sites/docs:theme/overview.mdx:38ettheme/custom-css.mdx:4,25parlent de Tailwind.
2. Cible
2.1 Mantine, version et paquets
@mantine/coreet@mantine/hooks9.7.4 (relevé le 29/10 sur le registre ; peerreact ^19.2.0, satisfait). Mantine est sans Emotion depuis la v7 : feuille CSS statique (@mantine/core/styles.css), variables CSS,MantineProvidersans contexte de style, rendu serveur parColorSchemeScript. STY0 vérifie la surface d'API de la v9 (noms declassNames,cssVariablesResolver,Transition,TypographyStylesProvider) avant d'écrire une ligne.postcss-preset-mantine1.18.0 etpostcss-simple-vars7.0.1 :rem(),light-dark(), les mixinslight/dark/smaller-than/larger-than, dans chaque bundler qui compile un module (§2.2).@mantine/spotlight9.7.4 remplacecmdkpour la boîte de recherche (Command.tsx,PagefindSearch.tsx) : même rôle, même paquet éditeur, une dépendance en moins.@mantine/modalsn'est pas pris : unDialogexiste déjà danscore(Modal), et le registre de modales n'est pas un besoin.- Rien d'autre. La règle « une seule stack » (
CLAUDE.md) tient :motion,lucide-react,react-router,@tanstack/react-query,@mdx-js/reactrestent ; Mantine n'apporte ni icônes, ni routeur, ni requêtes.
2.2 PostCSS et bundlers
Quatre compilateurs touchent du CSS ; chacun reçoit la même configuration de modules.
| Où | Compilateur | Après |
|---|---|---|
Site (packages/site/src/vite/plugin.ts) | Vite ; tailwindcss() en dernier plugin (plugin.ts:32) | css.postcss avec postcss-preset-mantine et postcss-simple-vars ; css.modules.generateScopedName: "[name]__[local]" (lisible, sans hachage : le framework est un seul bundle, et un HTML prérendu stable se compare d'une build à l'autre) ; plus de plugin Tailwind |
Deck seul (packages/cli/src/bundle/css.ts, js.ts) | @tailwindcss/node + @tailwindcss/oxide pour le CSS, esbuild pour le JS | css.ts concatène @recit/theme/*.css, @recit/components/styles.css, @recit/deck/styles.css et customCss, sans Tailwind ; esbuild compile nativement les .module.css des composants utilisateur (loader local-css, actif par défaut sur ce suffixe) et js.ts récupère le CSS émis dans outputFiles pour l'ajouter au bundle ; ces composants utilisateur gardent le motif de noms d'esbuild (ils ne sont pas prérendus, aucune capture n'en dépend) |
site-stories (scripts/build-css.mjs) | @tailwindcss/node + PostCSS de préfixage .recit-wrapper | PostCSS seul : tokens préfixés (déjà fait L17-22), deck.css, @recit/components/styles.css, @recit/deck/styles.css, puis son propre site-stories.module.css compilé en classes stables ; plus de scan @source |
Storybook (sites/storybook/.storybook/main.ts, preview.css) | Vite + @tailwindcss/vite dans viteFinal | Vite + la même configuration PostCSS ; preview.css importe la feuille Mantine, @recit/theme/* et les feuilles des paquets |
Remotion (motion-render/src/render.ts:134) | webpack de Remotion | Inchangé : l'entrée importe @recit/motion-blocks/styles.css à côté de @recit/theme/fonts.css |
Modules dans un paquet publié. @recit/components, @recit/deck, @recit/motion-blocks et
@recit/site-stories sont construits par tsdown et consommés par Vite (sites), esbuild (deck
seul) et webpack (Remotion). Pour que chaque consommateur voie la même chose, les modules sont
compilés à la construction du paquet par postcss-modules avec un nom de classe déterministe
et préfixé (recit-<paquet>-<fichier>__<classe>, pas de hachage), en une feuille par paquet
(dist/styles.css, exportée ./styles.css) et une table classes importée par le code
(import classes from "./X.module.css" est réécrit vers la table générée). En dev, la condition
@recit/source fait lire les .module.css par Vite avec le même generateScopedName, donc les
mêmes noms : pas d'écart entre dev et build, pas de collision entre paquets, pas de hachage qui
bouge d'une version à l'autre. C'est ce que STY0 établit par un spike (§10), avec deux options de
repli si tsdown sait le faire seul (plugin rolldown CSS) ou si postcss-modules ne suffit pas.
Décision (STY0b, 29/10). Le spike retient Lightning CSS, ni postcss-modules ni le plugin
rolldown @tsdown/css : c'est le compilateur CSS que Vite 8 embarque déjà, donc une ligne de
catalogue à la version installée (lightningcss: 1.33.0) en devDependency du paquet qui compile
ses modules, et aucune dépendance d'exécution. scripts/build-css-modules.js compile chaque
src/**/*.module.css du paquet avec le motif recit-<paquet>-<Fichier>__[local]
(cssModules.pattern, un préfixe littéral par fichier, pas de hachage ; dashedIdents: false, les
--variables restent globales), lit ses exports et écrit deux choses : la table de classes à
côté du module, X.module.css.ts
(const classes = { root: "recit-components-Kbd__root slide-kbd" } as const;, le
composes: … from global joint au nom local, clés triées, déjà au format oxfmt), et la feuille du
paquet dist/styles.css (exportée ./styles.css, sideEffects: ["**/*.css"]). Le composant
importe la table, pas le CSS : import classes from "./Kbd.module.css.js". C'est ce qui donne
les mêmes noms en dev et en build sans rien demander au bundler du consommateur : sous
@recit/source, Vite lit Kbd.tsx puis Kbd.module.css.ts, un objet JS ordinaire ; tsdown,
esbuild et webpack voient la même chose dans dist ; aucun generateScopedName à aligner dans les
configurations Vite (le mécanisme imaginé ci-dessus n'existe d'ailleurs pas avec le transformateur
Lightning CSS de Vite, qui n'accepte qu'un motif chaîne, sans nom de paquet). Le typage vient de la
table elle-même (as const), il n'y a pas de .d.ts à part : classes.inexistante échoue tsgo.
Câblage : "build": "node ../../scripts/build-css-modules.js && tsdown", le même préfixe pour
typecheck (la table doit exister avant tsgo), et tsdown.config.ts nettoie dist/** sauf
dist/styles.css. Preuves : .recit-components-Kbd__root dans
storybook-static/assets/iframe-*.css (preview.css importe @recit/components/styles.css) et
dans la feuille d'un deck qui charge la feuille par theme.customCss ; le deck seul l'aura quand
packages/cli/src/bundle/css.ts importera @recit/components/styles.css (STY6.1, hors des fichiers
de STY0b). À généraliser et à trancher en STY6.1 : les quatre paquets câblés de la même façon ; pas
de rem() ni de mixin postcss-preset-mantine dans les paquets de format (ils ne chargent pas
Mantine, §3.3, et Lightning CSS ne les connaît pas) ; composes depuis un autre fichier refusé ; la
feuille n'est pas en @layer recit tant que deck.css n'est pas en couche theme (§2.3, STY9) ;
les tables générées vivent dans src/ (à committer comme un .d.ts, ou à ignorer et à régénérer
par build et typecheck) ; un mode --watch pour le dev ; le relais d'un className d'auteur
sur un élément nu ([classes.root, className].join(" "), que la loi ne couvre pas).
Tables générées (STY6.1). Les tables src/**/*.module.css.ts sont committées avec leurs
modules, comme un .d.ts écrit à la main le serait : build et typecheck de chaque paquet les
régénèrent avant tsdown et tsgo, et node scripts/build-css-modules.js --check --all dans
pnpm check (check:other) refuse une table absente, périmée ou orpheline (module disparu) et une
feuille dist/ construite qui ne correspond plus à ses modules ; une feuille pas encore construite
n'est pas une faute (pnpm check précède pnpm build en CI). Un relecteur voit les noms de classes
dans le diff, l'IDE et tsgo typent sans construction préalable, et @recit/source lit la table
sans que Vite compile le module. Les quatre paquets (@recit/components, @recit/deck,
@recit/motion-blocks, @recit/site-stories) sont câblés de la même façon ; site-stories, dont
./styles.css est la feuille d'intégration préfixée .recit-wrapper, écrit ses modules dans
dist/modules.css ("recit": { "cssModulesSheet" } de son package.json) que build-css.mjs
replie dans styles.css avec celles de @recit/components, @recit/deck et
@recit/motion-blocks. --watch recompile à la volée en dev. L'utilisateur committe les tables
avec le lot qui les change.
SSR. packages/site/src/vite/css/collect.ts:43 garde toute URL pour laquelle
isCSSRequest(url) est vrai : un .module.css l'est. Le transform(code, id) de
css/plugin.ts:56-60 mémorise le code transformé ; pour un module, l'ordre des hooks décide s'il
voit le CSS ou la table JS : STY0 le vérifie en dev avec un module dans SiteRoot et corrige le
plugin si besoin (lecture directe via ?direct). En build, getEagerCssEntries
(src/vite/build.ts) ne change pas : Vite range les modules dans les mêmes chunks CSS.
2.3 Les couches
CSS
mantine:@mantine/core/styles.layer.csset@mantine/spotlight/styles.layer.css(les feuilles en couche, fournies par Mantine).theme:@recit/theme/tokens.css,fonts.css,page.css,deck.css,print.css, et levirtual:recit-theme.cssdu site (variables d'un site, §2.4).recit: les modules des paquets et du framework.site: la feuille d'un site (sites/*/styles.css) ettheme.customCss.
Une règle d'un module gagne sur Mantine sans !important, et la feuille d'un site gagne sur tout.
noImportantStyles de biome redevient actif partout (main.css disparaît).
2.4 Thème : du recit.config à createTheme
Le schéma public ne change pas (packages/site/src/config/validators/SiteConfig.ts:361-373
ThemeConfigSchema : light, dark, fonts, customCss, noDefaultTheme, registryUrl). Ce
qui change, c'est son interprète.
| Clé du config | Aujourd'hui (plugin-theme.ts) | Après |
|---|---|---|
light.*, dark.* (20 clés shadcn : background, primary, card, muted, border, ring, radius…) | :root {…} et .dark {…} en variables shadcn | Les mêmes variables, émises par virtual:recit-theme.css sous :root et [data-mantine-color-scheme="dark"], et lues par un cssVariablesResolver qui en dérive --mantine-color-*, --mantine-radius-*, --mantine-primary-color-* ; primary devient theme.primaryColor avec une échelle de 10 teintes générée (generateColors n'est pas dans @mantine/core mais dans le paquet séparé @mantine/colors-generator : STY2 écrit une génération HSL de dix nuances autour de la teinte donnée, trente lignes testées, et ne prend ce paquet qu'en repli si les nuances ne conviennent pas) |
fonts.sans/serif/mono | @import Google Fonts (liste GOOGLE_FONTS), --font-* | Même import, posé dans theme.fontFamily, fontFamilyMonospace, headings.fontFamily ; Geist reste la valeur par défaut via @fontsource-variable |
registryUrl | fetchShadcnRegistryItem (shadcn-registry.ts) | Retiré : diagnostic d'erreur « theme.registryUrl is gone with shadcn/ui: set theme.light and theme.dark », schéma marqué @deprecated une version puis supprimé ; shadcn-registry.ts et son test supprimés |
customCss | Injecté dans main.css | Émis en couche site, inchangé pour l'utilisateur |
noDefaultTheme | Saute defaultTheme.css | Saute les valeurs par défaut du thème (le createTheme de base) ; les tokens de @recit/theme restent |
defaultTheme.css (zinc oklch) disparaît : les défauts du site deviennent ceux de
@recit/theme/tokens.css (violet) ou restent zinc par un jeu de valeurs dans createTheme ;
STY2 choisit après comparaison dans la galerie (§7), le but étant l'iso-rendu des sites. Les
variables shadcn (--card, --popover, --sidebar-*) que personne ne lira plus une fois les
composants migrés sont retirées à STY9.
2.5 Mode de couleur
ColorSchemeScriptdans<head>(viaPluginHeads/Meta,@unhead/react) etlocalStorageColorSchemeManager({ key: "recit-color-scheme" })dansMantineProvider;defaultColorSchemedepuis le config ("auto"quand rien n'est dit). Plus denext-themes, plus deThemeProviderdansSiteRoot.tsx:75,useThemede@recit/site/componentsethooksréimplémenté suruseMantineColorScheme()(même signature de retour :theme,resolvedTheme,setTheme) pour ne pas casser les sites.- Le mot du glossaire est colorMode (
light,dark,system) : le site l'adopte enfin (site.colorModedans le config, défautsystem), etcompile-story.ts:177-255le mappe déjà pour les decks. - Le pont
.darkpendant la migration : vingt fichiers lisent la classe.dark(§1.4). Jusqu'à STY9, un effet dansSiteRootrecopiedata-mantine-color-schemevers la classe.darkde<html>; le deck (useThemeMode.ts:61),site-stories(.dark .recit-wrapper), Mermaid (mermaid.initialize({theme})), Inkeep (colorMode), Storybook (withThemeByClassName) et le gabarit de deck (html-template.ts:34-38) continuent de marcher. STY9 retourne chacun vers l'attribut (ou verslight-dark()dans les modules) et retire le pont ; le deck seul et le gabarit CLI, qui n'ont pas Mantine, lisentdata-mantine-color-schemepar un script de démarrage équivalent à celui d'aujourd'hui, pour quedeck.cssait une seule condition de mode. - Dans les modules, le mode s'écrit
light-dark(a, b)ou avec les mixins@mixin dark: plus de variantedark:.
2.6 Typographie
TypographyStylesProviderde Mantine remplace@tailwindcss/typographyetProseClasses(Typography.tsx,MdxPage.tsx,Markdown.tsx) :Typography.module.cssreporte les règles de@utility proseet@utility typography(main.css:57-164: liens, code, citations,hr, listes de tâches,h2mt-8 mb-4) sur le sélecteur du provider.theme.headings: les six niveaux deHeading.tsx(cva) deviennenttheme.headings.sizes(h12.25rem/800, h21.5rem/700, h31.25rem/600, h41.25rem, h51.125rem, h61rem), etHeadingdevientTitle+Heading.module.css(ancre.anchor,.link).MdxComponents.tsxréécrit élément par élément :h1–h6→Heading(Title),a→AnchorLink(module),img/vidéo →.media(module),table→ScrollableTable(module +Tablede Mantine),pre→.pre(module),code→InlineCode(Codede Mantine) ouCodeBlock, les douze alias deCallout→Alertde Mantine avecclassNames, et unpexplicite (il manque aujourd'hui) pour queTypographyStylesProvideret le MDX s'accordent.- Polices : Geist et Geist Mono gardées par
@fontsource-variable; polices Google du config gardées ; polices de deck inchangées (@recit/theme/fonts.css). - Échelle, avant → après, à vérifier pixel près dans la galerie :
| Surface | Élément | Avant | Après (Mantine theme.headings ou deck.css) |
|---|---|---|---|
| Site | h1 | text-4xl font-extrabold (2.25rem/800) | 2.25rem/800, lineHeight 1.2 |
| Site | h2 | text-2xl font-bold (1.5rem/700) | 1.5rem/700, marges 2rem 0 1rem |
| Site | h3 | text-xl font-semibold (1.25rem/600) | 1.25rem/600 |
| Site | corps | Tailwind prose (1rem/1.75) | theme.fontSizes.md 1rem, lineHeights.md 1.75 |
| Deck | h1/h2/h3 | deck.css 3.5/2.5/1.5rem | inchangé (deck.css reste) |
2.7 Icônes et animation
lucide-reactreste la seule bibliothèque d'icônes ;@recit/site/iconsinchangé ; les composants Mantine reçoivent une icône Lucide parleftSection/rightSection.motion/reactreste pour les transitions de diapositives (transitions.ts,AnimatePresence) et la navigation du site (motionFeatures.ts, règleLazyMotion/m.*duCLAUDE.md) ; les entrées/sorties d'interface (menus, dialogues, drawers) utilisentTransitionet les transitions intégrées des composants Mantine ;tw-animate-csset les classesanimate-*/data-statedisparaissent.
2.8 Shiki, Mermaid, KaTeX
- Les variables
--shiki-light,--shiki-dark,--shiki-*-bg(plugin-theme.ts:323-326) sont toujours émises ; la règle qui choisit la paire (main.css@layer base, Shiki rules) passe dansCodeBlock.module.cssaveclight-dark(var(--shiki-light), var(--shiki-dark));defaultColor: falsedeshiki.ts:81reste. Les thèmes Shiki restent configurables parsyntaxHighlighting.themes. - Mermaid :
initialize({ theme })lit le schéma Mantine (useMantineColorScheme) ; les libellés HTML gardent la règle dedeck.css:528. - KaTeX : rien dans le site (aucune feuille), inchangé dans la CLI (
buildKatexCss).
3. Conventions des modules
- Un module par composant,
Button.module.cssà côté deButton.tsx, importéimport classes from "./Button.module.css". Un fichier qui exporte plusieurs composants a un module par composant exporté, ou se découpe. - Nommage : classes en camelCase (
.root,.label,.isOpen), une classe racine.root, des états par classe ou pardata-*(&[data-active]), des variantes pardata-variant; pas de BEM, le module isole déjà. - Valeurs :
rem()pour toute longueur (postcss-preset-mantine),light-dark()pour les couleurs qui changent de mode,var(--mantine-*)pour tout ce que le thème définit, les tokens--primary,--background… de@recit/themeseulement dans les paquets de format (deck, components) qui ne chargent pas Mantine. - Points de rupture : les mixins
@mixin smaller-than $mantine-breakpoint-md; jamais une valeur en pixels écrite deux fois. - Imbrication : deux niveaux au plus ; au-delà, une classe.
- Typage :
declare module "*.module.css"de Vite (vite/client) danspackages/site, et uncss-modules.d.tsgénéré avec la table de classes dans les paquets publiés (STY0), pour queclasses.inexistantesoit une erreur de typecheck. - Sous-éléments Mantine :
classNames={{ root: classes.root, section: classes.section }}; un composant qui n'a pas de sous-élément reçoitclassName={classes.root}. - Dynamique : §0.3 permis 4 ; un
data-*quand les valeurs sont finies,useCssVariablesinon ; le module litvar(--x, repli). - SSR et HTML prérendu : noms déterministes (§2.2), donc un HTML prérendu identique d'une build
à l'autre à CSS constant ; les captures de §7 et les tests de
site-app-bundlene voient aucun bruit de hachage. - Storybook : chaque composant de
@recit/componentsgarde sa story ; les stories n'ont ni classe ni style (le décorateurwithSlidepasse en modulepreview.module.css). - Tests : un test unitaire qui lit une classe lit
classes.x, jamais une chaîne.
4. Table de correspondance
4.1 Les 54 composants ui/
Export @recit/site/ui/* : les noms de fichiers exportés restent pour les importeurs
(site-kit, MdxComponents), leur implémentation change. « Mantine » = réécrit sur le composant
Mantine nommé avec classNames ; « module » = sans composant Mantine, une balise et un module ; «
supprimé » = sans importeur, retiré avec son export.
Fichier ui/ | Dépendance retirée | Importeurs (int / @ui) | Après |
|---|---|---|---|
| Button, ButtonGroup | react-slot, cva | 12 / 5 ; 0 / 1 | Mantine Button, Button.Group ; asChild → component/renderRoot |
| Alert, DismissibleAlert | cva | 1 / 3 ; 0 | Mantine Alert (withCloseButton) ; DismissibleAlert supprimé |
| Callout | – | 2-3 | Mantine Alert + Callout.module.css (12 types par data-type, --callout-color → variable de module) |
| Badge | react-slot, cva | 2 / 0 | Mantine Badge |
| Separator | react-separator | 2 / 3 | Mantine Divider |
| Dialog | react-dialog, react-visually-hidden | 0 / 3 | Mantine Modal (+ VisuallyHidden) |
| Drawer | vaul | 2-3 | Mantine Drawer |
| Command | cmdk, react-dialog | 0 / 2 | @mantine/spotlight |
| DropdownMenu | react-dropdown-menu | 0 / 2 | Mantine Menu |
| Popover, HoverCard | react-popover, react-hover-card | 0 / 1 ; 0 | Mantine Popover, HoverCard ; HoverCard supprimé |
| Tooltip | react-tooltip | 1 / 2 | Mantine Tooltip |
| Kbd | – | 0 / 1 | Mantine Kbd |
| Input, InputGroup, Label, Textarea, NativeSelect, Select | react-label, cva, react-select | 1 ; 0/1 ; 1 ; 1 ; 0 ; 0 | Mantine TextInput, Input.Wrapper, Textarea, NativeSelect ; Select et NativeSelect supprimés |
| Card | – | 1 | Mantine Card |
| Collapsible | react-collapsible | 1 | Mantine Collapse (les keyframes .CollapsibleContent de main.css disparaissent) |
| NavigationMenu | react-navigation-menu, cva | 1 | module (HeaderNavigation) + Menu pour les sous-menus |
| Stepper | – | 1 | module (@layer utilities .stepper de main.css:423-470 → Stepper.module.css) |
| Toggle, ToggleGroup | @base-ui/react, cva | 1 ; 0 | Mantine SegmentedControl (ToggleGroup supprimé) ou ActionIcon |
| CodeBlock, CodeTabPanel, CodeTabs, EmbeddedCodeBlock, SyntaxHighlight | react-tabs (CodeTabs) | 2-3 ; 1 (site-kit) | modules ; CodeTabs sur Mantine Tabs ; SyntaxHighlight reste paresseux (règle Bundle Size) |
| Accordion, ActionButton, AlertDialog, AspectRatio, Breadcrumb, Carousel, Checkbox, Form, Frame, Item, Pagination, Progress, RadioGroup, ReactComponentDoc, ScrollArea, Secret, SidecarBox, Skeleton, Slider, Switch, Tabs, Value | accordion, alert-dialog, aspect-ratio, embla-carousel-react, checkbox, react-hook-form, slot+cva, progress, radio-group, scroll-area, slider, switch, tabs | 0 | supprimés (27 avec ceux ci-dessus) ; le Pagination de lib/components n'est pas celui-ci |
util.tsx (cn, focusRing) | clsx, tailwind-merge | partout | supprimé ; focusRing devient :focus-visible dans chaque module |
4.2 Le framework hors ui/
| Fichier(s) | className | Après |
|---|---|---|
LandingPage.tsx | 34 | Container, Title, Text, SimpleGrid, Card + module |
plugins/markdown/MdxPage.tsx | 27 | TypographyStylesProvider, Group, Menu + module ; ButtonGroup, DropdownMenu, Popover déjà migrés en §4.1 |
Header.tsx, HeaderNavigation.tsx, TopNavigation.tsx, MobileTopNavigation.tsx, MobileTopNavigationDrawer.tsx | 19, 13, 6, 2, 18 | AppShell.Header, Burger, Drawer + modules ; les variables de mise en page (--top-header-height, --header-height, --banner-height, main.css:166-220) deviennent AppShell header.height et des variables du module Layout |
Footer.tsx | 17 | module ; --columns → data-columns (1 à 6) |
Layout.tsx, Main.tsx, Banner.tsx, PageProgress.tsx, Spinner.tsx | 2, 5, 3, 1, 1 | AppShell, Container + modules ; PageProgress sur Progress de Mantine et son module → @tanem/react-nprogress retiré |
navigation/NavigationItem.tsx, NavigationCategory.tsx, Toc.tsx, NavigationFrames.tsx, StackRows.tsx, NavigationWrapper.tsx, SidebarToggle.tsx, NavigationBadge.tsx, NavigationFilterInput.tsx, MobileNavigationDrawer.tsx | 18, 14, 12, 9, 8, 6, 3, 4, 3, 2 | AppShell.Navbar, NavLink, ScrollArea, TextInput, Badge + modules ; Toc indicatorStyle → useCssVariable ; --side-nav-width → AppShell navbar.width |
BrowserWindow.tsx, Framed.tsx, Anchor.tsx, Pagination.tsx, Search.tsx, DeveloperHint.tsx, CategoryHeading.tsx, AiAssistantMenuItems.tsx, ThemeSwitch.tsx, Markdown.tsx, InlineCode.tsx, ScrollableTable.tsx, NotFoundPage.tsx, ErrorPage.tsx, Mermaid.tsx, LanguageIcon.tsx, Heading.tsx, Typography.tsx | 15, 6, 6, 10, 6, 4, 3, 3, 3, 3, 3, 1, 2, 2, 2, 1, 5, 2 | modules ; ThemeSwitch sur useMantineColorScheme + ActionIcon ; Heading/Typography §2.6 |
errors/ErrorAlert.tsx, RouterError.tsx | 3, 2 | Alert + module |
plugins/search-pagefind/PagefindSearch.tsx, ResultList.tsx, IndexingDialog.tsx | 9, 8, 5 | Spotlight + modules ; l'extrait HTML (dangerouslySetInnerHTML) garde une classe de module pour ses <mark> |
app/main.tsx | 1 | import de @mantine/core/styles.layer.css et des feuilles de thème ; main.css réduit aux @import et à la déclaration @layer |
lib/shiki-constants.ts (HIGHLIGHT_CODE_BLOCK_CLASS) | – | une classe du module CodeBlock exportée, consommée par plugin-shiki.ts |
4.3 Les paquets Récit, les sites, les exemples
| Surface | Fichiers | Après |
|---|---|---|
@recit/components | Cover, Points, Grid, Quote, Callout, Media, CodeBlock, CodeGroup, Mermaid, mdx/builtins.tsx (Card, Columns, Col, Left, Right, Image, Badge, Kbd, Step), SlideMotion | un module par composant, composes: slide-* from global pour le contrat de deck.css ; CodeBlock style={{fontSize}} ×2 → data-size ; Columns → data-columns ; stories inchangées en API |
@recit/deck | PresenterView (28), Presentation (13), MobileRemoteView (12), RemoteQrCodeModal (11), SlideFrame (8), AnnotationCanvas, SlideStage, HtmlSlide, DevHud, MotionViewer | modules ; PresenterView passe de la palette slate codée en dur aux tokens (différence visuelle attendue, acceptée en galerie) ; SlideStage échelle et taille → calcul CSS, SlideFrame fond → variable par scène du <style> de données (§0.3 permis 4) ; HtmlSlide display: contents → classe ; .deck-print {width, height} → variables |
@recit/theme | tailwind.css (pont) | supprimé avec le peer tailwindcss ; tokens.css, deck.css, page.css, print.css, fonts.css restent globaux (§5.4) ; deck.css perd ses sélecteurs .dark au profit de [data-mantine-color-scheme="dark"] à STY9 |
@recit/motion-blocks | styles.ts, 8 blocs, player.tsx, ui/render.tsx, uiMock.tsx, compareBars.tsx… | §5.6 : makeStyles → blocks.module.css + ThemeVars ; la couche d'animation reste en liste d'exclusion |
@recit/motion-render | Film.tsx:30 | AbsoluteFill avec className de module ; fond par variable posée par ThemeVars |
@recit/site-stories | mdx-components.tsx (10), MotionViewer (7), i18n.tsx (4), StoryViewer, StoryDocument, plugin.tsx | site-stories.css → site-stories.module.css compilé en classes stables, préfixage .recit-wrapper conservé en PostCSS ; not-prose disparaît (le provider de typographie s'arrête à .recit-wrapper par :not()) |
@recit/site-kit | Cards (9), ExampleHeader (9), Tutorial (6), SourceFile (1) | modules ; Tutorial prose prose-sm → TypographyStylesProvider + module (counter-increment en CSS) |
sites/www | Hero, FormatGrid, OneSource | modules (les valeurs arbitraires max-h-[460px], text-[10px] deviennent des règles) |
sites/docs, motion, components | StatusBadge, VideoDownloads, FormatSupport | Badge, module, module |
sites/motion/styles.css, presentation/styles.css, components/src/stories.css | classes de contenu MDX | restent (couche site), .typography renommé vers le sélecteur du provider ; media-demo reçoit enfin une règle ou disparaît du MDX |
examples/deck-mdx, deck-showcase | Counter, Timeline, Metric | modules compilés par esbuild (§2.2) ; deck-showcase/styles.css reste (customCss) |
sites/storybook | preview.tsx, preview.css, main.ts | preview.module.css, feuille Mantine + thème + feuilles des paquets, PostCSS ; withThemeByDataAttribute (data-mantine-color-scheme) |
packages/cli/src/motion/client.tsx, commands/motion-dev.ts | objet styles, <style> inline de la page de dev | client.module.css bundlé par esbuild ; la page de dev charge la feuille |
5. Chaque surface, le chemin technique
5.1 Le framework de site
- Socle (STY0) :
@mantine/core,@mantine/hooks, PostCSS ;MantineProviderdansSiteRootà côté duThemeProviderdenext-themes(coexistence le temps de la migration : les deux posent leur attribut, le pont §2.5 les accorde) ;@mantine/core/styles.layer.cssimporté dansmain.tsxavantmain.css;@layerdéclaré en tête demain.css;css.modulesconfiguré ; un premier module (Spinner.module.css) prouve la chaîne dev, SSR, build, prérendu, capture. - Thème (STY2) :
plugin-theme.tsgarde l'émission des variables et des polices, perd le registre shadcn ; un nouveausrc/lib/theme/createRecitTheme.tslitvirtual:recit-configet construitcreateTheme+cssVariablesResolver;ColorSchemeScriptdans la tête. - Primitives (STY3) puis composants (STY4) : fichier par fichier, dans l'ordre des
importeurs (un
ui/d'abord, ses consommateurs ensuite), chaque fichier perdant soncnet ses chaînes le jour où il reçoit son module ;pnpm checkcompte les violations qui restent (§8). - Retrait (STY5) :
tailwindcss()retiré deplugin.ts,@sourceet__pluginDirsretirés deplugin-theme.ts:349,main.cssréduit (reset,@layer, variables de mise en page qui restent globales), dépendances retirées, taille du bundle mesurée.
5.2 site-stories
Le préfixage .recit-wrapper protège aujourd'hui les tokens et deck.css du CSS du site. Il reste
: build-css.mjs garde sa passe PostCSS de préfixage sur les tokens et sur deck.css, y ajoute
@recit/components/styles.css et @recit/deck/styles.css (classes déjà préfixées recit-…, donc
sans collision, mais préfixées quand même par .recit-wrapper pour l'héritage des variables), et
compile site-stories.module.css en classes stables. Le site qui l'importe le place en couche
recit. not-prose disparaît : TypographyStylesProvider exclut .recit-wrapper par son propre
module (:where(.recit-wrapper) * hors portée).
5.3 Le deck seul et la CLI
css.ts: plus decompile()/Scanner; la feuille est la concaténation de@recit/theme/{tokens,page,deck,print}.css,@recit/components/styles.css,@recit/deck/styles.css, du CSS quejs.tsa émis pour les modules des composants utilisateur, decustomCss, puis KaTeX et polices comme aujourd'hui ;@tailwindcss/nodeetoxidequittentpackages/cli/package.json.js.ts:write: falseetoutputFilesexistent déjà ; un.module.cssimporté par un composant utilisateur produit un fichier.cssdansoutputFiles(loaderlocal-cssnatif d'esbuild) ;js.tsle rend dansJsBundle.css,build-deck.tsle passe àcss.ts. Les noms locaux suivent le même motif que Vite (optionlocalIdentName… si esbuild ne l'expose pas, les composants utilisateur gardent le hachage d'esbuild : ils ne sont pas prérendus, aucune capture ne dépend du nom).html-template.ts: le script de démarrage posedata-mantine-color-schemeet la classe.darkjusqu'à STY9, puis l'attribut seul.export-pdf.tset?print=1:print.cssinchangé ; captures dedeck-featuresavant/après.
5.4 @recit/theme : ce qui reste global, et pourquoi
packages/core compile des classes dans le HTML des diapositives (slide-*, grid-cols-n,
deck-*, 30 nœuds hast dans rehype-plugins.ts et unified-pipeline.ts) et les récits des
utilisateurs les écrivent dans leur Markdown. Ces classes sont un contrat public : elles ne
peuvent pas être hachées. deck.css (951 lignes) reste donc la feuille globale du format, en couche
theme, et les composants la rejoignent par composes: … from global. Ce qui change :
tailwind.css disparaît, les sélecteurs .dark deviennent [data-mantine-color-scheme="dark"]
(STY9), et les quelques règles de deck.css qui ne servent qu'à un composant React
(.slide-progress-fill, .deck-hud…) migrent dans le module du composant, une par une, avec la
capture qui le prouve. tokens.css reste la source des tokens HSL ; le site les lit par le
résolveur (§2.4).
5.5 @recit/components et Storybook
Chaque composant reçoit son module ; les classes sémantiques passent par composes. Storybook
compile les mêmes modules par Vite et montre le résultat dans la grille 16:9 du décorateur, en clair
et en sombre (withThemeByDataAttribute). Les 9 stories deviennent des captures (§7.2).
5.6 @recit/motion-blocks
makeStyles(theme): la partie statique (frame,eyebrow,display,statement,statValue,rowLabel,mask,styles.ts:15-60) devientblocks.module.css, dont les valeurs de thème sont lues envar(--mb-bg),var(--mb-text),var(--mb-font-body),var(--mb-type-display)… ; les unitéscqwetcontainerType: sizepassent telles quelles en CSS.ThemeVars: un composant racine (dansengine.tsxouthemes/) qui pose les variables--mb-*depuis leThemereçu parinputProps. C'est la seconde exception de §0.3, dans la liste d'exclusion.- La couche d'animation (
StaticAnim,FramerAnim,RemotionAnim,player.tsx:158-226,ui/render.tsxsampleSpec) reste : elle fusionne un objet par image. Ce qui est statique dansrender.tsx(57style: {) est trié : constant → module, par temps → couche. - Preuve : les goldens de
motion-render(pnpm -F @recit/motion-render goldens:check) avant et après, plus les capturesmotion-devdee2e.
5.7 Sites, exemples, site-kit
Composants de sites et d'exemples en modules (§4.3) ; les feuilles styles.css des sites restent
pour les classes écrites dans le MDX ; site-kit prend des modules et perd prose.
6. Dépendances
6.1 Retirées
| Paquet | Déclaré dans | Importeurs avant |
|---|---|---|
tailwindcss, @tailwindcss/vite, @tailwindcss/typography, tw-animate-css | packages/site | plugin.ts, main.css |
tailwindcss, @tailwindcss/node, @tailwindcss/oxide | packages/cli, packages/integrations/site-stories, catalogue | bundle/css.ts, build-css.mjs |
tailwindcss (peer optionnel) | packages/theme | tailwind.css |
@tailwindcss/vite, tailwindcss (catalogue) | sites/storybook | main.ts, preview.css |
tailwind-merge, clsx, class-variance-authority | packages/site | cn.ts, 14 cva |
@radix-ui/react-{slot,dialog,visually-hidden,collapsible,label,tabs,accordion,alert-dialog,aspect-ratio,checkbox,dropdown-menu,hover-card,navigation-menu,popover,progress,radio-group,scroll-area,select,separator,slider,switch,tooltip,toggle,toggle-group} (21 + 2 jamais importés) | packages/site | 7, 3, 3, 2, 2, 2, puis 1 ou 0 |
@base-ui/react, cmdk, vaul, embla-carousel-react, next-themes, react-hook-form, @tanem/react-nprogress | packages/site | 2, 1, 1, 1, 4, 1, 1 |
6.2 Ajoutées
| Paquet | Où | Rôle |
|---|---|---|
@mantine/core, @mantine/hooks 9.7.4 | packages/site (dépendance), sites/storybook (dev), catalogue | composants, thème, hooks |
@mantine/spotlight 9.7.4 | packages/site | recherche |
postcss-preset-mantine, postcss-simple-vars | packages/site, packages/cli, site-stories, sites/storybook, et le script de build des modules (dev) | mixins, rem(), light-dark() |
postcss-modules (ou le plugin rolldown de tsdown, décidé en STY0) | racine (dev) | classes stables des paquets publiés |
Aucune de ces dépendances ne duplique une existante. NOTICE.md reçoit une ligne pour Mantine (MIT)
et perd la mention implicite de shadcn (§9.4).
6.3 Taille des bundles
Règles du CLAUDE.md « Bundle Size » inchangées : rien de lourd dans le chemin d'entrée,
SyntaxHighlight, CodeTabs, Mermaid, Markdown paresseux, domAnimation par LazyMotion.
Mesure avant/après (STY0 pose la mesure, STY5 et STY9 la refont) : taille gzip du chunk d'entrée de
sites/docs et de sites/www (dist/assets/entry-*.js et le CSS qui l'accompagne), notée au
journal. Attendu : le CSS du site grossit (feuille Mantine, ~30 ko gzip pour styles.css
complète, à mesurer ; l'import par composant @mantine/core/styles/Button.css est l'option si la
feuille entière pèse trop) et le JS d'entrée baisse (21 paquets Radix, cmdk, vaul,
next-themes, tailwind-merge en moins, Mantine étant tree-shakable composant par composant). Le
deck seul (recit build) perd le CSS utilitaire et gagne les feuilles des paquets : mesuré sur
examples/deck-starter.
6.4 Rupture pour les utilisateurs de @recit/site
Aujourd'hui, le Tailwind du site compile aussi les classes utilitaires écrites par l'utilisateur
d'un site : ses pages MDX, ses mdx.components, ses slots et ses composants, scannés par @source
(plugin-theme.ts:349, __pluginDirs). Une fois Tailwind parti, ces classes ne produisent plus
rien. C'est une rupture, annoncée dans le changelog (§10.2) et documentée (STY10.2) :
theme.customCsscontinue de marcher (couchesite), et c'est le chemin : une feuille CSS propre au site, ou un*.module.cssà côté de chaque composant du site, compilé par Vite comme ceux du framework.theme.registryUrlpart avec un diagnostic (§2.4).- Les composants de
@recit/site/ui/*gardent leurs noms et leurs props (§4.1) ;cnde@recit/site/ui/utildisparaît. - La page
sites/docs/pages/theme/mantine.mdx(STY10.2) porte une section « Migrating a site from Tailwind utilities » avec la table Tailwind → CSS du skill (§9.1).
7. Captures avant et après
L'identité au pixel n'est pas le but : le but est aucune régression non vue, et l'acceptation consciente de chaque différence. Le protocole en trois temps.
7.1 Phase 0 : étendre la couverture avant de toucher au style (STY1)
Tout ce que §1.5 liste comme non couvert reçoit une capture avant le premier module :
| Ajout | Spec | Détail |
|---|---|---|
Les 28 pages de plans | site-plans.spec.ts | pleine page, comme vision |
Les 3 pages app* de la doc, /present/bridge, l'index et le récit anglais de site-i18n | site-docs, site-motion, site-i18n | ajout aux listes PAGES |
| Mode sombre de chaque page de site | un projet Playwright chromium-dark (colorScheme: "dark") | double le nombre de captures de site ; le nom reçoit -dark |
Mobile : un projet mobile (390×844, isMobile, hasTouch) | pages d'accueil, une page de doc, une story, le drawer ouvert | la navigation mobile et les drawers sont les composants les plus réécrits |
| 404, recherche ouverte (avec un résultat), en-tête seul, pied de page seul, bannière | site-docs | page.goto("/nulle-part"), openSearch() puis saisie, locator("header").screenshot() |
Le bundle Tauri (site-app-bundle) | 3 captures (accueil, récit, 404) | sert de témoin « sans CDN » |
| Storybook : les 9 stories, clair et sombre | storybook.spec.ts (nouveau) | pnpm -F recit-storybook build puis un webServer statique sur storybook-static, iframe.html?id=… |
Console présentateur en sombre, ?print=1 d'un deck | deck-features | PresenterView change de palette : il faut la voir |
| Les goldens motion | motion-render goldens | déjà là ; goldens:check entre dans la vérification de STY7 |
Puis pnpm test:e2e:update:linux une fois, sur la branche, au commit de départ : ce jeu de
-linux.png est le « avant » (environ 213 + 250 fichiers). e2e-linux.sh n'écrit que des
-linux.png : rien d'autre ne bouge.
7.2 La galerie
scripts/screenshot-gallery.js (nouveau, STY1) lit test-results/linux/** après un
pnpm test:e2e:linux et produit test-results/gallery/index.html : une ligne par capture qui a
changé, trois colonnes avant / après / diff (Playwright écrit -expected, -actual, -diff),
le pourcentage de pixels, un filtre par spec, un bouton « accepté » qui note la décision dans
test-results/gallery/decisions.json. Aucune dépendance : HTML et JS inline, lus depuis le disque.
La machine de travail est distante et sans écran :
node scripts/screenshot-gallery.js --serve <port> sert test-results/gallery sur toutes les
interfaces (comme le serveur de dev du site depuis allowedHosts: true), et le bouton « accepté »
envoie un POST au même serveur, qui écrit decisions.json ; sans --serve, le script n'écrit que
les fichiers. L'humain parcourt la galerie à la fin de chaque vague ; ce qu'il accepte est régénéré
avec pnpm test:e2e:update:linux <spec> (la seule commande qui écrit une -linux.png), le reste
est un bug du lot.
7.3 Réglages
maxDiffPixelRatioreste 0.02 pendant la migration, pour voir ;animations: "disabled";mermaidReady,filmsReady,decksReady,heightStabledee2e/support.tsinchangés ; les polices sont embarquées (Geist, Inter) donc stables dans le conteneur.- Chaque vague de §10 se termine par
pnpm test:e2e:linuxdeux fois (règle du skille2e-baselines), la galerie, la liste des images acceptées dans la note de journal avec la raison de chaque différence. Pas de capture locale sur cette machine Linux : le skill rappelle qu'un poste de dev ne rend pas comme l'image officielle ; les agents d'un lot vérifient par leurs tests et leurs builds, les captures se font dans le conteneur, une fois l'arbre stable. accessibility.spec.ts(axe, WCAG A/AA) reste vert à chaque lot ; Mantine est accessible par défaut, mais unclassNamespeut casser un contraste : c'est le test qui le dit.- Le coût d'un passage en conteneur :
scripts/e2e-linux.shcopie l'arbre, réinstalle (pnpm install --frozen-lockfile, magasin en cache), reconstruit les 47 projets (cache nx dans le volume,NX_CACHE_DIRECTORY=/cache/nx) puis lance la suite ; APP3 l'a vu dépasser dix minutes. STY1.1 le mesure (temps total, part de l'installation, de la construction, des captures) et écrit le chiffre ici. Mesuré le 29/10 (STY1, 8 cœurs, caches chauds) : un passage complet dure 7 à 8 min, dont 1 s de copie, 25 à 45 s d'installation depuis le magasin, 2 à 2 min 30 de construction (cache nx) et 4 min 20 à 5 min 20 pour les 444 tests des trois projets ; l'installation et la construction ne pèsent qu'un tiers, le saut d'installation ne vaudrait que 30 s. Budget : environ 460 captures, sept vagues, deux passages par vague ; le script accepte déjà un filtre de specs (playwright test "$@"), donc une vague ne rejoue que les specs des surfaces qu'elle a touchées (ligne « Vérification » de chaque lot), et la suite complète ne tourne qu'à STY1 et à STY9. STY1.5 essaie un saut d'installation danse2e-linux.shquand le hachage depnpm-lock.yamln'a pas changé (node_modulesgardé dans le volume de cache) ; la construction est déjà couverte par le cache nx ; si le saut n'est pas l'affaire d'une demi-journée, il est hors périmètre et la mesure reste la règle. - Les captures darwin ne sont pas régénérées pendant la migration (le skill les réserve au poste local) ; elles le sont une fois, après STY9, par l'utilisateur.
8. Application de la règle
8.1 scripts/check-style-rule.js (STY0, en avertissement ; STY9, en erreur)
Entré dans check:other de package.json. Sur tout .ts/.tsx de packages/, sites/*/src,
sites/storybook/.storybook, examples/*/components, hors tests, il signale :
className=suivi d'une chaîne ou d'un gabarit ("…",'…',`…`) ; toléré :className={classes.x},className={cond ? classes.a : classes.b},className={props.className}relayé tel quel.style={etstyle:dans uncreateElement, hors fichiers de la liste d'exclusion.- Les props de style Mantine (
p,px,py,m,mt,mb,ml,mr,c,bg,fz,fw,w,h,maw,mah,miw,mih,pos,display,ta,lh,bd,opacity,styles,sx,vars) sur un élément JSX. - Un import de
clsx,tailwind-merge,class-variance-authority,tailwindcss,cmdk,vaul,next-themes,@radix-ui/*,@base-ui/*. - Dans un
.csshors.module.csset hors@recit/themeetsites/*/styles.css: toute règle. - Dans un
.css:@apply,@theme,@custom-variant,@source,@plugin,@utility,dark:. - Un
.tsxqui n'importe aucun module et contient unclassName(signal : composant pas encore migré).
Il imprime le compte par dossier et le total ; en mode avertissement (jusqu'à STY9) il ne fait pas
échouer pnpm check, mais refuse une hausse par rapport à scripts/style-rule-baseline.json
(le compte de §1), ce qui gèle main (§11). La liste d'exclusion
scripts/style-rule-allowlist.json nomme fichier par fichier la couche d'animation (§0.3) et le
hook useCssVariable. Un test scripts/__tests__/check-style-rule.spec.ts couvre chaque motif.
8.2 Lint et format
biome.jsonc:noRestrictedImportsgagne les paquets de §8.1-4 avec un message qui renvoie au skill ;css.parser.tailwindDirectiveset lesnoUnknownAtRulesignorés (plugin,theme,custom-variant,apply) retirés à STY9 ;noImportantStylespartout ; les.module.csssont lintés comme les.css(déjà dansincludes)..oxfmtrc.json: rien à faire,.module.cssest un.css.lefthook.yml: rien à faire.- Typage :
pnpm typecheckattrapeclasses.inexistantegrâce aux déclarations de §3.6.
9. Skills, agents, documentation
9.1 .claude/skills/mantine-style/SKILL.md (nouveau, STY10)
Déclenché pour tout composant, style, CSS, Mantine, thème, mode sombre, classe, Tailwind. Plan de la fiche :
- Quand l'utiliser ; la loi de §0.3 recopiée (permis, interdit, exceptions).
- Écrire un composant : le module à côté,
classes.root,classNamesMantine,data-*,useCssVariable,composes … from globalpourdeck.css,light-dark(),rem(), mixins ; un exemple complet (Callout.tsx+Callout.module.css). - Table Tailwind → CSS des utilitaires rencontrés dans l'inventaire (
flex items-center gap-2→display:flex; align-items:center; gap: rem(8px),text-muted-foreground→color: var(--mantine-color-dimmed),rounded-lg→border-radius: var(--mantine-radius-lg),dark:…→light-dark(),sm:→@mixin larger-than $mantine-breakpoint-sm,prose→TypographyStylesProvider,not-prose→ sortir du provider,size-4→width/height: rem(16px),truncate,sr-only→VisuallyHidden…), une centaine de lignes. - Vérifier :
node scripts/check-style-rule.js <dossier>,pnpm check, le lot de captures, la galerie, la note de journal. - Ce qu'on ne fait pas : props de style Mantine,
style, chaîne de classes, CSS global hors@recit/theme, nouvelle dépendance.
9.2 Skill e2e-baselines (mis à jour, STY1)
Ajoute la galerie (§7.2) à l'étape 5, les projets chromium-dark et mobile, et le
storybook.spec.ts.
9.3 CLAUDE.md et AGENTS.md (STY10)
- L108 « Core tech » :
TailwindCSSetRadix UIremplacés parMantine 9, CSS modules. - « UI » (L133-137) : « UI from
@recit/site/ui(Mantine components wrapped with their modules) and icons from@recit/site/icons(Lucide) ». - Nouvelle section « Styling » : la loi de §0.3 en anglais, en dix lignes, et le renvoi au skill.
- « Bundle Size » : Mantine ajouté à la liste des modules qui ne s'importent pas statiquement en
entier (
import { Button } from "@mantine/core"est tree-shakable ;@mantine/spotlightparesseux derrièreSearch). - « Code Style » :
.module.cssimporté enclasses, type-only pourCSSPropertiesinterdit hors liste d'exclusion. - Un agent
.claude/agents/style-reviewer.md(optionnel) : relit un diff pour les violations de la loi et les différences de la galerie non justifiées.
9.4 CONTRIBUTING.md, NOTICE.md, docs (STY10)
CONTRIBUTING.md§4 : paragraphe « Style » (modules, loi, skill, galerie).NOTICE.md: ligne « Mantine 9 (@mantine/*, MIT) » dans les dépendances notables ; la lignepackages/site/précise « UI rewritten on Mantine on, the shadcn/ui and Radix code of the fork is gone ». sites/docs/pages/theme/overview.mdx:38ettheme/custom-css.mdx:4,25: « compiled with Tailwind » → « a plain stylesheet, loaded after the theme », plus une pagetheme/mantine.mdxqui montre comment un site écrit sonrecit.configtheme:et sa feuille en couchesite.03-packaging.md: la ligne des dépendances de@recit/theme(peertailwindcssretiré).
10. Lots
Chaque lot nomme ses fichiers possédés, ce dont il dépend, ce qu'il fournit et sa
vérification. Tailles S, M, L. Chaque lot se termine par ses tests unitaires, les builds de ses
paquets, node scripts/check-style-rule.js sur ses dossiers et sa note de journal ; chaque
vague se termine par la séquence de §7.3 (e2e dans le conteneur deux fois, galerie, revue), lancée
par le coordinateur quand plus aucun lot n'est à mi-chemin, parce que le conteneur copie l'arbre
entier (scripts/e2e-linux.sh) et qu'un manifeste changé sans son lockfile le ferait échouer. Les
lignes « e2e » des vérifications ci-dessous se lisent « en fin de vague ».
État au 29/10/2026
Exécution par vagues sur feature/mantine, découpage de 20-multi-agents.md §4 (notes détaillées
dans journal.md) :
| Vague | Lots | État |
|---|---|---|
| 0 | STY1, STY10.1 | Faits : 443 références Linux, galerie, skill mantine-style, section « Styling » |
| 1 | STY0a, STY0b, STY0c, STY0d | Faits : Mantine 9.7.4 et PostCSS dans le framework, Spinner.module.css, modules compilés par Lightning CSS en table typée (@recit/components), module utilisateur dans un deck par esbuild, compteur à 1 217 en avertissement ; site-docs 106/106, galerie vide |
| 2 | STY2, STY6.1, STY7a, TST-A | Faits : thème createRecitTheme et mode de couleur Mantine (.dark reflété), outillage des quatre paquets publiés et feuilles branchées (CLI, site-stories, Storybook, Remotion, recit dev), blocs motion sans style inline (32 goldens identiques), 15 tests e2e comportementaux ; site-docs 106/106, galerie vide |
| 3 | STY3a, STY3b, STY3c, STY6.2, STY7b, TST0 | Faits : primitives ui/ sur Mantine (13 composants réécrits, 27 supprimés, surcouches et Spotlight, code et Shiki), @recit/components en modules, couche d'animation triée (goldens identiques), Storybook testé par Vitest ; ordre des couches posé en tête de chaque feuille émise ; e2e 459/459, galerie vide |
| 4 | STY4a-b, STY6.3a-b, STY8, TST1, TST2a-b | Faits : packages/site hors typographie et main.css, @recit/deck (échelle en CSS), site-kit, site-stories, sites et exemples, ≈ 300 stories ; porte : e2e 447 passés, 306 références Storybook à créer, 8 pages à −1 px (FeatureCard, héros) corrigées par GATE4/TST4b |
| 5 | STY5a-b, TST3a-b, TST4a-b, GATE4, INFRA, SBFIX | Faits : typographie (Typography, Heading, MdxComponents) et main.css démonté (base.css, Layout.module.css), icônes de langage, composants des sites dans site-kit (sites en Markdown seulement), ≈ 460 stories ; compteur de style 17, stories 18. Vague de corrections en cours avant la porte : STORYFIX2, LH, A11Y-SITE, A11Y-DECK, HYDRA |
| 5' et 6 | STY5c, STY9, STY10.2, TST5, FINALFIX | Faits (30/10) : Tailwind, shadcn/ui, Radix et next-themes retirés, un seul attribut de mode, compteur de style et compteur de stories à 0 en erreur, style-law.grit en erreur, version plan unique, documentation finale ; porte finale : deux passages e2e complets verts consécutifs (1 480 tests, 10,6 min), 1 659 références. Reste à l'utilisateur : captures darwin (STY9.3) et décision sur les couleurs de warm-editorial/saas-product |
STY0 — Socle et spike (M)
Dépend de : STY1 (le « avant » est capturé sur l'arbre intact). Parallèle avec : STY10.1.
Fichiers : packages/site/package.json, pnpm-workspace.yaml (catalogue), pnpm-lock.yaml,
packages/site/src/vite/plugin.ts (PostCSS, css.modules), packages/site/src/vite/css/plugin.ts
et collect.ts (si la collecte SSR des modules le demande), packages/site/src/app/main.tsx,
main.css (en-tête @layer seulement), packages/site/src/lib/components/SiteRoot.tsx
(MantineProvider à côté de ThemeProvider), packages/site/src/lib/components/Spinner.tsx +
Spinner.module.css (le premier module), packages/site/src/lib/hooks/useCssVariable.ts,
packages/site/src/lib/theme/ (nouveau, squelette), scripts/check-style-rule.js,
scripts/style-rule-baseline.json, scripts/style-rule-allowlist.json,
scripts/__tests__/check-style-rule.spec.ts, package.json (check:other), le spike dans
scripts/build-css-modules.mjs (nouveau) et un paquet d'essai (packages/components, un seul
module Kbd.module.css + dist/styles.css), sites/plans/pages/19-style.md §2.2 mis à jour avec
la décision.
- STY0.1 — Mantine 9 et PostCSS installés dans
packages/site;MantineProvider,ColorSchemeScript, feuille en couche ; le site se construit et se prérend sans différence visuelle (galerie vide). - STY0.2 —
Spinner.module.css: dev, SSR dev (virtual:ssr-css.css), build, prérendu ; le nom de classeSpinner__rootapparaît dansdist/index.html. - STY0.3 — Spike des modules dans un paquet publié :
Kbd.module.cssdans@recit/components, compilé enrecit-components-Kbd__rootparscripts/build-css-modules.mjs(postcss-modules),dist/styles.cssexporté, importé parsites/storybooket par le deck seul (recit build examples/deck-starter/slides.md) ; même nom en dev par@recit/source. La décision (postcss-modules, plugin rolldown ou autre) écrite dans §2.2 de ce plan. - STY0.4 — esbuild : un
.module.cssdansexamples/deck-mdx/componentsproduit son CSS dansoutputFileset arrive dans la feuille du deck ; test danspackages/cli/src/__tests__. - STY0.5 —
check-style-rule.jsen avertissement avec la baseline de §1 ; test ;pnpm checkvert. - STY0.6 — Mesure des bundles (§6.3) notée au journal.
Fournit : la chaîne de compilation, useCssVariable, le squelette du thème, le compteur.
Vérification :
Bash
STY1 — Captures avant (M)
Dépend de : rien. Parallèle avec : rien, seul en vague 0 : le conteneur e2e copie l'arbre entier, un autre lot en cours fausserait le « avant ».
Fichiers : playwright.config.ts (projets chromium-dark, mobile, webServer Storybook),
e2e/site-plans.spec.ts, e2e/site-docs.spec.ts, e2e/site-motion.spec.ts,
e2e/site-i18n.spec.ts, e2e/site-app-bundle.spec.ts, e2e/deck-features.spec.ts,
e2e/storybook.spec.ts (nouveau), e2e/support.ts (helpers openSearch existant, darkMode),
e2e/ports.ts (port Storybook), scripts/screenshot-gallery.js (nouveau),
scripts/__tests__/screenshot-gallery.spec.ts, .claude/skills/e2e-baselines/SKILL.md,
e2e/__screenshots__/**/*-linux.png (générées, jamais éditées à la main).
- STY1.1 — Les ajouts de §7.1, projet par projet ;
pnpm test:e2e:linuxdeux fois vert avant toute génération. - STY1.2 —
pnpm test:e2e:update:linuxsur tout : le « avant ». Compte des fichiers noté. - STY1.3 — La galerie (§7.2) sur un diff fabriqué (une capture volontairement modifiée) ;
--serve <port>sur toutes les interfaces et lePOSTdedecisions.json; test du script : serveur sur un port libre,GETde la galerie,POSTd'une décision, lecture dedecisions.json. - STY1.4 — Skill
e2e-baselinesmis à jour. - STY1.5 — Mesure du passage en conteneur (§7.3) écrite dans ce plan et au journal ; essai du
saut d'installation de
e2e-linux.sh(fichier ajouté à ce lot :scripts/e2e-linux.sh), gardé s'il est sûr et court, sinon noté hors périmètre.
Fournit : le jeu « avant », la galerie.
Vérification : pnpm test:e2e:linux deux fois ; node scripts/screenshot-gallery.js ouvre une
galerie vide ; pnpm exec vitest run scripts/__tests__.
STY2 — Thème et mode de couleur (M)
Dépend de : STY0. Parallèle avec : STY6.1.
Fichiers : packages/site/src/vite/plugin-theme.ts (+ test),
packages/site/src/vite/shadcn-registry.ts (supprimé) et son test,
packages/site/src/lib/theme/createRecitTheme.ts (+ test),
packages/site/src/lib/theme/colorScale.ts (+ test),
packages/site/src/config/validators/SiteConfig.ts (registryUrl déprécié, colorMode),
packages/site/src/lib/components/SiteRoot.tsx, Meta.tsx ou PluginHeads.tsx
(ColorSchemeScript), ThemeSwitch.tsx + module, packages/site/src/lib/hooks/useTheme.ts
(nouveau : remplace la réexportation de next-themes), packages/site/src/lib/components/index.ts
et hooks/index.ts (exports), packages/site/src/app/defaultTheme.css, le pont .dark dans
SiteRoot, packages/site/src/lib/components/Mermaid.tsx, plugins/search-inkeep/index.tsx,
e2e/support.ts (le helper darkMode étendu à la clé Mantine, question 14).
- STY2.1 —
createRecitTheme(config): couleurs, échelle de 10 nuances, rayon, polices,headings,cssVariablesResolver; test d'instantané du thème poursites/docs. - STY2.2 —
ColorSchemeScript,localStorageColorSchemeManager,colorModedu config ; le pont.dark;next-themesne fait plus rien mais reste installé jusqu'à STY9. - STY2.3 —
registryUrlretiré avec diagnostic ;GOOGLE_FONTSconservé. - STY2.4 —
ThemeSwitchsur Mantine ;useThemecompatible. - STY2.5 — Galerie : les sites doivent être identiques (le thème est posé mais rien ne le
lit encore, hors
ThemeSwitch).
Fournit : le thème, le hook de mode, [data-mantine-color-scheme].
Vérification : pnpm exec vitest run packages/site ; pnpm check ; e2e deux fois, galerie vide
sauf ThemeSwitch.
STY3 — Primitives ui/ (L, en trois agents)
Dépend de : STY2. Parallèle entre eux et avec STY6.2, STY7.
Fichiers, par agent, dans packages/site/src/lib/ui/ (chaque .tsx et son .module.css) :
- STY3a, actions et texte :
Button,ButtonGroup,Badge,Kbd,Toggle,Input,InputGroup,Label,Textarea,Separator,Card,Alert,Callout, et la suppression des 27 fichiers sans importeur (§4.1) avec leurs exports danspackages/site/package.json. - STY3b, surcouches :
Dialog,Drawer,Popover,Tooltip,DropdownMenu,Command(→ Spotlight),Collapsible,NavigationMenu. - STY3c, code :
CodeBlock(+ test),CodeTabs,CodeTabPanel,EmbeddedCodeBlock,SyntaxHighlight,Stepper(+ test),packages/site/src/lib/shiki-constants.ts,packages/site/src/vite/plugin-shiki.ts(la classe du module).
Chaque agent possède aussi util.tsx en lecture : il est supprimé par STY3a à la fin, quand
grep -r "ui/util" packages/site/src ne rend plus rien. lib/util/cn.ts, lui, part avec STY5.
- STY3.1 — Chaque composant réécrit selon §4.1, même API de props qu'avant pour ses importeurs
(ou l'importeur adapté dans le même lot s'il est dans
lib/components, auquel cas le fichier est demandé au coordinateur, STY4 le possède). - STY3.2 — Les tests existants (
CodeBlock.test.tsx,Stepper.test.tsx) verts, lisantclasses.*. - STY3.3 — Galerie : différences attendues listées par composant (focus ring, ombres de Mantine) et acceptées ou corrigées pour revenir à l'avant.
Vérification : pnpm exec vitest run packages/site ;
node scripts/check-style-rule.js packages/site/src/lib/ui à zéro pour les fichiers de l'agent ;
e2e deux fois ; galerie.
STY4 — Composants du framework (L, en deux agents)
Dépend de : STY3. Parallèle entre eux et avec STY6.3, STY8.
-
STY4a, pages et mise en page :
LandingPage,Layout,Main,Header,HeaderNavigation,TopNavigation,MobileTopNavigation,MobileTopNavigationDrawer,Footer,Banner,PageProgress,BrowserWindow,Framed,Anchor,Pagination,Search,DeveloperHint,CategoryHeading,AiAssistantMenuItems,NotFoundPage,ErrorPage,LanguageIcon,ScrollableTable,InlineCode,Markdown,Mermaid(module),errors/*,app/main.tsx;Layout.module.cssest créé mais lit encore les variables globales demain.css:166-220, que STY5 déplace.Heading,TypographyetMdxComponentssont à STY5. -
STY4b, navigation et plugins :
lib/components/navigation/*(12 fichiers),plugins/markdown/MdxPage.tsx,plugins/search-pagefind/*,plugins/search-inkeep/index.tsx(attribut de mode),lib/components/Slot.tsxsi besoin. -
STY4.1 — Chaque fichier selon §4.2 ; les valeurs dynamiques selon §0.3 permis 4 :
--columnsendata-columns,TocparuseCssVariable(après interaction), le reste classé fini ou continu à la lecture ; rien d'inline, et jamais le hook pour une valeur du premier rendu. -
STY4.2 — Captures des pages de doc identiques ou acceptées ligne à ligne (c'est le lot le plus visible) ;
TypographyetHeadinggardent leur API, leur intérieur change en STY5. -
STY4.3 —
node scripts/check-style-rule.js packages/siteà zéro horsui/(STY3) et horsTypography,Heading,MdxComponents,main.css(STY5).
Vérification : comme STY3, plus pnpm exec nx run-many -t build -p docs www presentation et
accessibility.spec.ts.
STY5 — Typographie, main.css et retrait de Tailwind (L)
Dépend de : STY3, STY4. Parallèle avec : rien (il touche le manifeste et main.css, que tout le
monde lit).
Fichiers : packages/site/src/lib/components/Typography.tsx + Typography.module.css,
Heading.tsx + Heading.module.css, packages/site/src/lib/util/MdxComponents.tsx,
packages/site/src/lib/components/Layout.module.css (créé par STY4a, reçoit les variables de mise
en page), packages/site/src/app/main.css, packages/site/src/lib/util/cn.ts (supprimé),
packages/site/package.json, pnpm-lock.yaml, packages/site/src/vite/plugin.ts,
plugin-theme.ts (@source, __pluginDirs), biome.jsonc (directives Tailwind),
packages/site/src/lib/core/plugins.ts (__pluginDirs reste pour les routes, perd son rôle CSS ;
documenté).
- STY5.0 — Typographie (§2.6) :
TypographyStylesProviderpartout oùProseClassesl'était,Typography.module.cssreporte règle à règlemain.css:57-164,HeadingsurTitleettheme.headings,MdxComponentsréécrit élément par élément avec unp; captures des pages de doc en clair et en sombre acceptées ligne à ligne. - STY5.1 —
main.cssdémonté : les variables de mise en page (main.css:166-220) dansLayout.module.css, le reset ethtml/body/scrollbar/.wavy-linedansapp/base.css(couchetheme, le seul CSS global du framework), les règles Shiki déjà parties en STY3c ; à la fin,main.cssn'a plus que les@importet la déclaration@layer. - STY5.2 —
tailwindcss()retiré deplugin.ts; le site se construit ; aucune classe utilitaire orpheline (galerie : rien ne doit bouger, puisque tout est en module) ;cn.tssupprimé. - STY5.3 — Dépendances de §6.1 lignes 1 et 5 à 7 retirées, sauf
next-themes(STY9). - STY5.4 — Mesure des bundles (§6.3) comparée à STY0.6.
Vérification : pnpm exec nx run-many -t build typecheck -p @recit/site docs www ; pnpm check ;
e2e deux fois ; galerie vide.
STY6 — Paquets Récit (L, en trois temps)
- STY6.1, outillage des paquets publiés (M) — dépend de STY0 ; parallèle avec STY2. Fichiers :
scripts/build-css-modules.mjs(sortie du spike, généralisé),packages/components,packages/formats/deck,packages/formats/motion-blocks,packages/integrations/site-stories(package.json: scriptbuild, export./styles.css,tsdown.config.ts),packages/cli/src/bundle/css.ts,js.ts,packages/cli/src/render/build-deck.ts,packages/cli/package.json,packages/integrations/site-stories/scripts/build-css.mjs,packages/theme/package.json(peertailwindcss),packages/theme/src/tailwind.css(supprimé),sites/storybook/.storybook/main.tsetpreview.css,preview.tsx+preview.module.css. Fournit :@recit/<paquet>/styles.css, la CLI sans Tailwind, Storybook sans Tailwind. Vérification :pnpm build;recit buildde chaqueexamples/deck-*;pnpm -F recit-storybook build; capturesdeck-renderidentiques. - STY6.2,
@recit/components(M) — dépend de STY6.1 ; parallèle avec STY3, STY7. Fichiers :packages/components/src/**(11 composants, 9 stories, modules). Vérification : tests du paquet,storybook.spec.ts,deck-render(les composants sont dans chaque deck). - STY6.3,
@recit/decketsite-kit(M) — dépend de STY6.2 ; parallèle avec STY4, STY8. Fichiers :packages/formats/deck/src/**(13 composants, modules),packages/tools/site-kit/src/components/*(+ modules),packages/integrations/site-stories/src/**(site-stories.module.css,mdx-components,MotionViewer,i18n),packages/cli/src/motion/client.tsx+ module,packages/cli/src/commands/motion-dev.ts. Les valeurs du premier rendu du deck suivent §0.3 permis 4 :data-aspect, dimensions de design et fond par scène émis parbuildThemeCss(packages/core/src/config/theme-css.ts, ajouté aux fichiers de ce lot), échelle calculée en CSS ; aucunuseCssVariabledans le deck. Vérification :deck-features(console présentateur, différence attendue et acceptée),deck-render,site-components,site-embed,motion-dev.
STY7 — Motion blocks (M)
Dépend de : STY6.1. Parallèle avec : STY3, STY6.2.
Fichiers : packages/formats/motion-blocks/src/** (styles.ts → blocks.module.css, ThemeVars,
les 8 blocs, player.tsx, ui/render.tsx, uiMock.tsx, compareBars.tsx),
packages/formats/motion-render/src/{Film,entry,RemotionAnim}.tsx,
scripts/style-rule-allowlist.json (les fichiers de la couche d'animation, et rien d'autre).
- STY7.1 —
blocks.module.cssetThemeVars; les blocs n'ont plus queclassNameet la coucheA. - STY7.2 —
render.tsxtrié : constant → module, par temps → couche ; le compte destyle:hors couche tombe à zéro. - STY7.3 —
goldens:checkvert sans régénération (le rendu est identique), sinon chaque golden qui change est justifié et régénéré pargoldens.
Vérification : pnpm exec vitest run packages/formats/motion-blocks packages/formats/motion-render
; pnpm -F @recit/motion-render goldens:check ; motion-dev et site-motion en e2e.
STY8 — Sites et exemples (S)
Dépend de : STY2 (le thème) et STY6.1 (modules dans un deck). Parallèle avec : STY4, STY6.3.
Fichiers : sites/www/src/*, sites/docs/src/StatusBadge.tsx,
sites/motion/src/VideoDownloads.tsx, sites/components/src/FormatSupport.tsx, leurs modules,
sites/{motion,presentation}/styles.css, sites/components/src/stories.css,
sites/components/stories/media.mdx (media-demo), examples/deck-mdx/components/*,
examples/deck-showcase/components/*, packages/tools/site-kit/src/config.tsx (rien à changer,
vérifié).
Vérification : site-www, site-docs, site-motion, site-components, deck-render ; galerie.
STY9 — Bascule finale (M)
Dépend de : STY5, STY6, STY7, STY8. Parallèle avec : STY10.2.
Fichiers : packages/site/src/lib/components/SiteRoot.tsx (pont .dark retiré, next-themes
retiré), packages/site/package.json, pnpm-lock.yaml,
packages/theme/src/{tokens,deck,page,print}.css (.dark → [data-mantine-color-scheme="dark"]),
packages/formats/deck/src/hooks/useThemeMode.ts,
packages/formats/deck/src/deck/Presentation.tsx, packages/cli/src/render/html-template.ts,
packages/cli/src/commands/export-pdf.ts, packages/components/src/slides/{CodeBlock,Mermaid}.tsx,
tools/mermaid-render.ts, packages/core/src/config/theme-css.ts (le bloc sombre),
packages/integrations/site-stories/scripts/build-css.mjs (.dark .recit-wrapper),
sites/storybook/.storybook/preview.tsx (withThemeByDataAttribute), scripts/check-style-rule.js
(mode erreur, baseline supprimée), biome.jsonc, .nx/version-plans/version-plan-style.md.
- STY9.1 — Un seul attribut de mode partout ;
next-themesparti. - STY9.2 — Le compteur à zéro, en erreur dans
pnpm check. - STY9.3 — Mesure finale des bundles ;
pnpm verify; e2e complet deux fois ; galerie passée en revue avec l'utilisateur ; captures darwin régénérées par lui. - STY9.4 — Version plan (§10.2).
Vérification : pnpm verify && pnpm verify:e2e (local) et pnpm test:e2e:linux deux fois.
STY10 — Skills, agents, documentation (S, en deux temps)
-
STY10.1 (S) — dépend de STY1 ; parallèle avec STY0 (le skill décrit la loi et la chaîne telles que ce plan les fixe). Fichiers :
.claude/skills/mantine-style/SKILL.md,CLAUDE.mdetAGENTS.md(section « Styling » en avertissement « in progress on branch style/mantine »). Pour que les agents des vagues suivantes écrivent déjà selon la loi. -
STY10.2 (S) — dépend de STY5 ; parallèle avec STY9 (aucun fichier commun, la forme finale est celle de ce plan). Fichiers :
CLAUDE.md,AGENTS.md(versions finales),CONTRIBUTING.md,NOTICE.md,sites/docs/pages/theme/{overview,custom-css,mantine}.mdx(mantine.mdxavec sa section « Migrating a site from Tailwind utilities », §6.4),sites/docs/sidebar.ts,sites/plans/pages/03-packaging.md,.claude/agents/style-reviewer.md(optionnel). -
STY10.2 — Fait le 30/10 :
AGENTS.mdfinal (« UI », « Styling », « Tests »),CONTRIBUTING.md,NOTICE.md,theme/mantine.mdxet les pages qui citaient Tailwind,03-packaging.md; pas d'agentstyle-reviewer(journal, note STY10.2).
Vérification : pnpm check (frontmatter, liens) ; pnpm exec nx run docs:build.
10.1 Les vagues
| Vague | Lots en parallèle | Condition d'entrée |
|---|---|---|
| 0 | STY1 | la branche existe (§11) |
| 1 | STY0, STY10.1 | le « avant » de STY1 généré et relu |
| 2 | STY2, STY6.1 | STY0 vérifié |
| 3 | STY3a, STY3b, STY3c, STY6.2, STY7 | STY2 et STY6.1 vérifiés |
| 4 | STY4a, STY4b, STY6.3, STY8 | STY3 vérifié (les trois), STY6.2 vérifié |
| 5 | STY5 | STY4 vérifié (les deux) |
| 5' et 6 | STY5c, STY9, STY10.2, TST5, FINALFIX | Faits (30/10) : Tailwind, shadcn/ui, Radix et next-themes retirés, un seul attribut de mode, compteur de style et compteur de stories à 0 en erreur, style-law.grit en erreur, version plan unique, documentation finale ; porte finale : deux passages e2e complets verts consécutifs (1 480 tests, 10,6 min), 1 659 références. Reste à l'utilisateur : captures darwin (STY9.3) et décision sur les couleurs de warm-editorial/saas-product |
Règles d'exécution, les mêmes que 18-mobile-desktop.md §6 : relire avant d'écrire, un lot = un
agent = ses fichiers, un fichier manquant se demande au coordinateur, jamais de git, jamais de
formatage global, une note ### STYn — <titre> — <date> en fin de journal.md avec les commandes,
les comptes du compteur, la liste des captures acceptées et pourquoi. Chaque vague se termine par
un passage e2e dans le conteneur, lancé par le coordinateur sur un arbre stable, puis par une revue
de galerie par l'utilisateur avant la suivante : c'est la seule porte qui demande un humain.
10.2 Version plan
Tous les paquets publiés changent de contrat CSS : @recit/theme perd tailwind.css et son peer,
@recit/components, @recit/deck, @recit/motion-blocks, @recit/site-stories gagnent
./styles.css, @recit/cli ne compile plus Tailwind. En 0.x (CONTRIBUTING.md §5), c'est un
prerelease : la migration n'ouvre pas de phase de la feuille de route (06-roadmap.md, P5 à P12),
elle traverse les phases en cours. Un seul plan, .nx/version-plans/version-plan-style.md, écrit
par STY9, prerelease pour @recit/core (theme-css.ts change le bloc sombre), @recit/theme,
@recit/components, @recit/deck, @recit/motion-blocks, @recit/motion-render,
@recit/site-stories, @recit/cli, avec la ligne de changelog « Styling: Mantine and CSS modules
replace Tailwind, shadcn/ui and Radix; @recit/theme/tailwind.css is gone, each package ships
styles.css; a site's own MDX, mdx.components, slots and components can no longer use Tailwind
utility classes: write a stylesheet (theme.customCss) or a CSS module; theme.registryUrl is
removed ». Si, au moment de fusionner, l'utilisateur veut en faire le premier jalon d'une phase,
preminor ; question ouverte Q6.
10.3 Effort
| Vague | Lots | Agents | Taille cumulée |
|---|---|---|---|
| 0 | STY1 | 1 | M |
| 1 | STY0, STY10.1 | 2 | M + S |
| 2 | STY2, STY6.1 | 2 | M + M |
| 3 | STY3a-c, STY6.2, STY7 | 5 | L + M + M |
| 4 | STY4a-b, STY6.3, STY8 | 4 | L + M + S |
| 5 | STY5 | 1 | L |
| 5' et 6 | STY5c, STY9, STY10.2, TST5, FINALFIX | Faits (30/10) : Tailwind, shadcn/ui, Radix et next-themes retirés, un seul attribut de mode, compteur de style et compteur de stories à 0 en erreur, style-law.grit en erreur, version plan unique, documentation finale ; porte finale : deux passages e2e complets verts consécutifs (1 480 tests, 10,6 min), 1 659 références. Reste à l'utilisateur : captures darwin (STY9.3) et décision sur les couleurs de warm-editorial/saas-product |
Dix-sept lots d'agent, sept vagues, sept revues de galerie (la première, après STY1, ne fait que
relire le « avant »). Environ 950 className du framework, 160 des paquets, 60 des sites et
exemples, 120 style des blocs motion à trier, 54 composants ui/ à réécrire ou supprimer, 55 + 12
composants du framework, 11 + 13 des paquets, 4 de site-kit, 7 de sites et d'exemples. Une vague
par journée de travail d'agents est l'ordre de grandeur, les revues humaines en plus.
11. La branche
-
Nom :
feature/mantine, créée par l'utilisateur depuismainle 29/10/2026 ; les agents n'en créent aucune (règle globale). -
Gel de
main: STY0 est le seul lot dont une partie peut aller surmainen avance, si l'utilisateur le veut :scripts/check-style-rule.jsen mode avertissement avec sa baseline, qui refuse toute hausse ; ainsi aucun nouveauclassNameutilitaire n'entre dansmainpendant la migration. Le reste du plan ne touche pasmain. -
Rebase : à la fin de chaque vague, l'utilisateur rebase
style/mantinesurmain(les agents ne le font pas) ; les conflits attendus sont dansjournal.md(ajouts des deux côtés, triviaux) et dans les fichiers que le plan 18 touche encore (packages/tools/app/**,.github/,sites/docs/pages/documentation/configuration/app*.mdx) : aucun d'eux n'a de style, la cohabitation est sans conflit par construction. Une nouvelle page de doc arrivée surmainreçoit sa capture au rebase suivant (STY1 la liste). -
Fusion : une seule fois, après STY9 et la revue finale, avec les
-linux.pngacceptées ; la CI (build.yaml,test:e2e) doit être verte sur la branche avant. Pas de fusion partielle : un site à moitié migré a deux systèmes de style, et c'est ce que le plan interdit. -
Tauri (plan 18) : APP9 et APP10.2 touchent la coquille Rust et
window-open.js; ni l'un ni l'autre n'a de CSS. Le bundle Tauri (site-app-bundle.spec.ts) suitdist/du site, donc il change de style avec le site : ses trois captures sont dans STY1. -
Abandon : la branche peut être abandonnée à n'importe quelle vague sans effet sur
main, qui n'a rien reçu d'elle. Ce qui vaut d'être repris seul dans ce cas, chacun dans un commit propre surmainà la demande de l'utilisateur : la couverture de captures et la galerie de STY1 (utiles quel que soit le style), le compteurcheck-style-rule.jsen avertissement avec sa baseline, et le skillmantine-styleréduit à sa table Tailwind → CSS si Mantine n'est pas retenu. Une branche à moitié migrée n'est jamais fusionnée : deux systèmes de style dans un site, c'est ce que ce plan interdit.
12. Risques
| Risque | Parade |
|---|---|
| Les modules d'un paquet publié : trois bundlers, trois comportements | STY0.3 et STY0.4 tranchent avant tout le reste par la compilation à la construction (noms stables, une feuille) ; repli : modules copiés en source dans dist |
La collecte SSR en dev ne voit pas le CSS d'un module (css/plugin.ts hook transform) | STY0.2 le prouve ou corrige ; en build, Vite range les modules dans les chunks CSS comme tout CSS |
| Différences visuelles massives d'un coup (ombres, focus, rayons de Mantine) | Thème calé sur les tokens actuels (STY2.5 : galerie vide) ; un composant à la fois ; classNames ramène chaque écart ; la galerie rend chaque écart visible |
PresenterView change de palette (slate codée en dur → tokens) | Différence voulue, annoncée ici, acceptée en galerie |
Les classes compilées par @recit/core dans le HTML (slide-*) ne peuvent pas être hachées | deck.css reste global (§5.4), composes … from global fait le pont |
| Remotion : un module CSS dans le bundle webpack | Le paquet livre styles.css déjà compilé ; l'entrée l'importe comme fonts.css ; goldens avant/après |
prose disparaît : la typographie du MDX bouge | Typography.module.css reporte règle à règle main.css:57-164 ; captures des 38 pages de doc en clair et en sombre |
| Taille du CSS du site (feuille Mantine) | Mesurée en STY0.6 et STY5.3 ; import par composant si besoin ; le JS d'entrée baisse en face |
| Les props de style de Mantine sont tentantes et partout dans sa doc | Interdites par le compteur (§8.1-3) et par le skill ; la revue de diff les attrape |
Le pont .dark oublié quelque part après STY9 | La liste des vingt fichiers de §1.4 est la check-list de STY9.1 ; grep -rn '"dark"' packages sites doit ne rendre que les valeurs de config |
| Deux sessions sur la même branche | Les règles de 18-mobile-desktop.md §6 (relire avant d'écrire, un fichier un lot) |
Une baseline -linux régénérée par erreur masque une régression | Seul test:e2e:update:linux <spec> écrit, après galerie, avec la raison dans le journal ; le diff git des PNG est relu avant fusion |
vite/client et le typage des modules dans les paquets publiés | Déclaration générée par build-css-modules.mjs (STY0.3), typecheck de chaque paquet dans sa vérification |
13. Questions ouvertes
- Props de style de Mantine (
p,m,c,bg…) : interdites ici par cohérence avec « aucun style dans le JSX ». Les composants de mise en page (Group,Stack,Grid) gardentgap,justify,align, qui sont leur raison d'être. Confirmer ces deux lignes avant STY3. - Recherche :
@mantine/spotlight(proposé) ou une boîte en module surModalsans paquet de plus ? deck.cssglobal (proposé, §5.4) ou découpé en modules avec un contrat de classes publiques maintenu à la main ? Le global coûte moins et ne casse aucun récit.motion/reactgardé pour les transitions de diapositives (proposé) : Mantine n'a pas d'équivalent deAnimatePresence.- Défauts du site : rester zinc (shadcn) ou passer au violet de
@recit/theme? La galerie de STY2 montre les deux ; iso-rendu proposé (zinc) pour cette migration, violet pour le plan de refonte. - Version :
prerelease(proposé) oupreminorau moment de fusionner ? - Les pages héritées du framework d'origine dans
sites/docs: elles n'ont ni classe ni style (§1.3), donc rien à faire ; mais leur capture change avec le framework : est-ce que l'utilisateur veut les relire une à une en galerie, ou accepter la specsite-docsen bloc après revue d'échantillon ? - Export PDF (
export-pdf.ts,?print=1,print.css) : accepter des différences de pagination dues aux polices ou aux marges de Mantine, ou viser l'identité ? Proposé : identité,print.cssne change pas. - Captures mobiles : un seul viewport (390×844) ou deux (plus 768×1024) ?
- Le nom
@recit/site/ui/*: garder ces exports (proposé, poursite-kitet les sites qui les prendraient) ou exposer Mantine directement et supprimer la couche ? Garder évite une rupture et permet desclassNamespar défaut cohérents. - Le projet Playwright en mode sombre :
colorScheme: "dark"émuleprefers-color-scheme, quenext-themesn'honore que si le choix mémorisé estsystem(et Mantine, que sidefaultColorSchemeestautoet qu'aucune valeur n'est mémorisée). Le helperdarkModedee2e/support.tsdoit donc poser le choix mémorisé : la clé denext-themes(theme) aujourd'hui, la clé de Mantine (recit-color-scheme, §2.5) après STY2, les deux jusqu'à STY9. STY1 crée le helper, STY2 l'étend (fichier ajouté à STY2). Faut-il garder le projetchromium-darkpar émulation, ou capturer chaque page en sombre par le helper seul ?