Fondations

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é en classes ; 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

DomaineAujourd'huiAprès
CSSTailwind v4 (@apply, @theme, utilitaires), CSS global, objets inlineModules CSS par composant, CSS global limité à @recit/theme et à Mantine
TokensVariables HSL de @recit/theme/tokens.css, variables oklch shadcn du siteUn seul jeu de variables, exposé à Mantine par cssVariablesResolver (§2.4)
Typographie@tailwindcss/typography (prose), Heading en cva, Geist, InterTypographyStylesProvider, theme.headings, mêmes polices (§2.6)
IcônesLucide (lucide-react)Lucide, inchangé : Mantine n'embarque pas d'icônes (§2.7)
Animationmotion/react (deck, navigation), tw-animate-css, Remotionmotion/react gardé ; Transition de Mantine pour l'interface ; Remotion
Mode de couleurnext-themes + classe .dark sur <html>ColorSchemeScript + data-mantine-color-scheme (§2.5)
Thème publicSection 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.

  1. className={classes.x} où classes vient du module X.module.css placé à côté de X.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.
  2. 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, gap sur 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 par classNames.
  3. L'API classNames de Mantine branchée sur un module : classNames={{ root: classes.root, label: classes.label }}.
  4. 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) le vars d'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 hook useCssVariable(ref, "--name", value) fourni par @recit/site/hooks et 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 de SlideStage → calcul CSS (container-type: size sur le viewport du deck, aspect-ratio depuis data-aspect, ensemble fini 16:9/4:3, et scale: min(tan(atan2(100cqw, var(--deck-design-width))), tan(atan2(100cqh, var(--deck-design-height)))), les dimensions de design étant émises par buildThemeCss dans le <style> de données ; ou, plus simple, une diapositive dimensionnée en cqw/cqh sans mise à l'échelle) ; le fond de SlideFrame → variable par scène émise par buildThemeCss ([data-scene=\"…\"] { --slide-bg: … }, lue par le module) ; les {width, height} du mode impression → les mêmes variables de design ; --columns du Footer → data-columns (1 à 6) ; la barre de progression → le prop value de Progress (Mantine écrit sa propre variable) ; l'indicateur de Toc → 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.
  5. 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 saute theme.noDefaultTheme) et une feuille de base par site (sites/*/styles.css) réservée aux classes qu'une page MDX écrit elle-même.
  6. Le <style> qui injecte story.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.

  1. Toute chaîne littérale dans className (guillemets ou gabarit de chaîne), y compris une classe sémantique de deck.css : un composant qui veut la classe slide-card écrit dans son module .card { composes: slide-card from global; } et utilise classes.card.
  2. cn, clsx, twMerge, cva, class-variance-authority, tailwind-merge.
  3. style={{…}} et style={variable} dans le JSX, style: dans un createElement, le prop style, styles, sx, vars et les props de style de Mantine (p, px, m, mt, c, bg, fz, fw, w, h, maw, pos, display, ta, lh, bd, opacity…).
  4. @apply, @theme, @custom-variant, @source, @plugin, @utility et tout import de tailwindcss.
  5. <style> inline et dangerouslySetInnerHTML portant du CSS, hors les deux cas de données ci-dessus.

Exceptions, nommées et limitées.

ExceptionPourquoiOù
La couche d'animation des blocs motion : un objet style calculé par image est fusionné dans l'élémentUne valeur par image (Remotion) ou par pas (Framer) ne peut pas vivre dans un module statiquepackages/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èneLe thème d'un film arrive par inputProps (Remotion), pas par une feuille : il doit devenir des variables CSS une fois, à la racineUn composant ThemeVars unique dans motion-blocks (§5.6), dans la même liste d'exclusion
Les deux <style> de donnéesVariables 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: du recit.config ni des classes sémantiques que @recit/core compile 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)

MesureValeur
Occurrences de className947, 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-state62 (21 fichiers) / 18 / 36
Modules CSS0
Chaînes de classes hors JSXHIGHLIGHT_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és21 (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)
Tailwindtailwindcss, @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èmesrc/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 sombrenext-themes <ThemeProvider attribute="class"> (src/lib/components/SiteRoot.tsx:75) ; aucun script de tête maison ; ThemeSwitch.tsx
SSRsrc/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

PaquetMécanismeclassName="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-*…)26212000
themeCSS 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 tailles75812100
formats/motion-blocks (13 tsx)Inline seulement : src/styles.ts makeStyles(theme) → CSSProperties ; src/ui/render.tsx 1 490 lignes002731600
formats/motion-render (5 tsx)Inline (Film.tsx:30) ; le bundle Remotion n'importe que @recit/theme/fonts.css (src/entry.tsx:4)001010
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>2561101
tools/site-kit (6, privé)Tailwind dans le JSX, compilé par le site consommateur ; Tutorial.tsx utilise prose prose-sm dark:prose-invert2410000
clisrc/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 styles002800
corePas 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 / exempleComposants .tsx et classesCSS propreMDX avec classes
wwwHero 9, FormatGrid 11 ([&_pre]:max-h-[460px], text-[10px]), OneSource 2–0
docsStatusBadge 1–0
motionVideoDownloads 1styles.css 24 lignes (.motion-grid, .typography)3 (motion-grid)
componentsFormatSupport 1 (emerald, dark:)src/stories.css 6 lignes (.metric-value)1 (media-demo, sans règle CSS)
presentation–styles.css 8 lignes0
plans, vision––0
storybook.storybook/preview.tsx : 2 classes, 2 style (décorateur withSlide)preview.css : @import "tailwindcss" + thème + @source–
site-kitCards 9, ExampleHeader 9, Tutorial 6, SourceFile 1––
deck-mdxcomponents/index.tsx : Counter, Timeline, 4 classes––
deck-showcasecomponents/index.tsx : Metric, 4 classes (emerald) ; réutilisé par docs, components, presentation, site-i18nstyles.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/typography et --tw-prose-* surchargés (main.css:57-108), .prose en @utility (main.css:110-164), Typography.tsx (prose dark:prose-invert typography), Heading.tsx en cva (text-4xl font-extrabold … text-md), MdxComponents.tsx : h1–h6 → Heading, a → AnchorLink, img rounded-lg, table → ScrollableTable, pre not-prose my-4, code → InlineCode/CodeBlock, douze alias de Callout ; pas de p.
  • Decks : deck.css .slide h1 3.5rem, h2 2.5rem, h3 1.5rem (L81-97, surcharges L565, L582) ; polices fonts.css (Inter, JetBrains Mono, Space Grotesk, Space Mono, Yeseva One, Josefin Sans).
  • Mode sombre : la classe .dark sur <html> est lue par 20 fichiers, dont packages/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 projet chromium 1280×720, maxDiffPixelRatio: 0.02, animations: "disabled", baselines e2e/__screenshots__/<spec>/<nom>-<platform>.png ; 213 -linux.png et 198 -darwin.png dans git ; Docker via scripts/e2e-linux.{js,sh} (mcr.microsoft.com/playwright:v1.64.1-noble, linux/amd64) ; test:e2e:update:linux seul à écrire une baseline Linux.
  • Couverture : deck-render 91 (six exemples, clair et sombre), site-docs 38, site-components 29, site-presentation 19, motion-dev 11, site-motion 11, site-vision 6, site-www 3, site-embed 3, site-i18n 1, deck-features 1 (console présentateur) ; site-plans, site-app-bundle et accessibility (axe sur 3 URL) sans capture.
  • Non couverts : les trois pages app* de la doc, les 28 pages de plans, /present/bridge, l'index et le récit anglais de site-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, vision sur darwin.
  • Storybook (sites/storybook) : addons docs, a11y (test: "error"), themes ; aucun test-runner ni capture. Goldens motion : packages/formats/motion-render goldens: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, className ou le style inline.
  • biome.jsonc : css.parser.tailwindDirectives, noUnknownAtRules ignore plugin, theme, custom-variant, apply ; noImportantStyles coupé pour main.css ; noRestrictedImports interdit @recit/site sauf site, site-stories, site-kit ; aucune règle sur les classes.
  • .oxfmtrc.json : les .css sont formatés, un .module.css le sera aussi ; lefthook.yml : biome lint --write et pnpm fmt sur 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:38 et theme/custom-css.mdx:4,25 parlent de Tailwind.

2. Cible

2.1 Mantine, version et paquets

  • @mantine/core et @mantine/hooks 9.7.4 (relevé le 29/10 sur le registre ; peer react ^19.2.0, satisfait). Mantine est sans Emotion depuis la v7 : feuille CSS statique (@mantine/core/styles.css), variables CSS, MantineProvider sans contexte de style, rendu serveur par ColorSchemeScript. STY0 vérifie la surface d'API de la v9 (noms de classNames, cssVariablesResolver, Transition, TypographyStylesProvider) avant d'écrire une ligne.
  • postcss-preset-mantine 1.18.0 et postcss-simple-vars 7.0.1 : rem(), light-dark(), les mixins light/dark/smaller-than/larger-than, dans chaque bundler qui compile un module (§2.2).
  • @mantine/spotlight 9.7.4 remplace cmdk pour la boîte de recherche (Command.tsx, PagefindSearch.tsx) : même rôle, même paquet éditeur, une dépendance en moins. @mantine/modals n'est pas pris : un Dialog existe déjà dans core (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/react restent ; 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ùCompilateurAprè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 JScss.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-wrapperPostCSS 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 viteFinalVite + 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 RemotionInchangé : 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

CSSCSS
@layer mantine, theme, recit, site;
  • mantine : @mantine/core/styles.layer.css et @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 le virtual:recit-theme.css du site (variables d'un site, §2.4).
  • recit : les modules des paquets et du framework.
  • site : la feuille d'un site (sites/*/styles.css) et theme.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 configAujourd'hui (plugin-theme.ts)Après
light.*, dark.* (20 clés shadcn : background, primary, card, muted, border, ring, radius…):root {…} et .dark {…} en variables shadcnLes 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
registryUrlfetchShadcnRegistryItem (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
customCssInjecté dans main.cssÉmis en couche site, inchangé pour l'utilisateur
noDefaultThemeSaute defaultTheme.cssSaute 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

  • ColorSchemeScript dans <head> (via PluginHeads/Meta, @unhead/react) et localStorageColorSchemeManager({ key: "recit-color-scheme" }) dans MantineProvider ; defaultColorScheme depuis le config ("auto" quand rien n'est dit). Plus de next-themes, plus de ThemeProvider dans SiteRoot.tsx:75, useTheme de @recit/site/components et hooks réimplémenté sur useMantineColorScheme() (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.colorMode dans le config, défaut system), et compile-story.ts:177-255 le mappe déjà pour les decks.
  • Le pont .dark pendant la migration : vingt fichiers lisent la classe .dark (§1.4). Jusqu'à STY9, un effet dans SiteRoot recopie data-mantine-color-scheme vers la classe .dark de <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 vers light-dark() dans les modules) et retire le pont ; le deck seul et le gabarit CLI, qui n'ont pas Mantine, lisent data-mantine-color-scheme par un script de démarrage équivalent à celui d'aujourd'hui, pour que deck.css ait une seule condition de mode.
  • Dans les modules, le mode s'écrit light-dark(a, b) ou avec les mixins @mixin dark : plus de variante dark:.

2.6 Typographie

  • TypographyStylesProvider de Mantine remplace @tailwindcss/typography et ProseClasses (Typography.tsx, MdxPage.tsx, Markdown.tsx) : Typography.module.css reporte les règles de @utility prose et @utility typography (main.css:57-164 : liens, code, citations, hr, listes de tâches, h2 mt-8 mb-4) sur le sélecteur du provider.
  • theme.headings : les six niveaux de Heading.tsx (cva) deviennent theme.headings.sizes (h1 2.25rem/800, h2 1.5rem/700, h3 1.25rem/600, h4 1.25rem, h5 1.125rem, h6 1rem), et Heading devient Title + Heading.module.css (ancre .anchor, .link).
  • MdxComponents.tsx réécrit élément par élément : h1–h6 → Heading (Title), a → AnchorLink (module), img/vidéo → .media (module), table → ScrollableTable (module + Table de Mantine), pre → .pre (module), code → InlineCode (Code de Mantine) ou CodeBlock, les douze alias de Callout → Alert de Mantine avec classNames, et un p explicite (il manque aujourd'hui) pour que TypographyStylesProvider et 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émentAvantAprès (Mantine theme.headings ou deck.css)
Siteh1text-4xl font-extrabold (2.25rem/800)2.25rem/800, lineHeight 1.2
Siteh2text-2xl font-bold (1.5rem/700)1.5rem/700, marges 2rem 0 1rem
Siteh3text-xl font-semibold (1.25rem/600)1.25rem/600
SitecorpsTailwind prose (1rem/1.75)theme.fontSizes.md 1rem, lineHeights.md 1.75
Deckh1/h2/h3deck.css 3.5/2.5/1.5reminchangé (deck.css reste)

2.7 Icônes et animation

  • lucide-react reste la seule bibliothèque d'icônes ; @recit/site/icons inchangé ; les composants Mantine reçoivent une icône Lucide par leftSection/rightSection.
  • motion/react reste pour les transitions de diapositives (transitions.ts, AnimatePresence) et la navigation du site (motionFeatures.ts, règle LazyMotion/m.* du CLAUDE.md) ; les entrées/sorties d'interface (menus, dialogues, drawers) utilisent Transition et les transitions intégrées des composants Mantine ; tw-animate-css et les classes animate-*/data-state disparaissent.

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 dans CodeBlock.module.css avec light-dark(var(--shiki-light), var(--shiki-dark)) ; defaultColor: false de shiki.ts:81 reste. Les thèmes Shiki restent configurables par syntaxHighlighting.themes.
  • Mermaid : initialize({ theme }) lit le schéma Mantine (useMantineColorScheme) ; les libellés HTML gardent la règle de deck.css:528.
  • KaTeX : rien dans le site (aucune feuille), inchangé dans la CLI (buildKatexCss).

3. Conventions des modules

  1. Un module par composant, Button.module.css à côté de Button.tsx, importé import classes from "./Button.module.css". Un fichier qui exporte plusieurs composants a un module par composant exporté, ou se découpe.
  2. Nommage : classes en camelCase (.root, .label, .isOpen), une classe racine .root, des états par classe ou par data-* (&[data-active]), des variantes par data-variant ; pas de BEM, le module isole déjà.
  3. 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/theme seulement dans les paquets de format (deck, components) qui ne chargent pas Mantine.
  4. Points de rupture : les mixins @mixin smaller-than $mantine-breakpoint-md ; jamais une valeur en pixels écrite deux fois.
  5. Imbrication : deux niveaux au plus ; au-delà, une classe.
  6. Typage : declare module "*.module.css" de Vite (vite/client) dans packages/site, et un css-modules.d.ts généré avec la table de classes dans les paquets publiés (STY0), pour que classes.inexistante soit une erreur de typecheck.
  7. Sous-éléments Mantine : classNames={{ root: classes.root, section: classes.section }} ; un composant qui n'a pas de sous-élément reçoit className={classes.root}.
  8. Dynamique : §0.3 permis 4 ; un data-* quand les valeurs sont finies, useCssVariable sinon ; le module lit var(--x, repli).
  9. 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-bundle ne voient aucun bruit de hachage.
  10. Storybook : chaque composant de @recit/components garde sa story ; les stories n'ont ni classe ni style (le décorateur withSlide passe en module preview.module.css).
  11. 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éeImporteurs (int / @ui)Après
Button, ButtonGroupreact-slot, cva12 / 5 ; 0 / 1Mantine Button, Button.Group ; asChild → component/renderRoot
Alert, DismissibleAlertcva1 / 3 ; 0Mantine Alert (withCloseButton) ; DismissibleAlert supprimé
Callout–2-3Mantine Alert + Callout.module.css (12 types par data-type, --callout-color → variable de module)
Badgereact-slot, cva2 / 0Mantine Badge
Separatorreact-separator2 / 3Mantine Divider
Dialogreact-dialog, react-visually-hidden0 / 3Mantine Modal (+ VisuallyHidden)
Drawervaul2-3Mantine Drawer
Commandcmdk, react-dialog0 / 2@mantine/spotlight
DropdownMenureact-dropdown-menu0 / 2Mantine Menu
Popover, HoverCardreact-popover, react-hover-card0 / 1 ; 0Mantine Popover, HoverCard ; HoverCard supprimé
Tooltipreact-tooltip1 / 2Mantine Tooltip
Kbd–0 / 1Mantine Kbd
Input, InputGroup, Label, Textarea, NativeSelect, Selectreact-label, cva, react-select1 ; 0/1 ; 1 ; 1 ; 0 ; 0Mantine TextInput, Input.Wrapper, Textarea, NativeSelect ; Select et NativeSelect supprimés
Card–1Mantine Card
Collapsiblereact-collapsible1Mantine Collapse (les keyframes .CollapsibleContent de main.css disparaissent)
NavigationMenureact-navigation-menu, cva1module (HeaderNavigation) + Menu pour les sous-menus
Stepper–1module (@layer utilities .stepper de main.css:423-470 → Stepper.module.css)
Toggle, ToggleGroup@base-ui/react, cva1 ; 0Mantine SegmentedControl (ToggleGroup supprimé) ou ActionIcon
CodeBlock, CodeTabPanel, CodeTabs, EmbeddedCodeBlock, SyntaxHighlightreact-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, Valueaccordion, alert-dialog, aspect-ratio, embla-carousel-react, checkbox, react-hook-form, slot+cva, progress, radio-group, scroll-area, slider, switch, tabs0supprimés (27 avec ceux ci-dessus) ; le Pagination de lib/components n'est pas celui-ci
util.tsx (cn, focusRing)clsx, tailwind-mergepartoutsupprimé ; focusRing devient :focus-visible dans chaque module

4.2 Le framework hors ui/

Fichier(s)classNameAprès
LandingPage.tsx34Container, Title, Text, SimpleGrid, Card + module
plugins/markdown/MdxPage.tsx27TypographyStylesProvider, Group, Menu + module ; ButtonGroup, DropdownMenu, Popover déjà migrés en §4.1
Header.tsx, HeaderNavigation.tsx, TopNavigation.tsx, MobileTopNavigation.tsx, MobileTopNavigationDrawer.tsx19, 13, 6, 2, 18AppShell.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.tsx17module ; --columns → data-columns (1 à 6)
Layout.tsx, Main.tsx, Banner.tsx, PageProgress.tsx, Spinner.tsx2, 5, 3, 1, 1AppShell, 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.tsx18, 14, 12, 9, 8, 6, 3, 4, 3, 2AppShell.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.tsx15, 6, 6, 10, 6, 4, 3, 3, 3, 3, 3, 1, 2, 2, 2, 1, 5, 2modules ; ThemeSwitch sur useMantineColorScheme + ActionIcon ; Heading/Typography §2.6
errors/ErrorAlert.tsx, RouterError.tsx3, 2Alert + module
plugins/search-pagefind/PagefindSearch.tsx, ResultList.tsx, IndexingDialog.tsx9, 8, 5Spotlight + modules ; l'extrait HTML (dangerouslySetInnerHTML) garde une classe de module pour ses <mark>
app/main.tsx1import 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

SurfaceFichiersAprès
@recit/componentsCover, Points, Grid, Quote, Callout, Media, CodeBlock, CodeGroup, Mermaid, mdx/builtins.tsx (Card, Columns, Col, Left, Right, Image, Badge, Kbd, Step), SlideMotionun 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/deckPresenterView (28), Presentation (13), MobileRemoteView (12), RemoteQrCodeModal (11), SlideFrame (8), AnnotationCanvas, SlideStage, HtmlSlide, DevHud, MotionViewermodules ; 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/themetailwind.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-blocksstyles.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-renderFilm.tsx:30AbsoluteFill avec className de module ; fond par variable posée par ThemeVars
@recit/site-storiesmdx-components.tsx (10), MotionViewer (7), i18n.tsx (4), StoryViewer, StoryDocument, plugin.tsxsite-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-kitCards (9), ExampleHeader (9), Tutorial (6), SourceFile (1)modules ; Tutorial prose prose-sm → TypographyStylesProvider + module (counter-increment en CSS)
sites/wwwHero, FormatGrid, OneSourcemodules (les valeurs arbitraires max-h-[460px], text-[10px] deviennent des règles)
sites/docs, motion, componentsStatusBadge, VideoDownloads, FormatSupportBadge, module, module
sites/motion/styles.css, presentation/styles.css, components/src/stories.cssclasses de contenu MDXrestent (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-showcaseCounter, Timeline, Metricmodules compilés par esbuild (§2.2) ; deck-showcase/styles.css reste (customCss)
sites/storybookpreview.tsx, preview.css, main.tspreview.module.css, feuille Mantine + thème + feuilles des paquets, PostCSS ; withThemeByDataAttribute (data-mantine-color-scheme)
packages/cli/src/motion/client.tsx, commands/motion-dev.tsobjet styles, <style> inline de la page de devclient.module.css bundlé par esbuild ; la page de dev charge la feuille

5. Chaque surface, le chemin technique

5.1 Le framework de site

  1. Socle (STY0) : @mantine/core, @mantine/hooks, PostCSS ; MantineProvider dans SiteRoot à côté du ThemeProvider de next-themes (coexistence le temps de la migration : les deux posent leur attribut, le pont §2.5 les accorde) ; @mantine/core/styles.layer.css importé dans main.tsx avant main.css ; @layer déclaré en tête de main.css ; css.modules configuré ; un premier module (Spinner.module.css) prouve la chaîne dev, SSR, build, prérendu, capture.
  2. Thème (STY2) : plugin-theme.ts garde l'émission des variables et des polices, perd le registre shadcn ; un nouveau src/lib/theme/createRecitTheme.ts lit virtual:recit-config et construit createTheme + cssVariablesResolver ; ColorSchemeScript dans la tête.
  3. Primitives (STY3) puis composants (STY4) : fichier par fichier, dans l'ordre des importeurs (un ui/ d'abord, ses consommateurs ensuite), chaque fichier perdant son cn et ses chaînes le jour où il reçoit son module ; pnpm check compte les violations qui restent (§8).
  4. Retrait (STY5) : tailwindcss() retiré de plugin.ts, @source et __pluginDirs retirés de plugin-theme.ts:349, main.css ré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 de compile()/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 que js.ts a émis pour les modules des composants utilisateur, de customCss, puis KaTeX et polices comme aujourd'hui ; @tailwindcss/node et oxide quittent packages/cli/package.json.
  • js.ts : write: false et outputFiles existent déjà ; un .module.css importé par un composant utilisateur produit un fichier .css dans outputFiles (loader local-css natif d'esbuild) ; js.ts le rend dans JsBundle.css, build-deck.ts le passe à css.ts. Les noms locaux suivent le même motif que Vite (option localIdentName… 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 pose data-mantine-color-scheme et la classe .dark jusqu'à STY9, puis l'attribut seul.
  • export-pdf.ts et ?print=1 : print.css inchangé ; captures de deck-features avant/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) devient blocks.module.css, dont les valeurs de thème sont lues en var(--mb-bg), var(--mb-text), var(--mb-font-body), var(--mb-type-display)… ; les unités cqw et containerType: size passent telles quelles en CSS.
  • ThemeVars : un composant racine (dans engine.tsx ou themes/) qui pose les variables --mb-* depuis le Theme reçu par inputProps. 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.tsx sampleSpec) reste : elle fusionne un objet par image. Ce qui est statique dans render.tsx (57 style: {) 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 captures motion-dev de e2e.

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

PaquetDéclaré dansImporteurs avant
tailwindcss, @tailwindcss/vite, @tailwindcss/typography, tw-animate-csspackages/siteplugin.ts, main.css
tailwindcss, @tailwindcss/node, @tailwindcss/oxidepackages/cli, packages/integrations/site-stories, cataloguebundle/css.ts, build-css.mjs
tailwindcss (peer optionnel)packages/themetailwind.css
@tailwindcss/vite, tailwindcss (catalogue)sites/storybookmain.ts, preview.css
tailwind-merge, clsx, class-variance-authoritypackages/sitecn.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/site7, 3, 3, 2, 2, 2, puis 1 ou 0
@base-ui/react, cmdk, vaul, embla-carousel-react, next-themes, react-hook-form, @tanem/react-nprogresspackages/site2, 1, 1, 1, 4, 1, 1

6.2 Ajoutées

PaquetOùRôle
@mantine/core, @mantine/hooks 9.7.4packages/site (dépendance), sites/storybook (dev), cataloguecomposants, thème, hooks
@mantine/spotlight 9.7.4packages/siterecherche
postcss-preset-mantine, postcss-simple-varspackages/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.customCss continue de marcher (couche site), 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.registryUrl part avec un diagnostic (§2.4).
  • Les composants de @recit/site/ui/* gardent leurs noms et leurs props (§4.1) ; cn de @recit/site/ui/util disparaî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 :

AjoutSpecDétail
Les 28 pages de planssite-plans.spec.tspleine page, comme vision
Les 3 pages app* de la doc, /present/bridge, l'index et le récit anglais de site-i18nsite-docs, site-motion, site-i18najout aux listes PAGES
Mode sombre de chaque page de siteun 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 ouvertla 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èresite-docspage.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 sombrestorybook.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 deckdeck-featuresPresenterView change de palette : il faut la voir
Les goldens motionmotion-render goldensdé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

  • maxDiffPixelRatio reste 0.02 pendant la migration, pour voir ; animations: "disabled" ; mermaidReady, filmsReady, decksReady, heightStable de e2e/support.ts inchangés ; les polices sont embarquées (Geist, Inter) donc stables dans le conteneur.
  • Chaque vague de §10 se termine par pnpm test:e2e:linux deux fois (règle du skill e2e-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 un classNames peut casser un contraste : c'est le test qui le dit.
  • Le coût d'un passage en conteneur : scripts/e2e-linux.sh copie 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 dans e2e-linux.sh quand le hachage de pnpm-lock.yaml n'a pas changé (node_modules gardé 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 :

  1. 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.
  2. style={ et style: dans un createElement, hors fichiers de la liste d'exclusion.
  3. 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.
  4. Un import de clsx, tailwind-merge, class-variance-authority, tailwindcss, cmdk, vaul, next-themes, @radix-ui/*, @base-ui/*.
  5. Dans un .css hors .module.css et hors @recit/theme et sites/*/styles.css : toute règle.
  6. Dans un .css : @apply, @theme, @custom-variant, @source, @plugin, @utility, dark:.
  7. Un .tsx qui n'importe aucun module et contient un className (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 : noRestrictedImports gagne les paquets de §8.1-4 avec un message qui renvoie au skill ; css.parser.tailwindDirectives et les noUnknownAtRules ignorés (plugin, theme, custom-variant, apply) retirés à STY9 ; noImportantStyles partout ; les .module.css sont lintés comme les .css (déjà dans includes).
  • .oxfmtrc.json : rien à faire, .module.css est un .css.
  • lefthook.yml : rien à faire.
  • Typage : pnpm typecheck attrape classes.inexistante grâ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 :

  1. Quand l'utiliser ; la loi de §0.3 recopiée (permis, interdit, exceptions).
  2. Écrire un composant : le module à côté, classes.root, classNames Mantine, data-*, useCssVariable, composes … from global pour deck.css, light-dark(), rem(), mixins ; un exemple complet (Callout.tsx + Callout.module.css).
  3. 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.
  4. Vérifier : node scripts/check-style-rule.js <dossier>, pnpm check, le lot de captures, la galerie, la note de journal.
  5. 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 » : TailwindCSS et Radix UI remplacés par Mantine 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/spotlight paresseux derrière Search).
  • « Code Style » : .module.css importé en classes, type-only pour CSSProperties interdit 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 ligne packages/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:38 et theme/custom-css.mdx:4,25 : « compiled with Tailwind » → « a plain stylesheet, loaded after the theme », plus une page theme/mantine.mdx qui montre comment un site écrit son recit.config theme: et sa feuille en couche site.
  • 03-packaging.md : la ligne des dépendances de @recit/theme (peer tailwindcss retiré).

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) :

VagueLotsÉtat
0STY1, STY10.1Faits : 443 références Linux, galerie, skill mantine-style, section « Styling »
1STY0a, STY0b, STY0c, STY0dFaits : 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
2STY2, STY6.1, STY7a, TST-AFaits : 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
3STY3a, STY3b, STY3c, STY6.2, STY7b, TST0Faits : 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
4STY4a-b, STY6.3a-b, STY8, TST1, TST2a-bFaits : 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
5STY5a-b, TST3a-b, TST4a-b, GATE4, INFRA, SBFIXFaits : 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 6STY5c, STY9, STY10.2, TST5, FINALFIXFaits (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 classe Spinner__root apparaît dans dist/index.html.
  • STY0.3 — Spike des modules dans un paquet publié : Kbd.module.css dans @recit/components, compilé en recit-components-Kbd__root par scripts/build-css-modules.mjs (postcss-modules), dist/styles.css exporté, importé par sites/storybook et 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.css dans examples/deck-mdx/components produit son CSS dans outputFiles et arrive dans la feuille du deck ; test dans packages/cli/src/__tests__.
  • STY0.5 — check-style-rule.js en avertissement avec la baseline de §1 ; test ; pnpm check vert.
  • 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 :

TerminalBash
pnpm exec nx run docs:build && grep -c "Spinner__root" sites/docs/dist/index.html pnpm exec vitest run packages/site scripts/__tests__/check-style-rule.spec.ts packages/cli pnpm check && pnpm test:e2e:linux e2e/site-docs.spec.ts

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:linux deux fois vert avant toute génération.
  • STY1.2 — pnpm test:e2e:update:linux sur 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 le POST de decisions.json ; test du script : serveur sur un port libre, GET de la galerie, POST d'une décision, lecture de decisions.json.
  • STY1.4 — Skill e2e-baselines mis à 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 pour sites/docs.
  • STY2.2 — ColorSchemeScript, localStorageColorSchemeManager, colorMode du config ; le pont .dark ; next-themes ne fait plus rien mais reste installé jusqu'à STY9.
  • STY2.3 — registryUrl retiré avec diagnostic ; GOOGLE_FONTS conservé.
  • STY2.4 — ThemeSwitch sur Mantine ; useTheme compatible.
  • 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 dans packages/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, lisant classes.*.
  • 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.css est créé mais lit encore les variables globales de main.css:166-220, que STY5 déplace. Heading, Typography et MdxComponents sont à 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.tsx si besoin.

  • STY4.1 — Chaque fichier selon §4.2 ; les valeurs dynamiques selon §0.3 permis 4 : --columns en data-columns, Toc par useCssVariable (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) ; Typography et Heading gardent leur API, leur intérieur change en STY5.

  • STY4.3 — node scripts/check-style-rule.js packages/site à zéro hors ui/ (STY3) et hors Typography, 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) : TypographyStylesProvider partout où ProseClasses l'était, Typography.module.css reporte règle à règle main.css:57-164, Heading sur Title et theme.headings, MdxComponents réécrit élément par élément avec un p ; captures des pages de doc en clair et en sombre acceptées ligne à ligne.
  • STY5.1 — main.css démonté : les variables de mise en page (main.css:166-220) dans Layout.module.css, le reset et html/body/scrollbar/.wavy-line dans app/base.css (couche theme, le seul CSS global du framework), les règles Shiki déjà parties en STY3c ; à la fin, main.css n'a plus que les @import et la déclaration @layer.
  • STY5.2 — tailwindcss() retiré de plugin.ts ; le site se construit ; aucune classe utilitaire orpheline (galerie : rien ne doit bouger, puisque tout est en module) ; cn.ts supprimé.
  • 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 : script build, 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 (peer tailwindcss), packages/theme/src/tailwind.css (supprimé), sites/storybook/.storybook/main.ts et preview.css, preview.tsx + preview.module.css. Fournit : @recit/<paquet>/styles.css, la CLI sans Tailwind, Storybook sans Tailwind. Vérification : pnpm build ; recit build de chaque examples/deck-* ; pnpm -F recit-storybook build ; captures deck-render identiques.
  • 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/deck et site-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 par buildThemeCss (packages/core/src/config/theme-css.ts, ajouté aux fichiers de ce lot), échelle calculée en CSS ; aucun useCssVariable dans 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.css et ThemeVars ; les blocs n'ont plus que className et la couche A.
  • STY7.2 — render.tsx trié : constant → module, par temps → couche ; le compte de style: hors couche tombe à zéro.
  • STY7.3 — goldens:check vert sans régénération (le rendu est identique), sinon chaque golden qui change est justifié et régénéré par goldens.

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-themes parti.
  • 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.md et AGENTS.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.mdx avec 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.md final (« UI », « Styling », « Tests »), CONTRIBUTING.md, NOTICE.md, theme/mantine.mdx et les pages qui citaient Tailwind, 03-packaging.md ; pas d'agent style-reviewer (journal, note STY10.2).

Vérification : pnpm check (frontmatter, liens) ; pnpm exec nx run docs:build.

10.1 Les vagues

VagueLots en parallèleCondition d'entrée
0STY1la branche existe (§11)
1STY0, STY10.1le « avant » de STY1 généré et relu
2STY2, STY6.1STY0 vérifié
3STY3a, STY3b, STY3c, STY6.2, STY7STY2 et STY6.1 vérifiés
4STY4a, STY4b, STY6.3, STY8STY3 vérifié (les trois), STY6.2 vérifié
5STY5STY4 vérifié (les deux)
5' et 6STY5c, STY9, STY10.2, TST5, FINALFIXFaits (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

VagueLotsAgentsTaille cumulée
0STY11M
1STY0, STY10.12M + S
2STY2, STY6.12M + M
3STY3a-c, STY6.2, STY75L + M + M
4STY4a-b, STY6.3, STY84L + M + S
5STY51L
5' et 6STY5c, STY9, STY10.2, TST5, FINALFIXFaits (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 depuis main le 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 sur main en avance, si l'utilisateur le veut : scripts/check-style-rule.js en mode avertissement avec sa baseline, qui refuse toute hausse ; ainsi aucun nouveau className utilitaire n'entre dans main pendant la migration. Le reste du plan ne touche pas main.

  • Rebase : à la fin de chaque vague, l'utilisateur rebase style/mantine sur main (les agents ne le font pas) ; les conflits attendus sont dans journal.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 sur main reçoit sa capture au rebase suivant (STY1 la liste).

  • Fusion : une seule fois, après STY9 et la revue finale, avec les -linux.png accepté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) suit dist/ 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 sur main à la demande de l'utilisateur : la couverture de captures et la galerie de STY1 (utiles quel que soit le style), le compteur check-style-rule.js en avertissement avec sa baseline, et le skill mantine-style ré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

RisqueParade
Les modules d'un paquet publié : trois bundlers, trois comportementsSTY0.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éesdeck.css reste global (§5.4), composes … from global fait le pont
Remotion : un module CSS dans le bundle webpackLe 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 bougeTypography.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 docInterdites par le compteur (§8.1-3) et par le skill ; la revue de diff les attrape
Le pont .dark oublié quelque part après STY9La 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 brancheLes 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égressionSeul 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ésDéclaration générée par build-css-modules.mjs (STY0.3), typecheck de chaque paquet dans sa vérification

13. Questions ouvertes

  1. 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) gardent gap, justify, align, qui sont leur raison d'être. Confirmer ces deux lignes avant STY3.
  2. Recherche : @mantine/spotlight (proposé) ou une boîte en module sur Modal sans paquet de plus ?
  3. deck.css global (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.
  4. motion/react gardé pour les transitions de diapositives (proposé) : Mantine n'a pas d'équivalent de AnimatePresence.
  5. 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.
  6. Version : prerelease (proposé) ou preminor au moment de fusionner ?
  7. 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 spec site-docs en bloc après revue d'échantillon ?
  8. 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.css ne change pas.
  9. Captures mobiles : un seul viewport (390×844) ou deux (plus 768×1024) ?
  10. Le nom @recit/site/ui/* : garder ces exports (proposé, pour site-kit et les sites qui les prendraient) ou exposer Mantine directement et supprimer la couche ? Garder évite une rupture et permet des classNames par défaut cohérents.
  11. Le projet Playwright en mode sombre : colorScheme: "dark" émule prefers-color-scheme, que next-themes n'honore que si le choix mémorisé est system (et Mantine, que si defaultColorScheme est auto et qu'aucune valeur n'est mémorisée). Le helper darkMode de e2e/support.ts doit donc poser le choix mémorisé : la clé de next-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 projet chromium-dark par émulation, ou capturer chaque page en sombre par le helper seul ?