From 708483b0c6e835ad62bb4d43ffd83d123426f419 Mon Sep 17 00:00:00 2001 From: CarlosSB2028 Date: Fri, 16 Oct 2026 01:54:59 +0900 Subject: [PATCH 1/2] feat(tokens): centralize shared design tokens Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 10 +++ design-tokens/README.md | 61 +++++++++++++++++++ design-tokens/generate.mjs | 71 ++++++++++++++++++++++ design-tokens/generated/astrea.tokens.css | 62 +++++++++++++++++++ design-tokens/generated/astrea_tokens.dart | 47 ++++++++++++++ design-tokens/tokens.json | 53 ++++++++++++++++ design-tokens/validate.mjs | 45 ++++++++++++++ docs/standards/design-tokens.md | 49 +++++++++++++++ package.json | 2 + sidebars.js | 10 +++ 10 files changed, 410 insertions(+) create mode 100644 design-tokens/README.md create mode 100644 design-tokens/generate.mjs create mode 100644 design-tokens/generated/astrea.tokens.css create mode 100644 design-tokens/generated/astrea_tokens.dart create mode 100644 design-tokens/tokens.json create mode 100644 design-tokens/validate.mjs create mode 100644 docs/standards/design-tokens.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e19c57..bd42b2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,5 +33,15 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Validate design tokens + run: pnpm tokens:validate + + - name: Check generated design tokens + run: | + test -f design-tokens/generated/astrea.tokens.css + test -f design-tokens/generated/astrea_tokens.dart + pnpm tokens:generate + git diff --exit-code -- design-tokens/generated + - name: Build handbook run: pnpm build diff --git a/design-tokens/README.md b/design-tokens/README.md new file mode 100644 index 0000000..f1d321a --- /dev/null +++ b/design-tokens/README.md @@ -0,0 +1,61 @@ +# Astrea design tokens + +`tokens.json` est la représentation agnostique des fondations de la page `10 - Foundations` du fichier Figma Astrea. + +Les fichiers `generated/astrea.tokens.css` et `generated/astrea_tokens.dart` sont produits par : + +```bash +node design-tokens/generate.mjs +``` + +La validation des paires de contraste se lance avec : + +```bash +node design-tokens/validate.mjs +``` + +Les applications consomment ces sorties dans leur propre dépôt : + +- `app-web/src/styles/tokens.css` +- `app-mobile/lib/core/design_tokens.dart` + +Une synchronisation doit régénérer les deux fichiers. Les valeurs ne doivent pas être modifiées directement dans les sorties générées. + +## Procédure de mise à jour + +Lorsqu'une fondation change dans Figma : + +1. Mettre à jour `tokens.json` avec les valeurs validées de `10 - Foundations`. +2. Lancer `pnpm tokens:validate`. +3. Lancer `pnpm tokens:generate`. +4. Copier `generated/astrea.tokens.css` dans `app-web/src/styles/tokens.css`. +5. Copier `generated/astrea_tokens.dart` dans `app-mobile/lib/core/design_tokens.dart`. +6. Vérifier que les sorties sont identiques aux fichiers synchronisés. +7. Lancer les validations locales des trois dépôts. +8. Ouvrir les PR liées au même ticket. + +Les PR `app-web` et `app-mobile` doivent indiquer la source utilisée dans leur description : + +```text +Design tokens source: docs@ +``` + +Le commit ou tag correspond à la version de `tokens.json` utilisée pour générer les sorties. + +## Règles d'usage + +- `color/border` est réservé aux séparateurs et bordures décoratives. +- `color/border-strong` est utilisé pour les champs, focus et composants interactifs. +- `color/primary` et `color/accent` sont utilisés comme fonds. +- `color/primary-strong` et `color/accent-strong` sont utilisés pour le texte, les icônes et les marqueurs. +- Les textes exigent un contraste minimal de `4,5:1`. +- Les bordures et composants graphiques exigent un contraste minimal de `3:1`. + +## Polices + +Les applications embarquent localement les graisses suivantes : + +- Montserrat : `400`, `600`, `700` ; +- Inter : `400`, `600`, `700`. + +Les fichiers sont distribués sous la licence SIL Open Font License. Chaque dépôt applicatif conserve la notice `OFL.txt` avec ses fichiers de police. diff --git a/design-tokens/generate.mjs b/design-tokens/generate.mjs new file mode 100644 index 0000000..1250833 --- /dev/null +++ b/design-tokens/generate.mjs @@ -0,0 +1,71 @@ +import { readFile, mkdir, writeFile } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = dirname(fileURLToPath(import.meta.url)); +const tokens = JSON.parse(await readFile(resolve(root, 'tokens.json'), 'utf8')); +const outputDirectory = resolve(root, 'generated'); + +const cssName = (name) => name.replace(/[^a-z0-9]+/gi, '-').toLowerCase(); +const dartName = (name, numericPrefix = 'value') => { + const identifier = name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); + return /^[0-9]/.test(identifier) ? `${numericPrefix}${identifier}` : identifier; +}; +const dartColor = (value) => `0xFF${value.slice(1)}`; + +const css = `:root { +${Object.entries(tokens.color).map(([name, value]) => ` --color-${cssName(name)}: ${value.toLowerCase()};`).join('\n')} + + --font-family-heading: '${tokens.font.heading}', sans-serif; + --font-family-body: '${tokens.font.body}', sans-serif; +${Object.entries(tokens.type).flatMap(([name, value]) => [ + ` --font-size-${cssName(name)}: ${value['font-size']}px;`, + ` --line-height-${cssName(name)}: ${value['line-height']}px;` +]).join('\n')} +${Object.entries(tokens.spacing).map(([name, value]) => ` --spacing-${cssName(name)}: ${value}px;`).join('\n')} +} + +.text-petit { + font-family: var(--font-family-body); + font-size: var(--font-size-petit); + line-height: var(--line-height-petit); +} + +.text-petit-gras { + font-family: var(--font-family-body); + font-size: var(--font-size-petit-gras); + font-weight: 700; + line-height: var(--line-height-petit-gras); +} + +.text-titre { + font-family: var(--font-family-heading); + font-weight: 600; +} + +.text-corps { + font-family: var(--font-family-body); +} +`; + +const dart = `import 'package:flutter/material.dart'; + +abstract final class AstreaColors { +${Object.entries(tokens.color).map(([name, value]) => ` static const ${dartName(name)} = Color(${dartColor(value)});`).join('\n')} +} + +abstract final class AstreaTypography { + static const heading = '${tokens.font.heading}'; + static const body = '${tokens.font.body}'; +${Object.entries(tokens.type).map(([name, value]) => ` static const ${dartName(name)}FontSize = ${value['font-size']}.0; + static const ${dartName(name)}LineHeight = ${value['line-height']}.0;`).join('\n')} +} + +abstract final class AstreaSpacing { +${Object.entries(tokens.spacing).map(([name, value]) => ` static const ${dartName(name, 'spacing')} = ${value}.0;`).join('\n')} +} +`; + +await mkdir(outputDirectory, { recursive: true }); +await writeFile(resolve(outputDirectory, 'astrea.tokens.css'), css); +await writeFile(resolve(outputDirectory, 'astrea_tokens.dart'), dart); diff --git a/design-tokens/generated/astrea.tokens.css b/design-tokens/generated/astrea.tokens.css new file mode 100644 index 0000000..857fc27 --- /dev/null +++ b/design-tokens/generated/astrea.tokens.css @@ -0,0 +1,62 @@ +:root { + --color-primary: #4caf50; + --color-primary-hover: #43a047; + --color-primary-strong: #2e7d32; + --color-on-primary: #212529; + --color-accent: #ff9800; + --color-accent-strong: #b34700; + --color-on-accent: #212529; + --color-tertiary: #2b6cb0; + --color-ink: #212529; + --color-muted: #5b6560; + --color-border: #ddd8ce; + --color-border-strong: #7c847f; + --color-error: #b3261e; + --color-error-border: #b3261e; + --color-error-bg: #fdedeb; + --color-surface: #ffffff; + --color-page: #faf7f2; + --color-badge: #e8f5e9; + --color-map: #efeae0; + --color-brand-gradient-start: #43a047; + --color-brand-gradient-end: #ffa726; + --color-on-error: #ffffff; + + --font-family-heading: 'Montserrat', sans-serif; + --font-family-body: 'Inter', sans-serif; + --font-size-petit: 12px; + --line-height-petit: 16px; + --font-size-petit-gras: 12px; + --line-height-petit-gras: 16px; + --spacing-12: 12px; + --spacing-20: 20px; + --spacing-32: 32px; + --spacing-40: 40px; + --spacing-56: 56px; + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 24px; +} + +.text-petit { + font-family: var(--font-family-body); + font-size: var(--font-size-petit); + line-height: var(--line-height-petit); +} + +.text-petit-gras { + font-family: var(--font-family-body); + font-size: var(--font-size-petit-gras); + font-weight: 700; + line-height: var(--line-height-petit-gras); +} + +.text-titre { + font-family: var(--font-family-heading); + font-weight: 600; +} + +.text-corps { + font-family: var(--font-family-body); +} diff --git a/design-tokens/generated/astrea_tokens.dart b/design-tokens/generated/astrea_tokens.dart new file mode 100644 index 0000000..27bd237 --- /dev/null +++ b/design-tokens/generated/astrea_tokens.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +abstract final class AstreaColors { + static const primary = Color(0xFF4CAF50); + static const primaryHover = Color(0xFF43A047); + static const primaryStrong = Color(0xFF2E7D32); + static const onPrimary = Color(0xFF212529); + static const accent = Color(0xFFFF9800); + static const accentStrong = Color(0xFFB34700); + static const onAccent = Color(0xFF212529); + static const tertiary = Color(0xFF2B6CB0); + static const ink = Color(0xFF212529); + static const muted = Color(0xFF5B6560); + static const border = Color(0xFFDDD8CE); + static const borderStrong = Color(0xFF7C847F); + static const error = Color(0xFFB3261E); + static const errorBorder = Color(0xFFB3261E); + static const errorBg = Color(0xFFFDEDEB); + static const surface = Color(0xFFFFFFFF); + static const page = Color(0xFFFAF7F2); + static const badge = Color(0xFFE8F5E9); + static const map = Color(0xFFEFEAE0); + static const brandGradientStart = Color(0xFF43A047); + static const brandGradientEnd = Color(0xFFFFA726); + static const onError = Color(0xFFFFFFFF); +} + +abstract final class AstreaTypography { + static const heading = 'Montserrat'; + static const body = 'Inter'; + static const petitFontSize = 12.0; + static const petitLineHeight = 16.0; + static const petitGrasFontSize = 12.0; + static const petitGrasLineHeight = 16.0; +} + +abstract final class AstreaSpacing { + static const spacing12 = 12.0; + static const spacing20 = 20.0; + static const spacing32 = 32.0; + static const spacing40 = 40.0; + static const spacing56 = 56.0; + static const xs = 4.0; + static const sm = 8.0; + static const md = 16.0; + static const lg = 24.0; +} diff --git a/design-tokens/tokens.json b/design-tokens/tokens.json new file mode 100644 index 0000000..e63f757 --- /dev/null +++ b/design-tokens/tokens.json @@ -0,0 +1,53 @@ +{ + "color": { + "primary": "#4CAF50", + "primary-hover": "#43A047", + "primary-strong": "#2E7D32", + "on-primary": "#212529", + "accent": "#FF9800", + "accent-strong": "#B34700", + "on-accent": "#212529", + "tertiary": "#2B6CB0", + "ink": "#212529", + "muted": "#5B6560", + "border": "#DDD8CE", + "border-strong": "#7C847F", + "error": "#B3261E", + "error-border": "#B3261E", + "error-bg": "#FDEDEB", + "surface": "#FFFFFF", + "page": "#FAF7F2", + "badge": "#E8F5E9", + "map": "#EFEAE0", + "brand-gradient-start": "#43A047", + "brand-gradient-end": "#FFA726", + "on-error": "#FFFFFF" + }, + "font": { + "heading": "Montserrat", + "body": "Inter" + }, + "type": { + "petit": { + "font-family": "body", + "font-size": 12, + "line-height": 16 + }, + "petit-gras": { + "font-family": "body", + "font-size": 12, + "line-height": 16 + } + }, + "spacing": { + "xs": 4, + "sm": 8, + "md": 16, + "lg": 24, + "12": 12, + "20": 20, + "32": 32, + "40": 40, + "56": 56 + } +} diff --git a/design-tokens/validate.mjs b/design-tokens/validate.mjs new file mode 100644 index 0000000..156e2bf --- /dev/null +++ b/design-tokens/validate.mjs @@ -0,0 +1,45 @@ +import { readFile } from 'node:fs/promises'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = dirname(fileURLToPath(import.meta.url)); +const tokens = JSON.parse(await readFile(resolve(root, 'tokens.json'), 'utf8')); + +const luminance = (hex) => { + const channels = [1, 3, 5].map((offset) => parseInt(hex.slice(offset, offset + 2), 16) / 255); + const [r, g, b] = channels.map((channel) => + channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4, + ); + return 0.2126 * r + 0.7152 * g + 0.0722 * b; +}; + +const contrast = (foreground, background) => { + const values = [luminance(foreground), luminance(background)].sort((a, b) => b - a); + return (values[0] + 0.05) / (values[1] + 0.05); +}; + +const color = (name) => tokens.color[name]; +const checks = [ + ['ink', 'page', 4.5], + ['ink', 'surface', 4.5], + ['muted', 'page', 4.5], + ['muted', 'surface', 4.5], + ['primary-strong', 'page', 4.5], + ['accent-strong', 'page', 4.5], + ['error', 'error-bg', 4.5], + ['on-error', 'error', 4.5], + ['border-strong', 'surface', 3], + ['border-strong', 'page', 3], + ['border-strong', 'error-bg', 3], +]; + +for (const [foreground, background, threshold] of checks) { + const ratio = contrast(color(foreground), color(background)); + if (ratio < threshold) { + throw new Error( + `${foreground}/${background}: ${ratio.toFixed(2)}:1 < ${threshold.toFixed(1)}:1`, + ); + } +} + +console.log(`Validated ${checks.length} WCAG token pairs.`); diff --git a/docs/standards/design-tokens.md b/docs/standards/design-tokens.md new file mode 100644 index 0000000..75aa278 --- /dev/null +++ b/docs/standards/design-tokens.md @@ -0,0 +1,49 @@ +--- +title: Design tokens +sidebar_position: 1 +description: Source et synchronisation des fondations visuelles partagées par Astrea. +--- + +# Design tokens partagés + +Les fondations visuelles Astrea sont définies dans le fichier Figma `10 - Foundations`, puis représentées dans [`design-tokens/tokens.json`](https://github.com/Astrea-EIP/docs/blob/main/design-tokens/tokens.json). + +Le JSON est la source consommée par les sorties générées : + +- CSS pour `app-web` ; +- Dart pour `app-mobile`. + +Les sorties ne doivent pas être modifiées manuellement. Une mise à jour commence par Figma, puis met à jour `tokens.json`, régénère les sorties et synchronise les deux dépôts applicatifs. + +## Procédure de synchronisation + +Après une modification validée dans Figma : + +1. Mettre à jour `design-tokens/tokens.json`. +2. Exécuter `pnpm tokens:validate`. +3. Exécuter `pnpm tokens:generate`. +4. Synchroniser `design-tokens/generated/astrea.tokens.css` avec `app-web/src/styles/tokens.css`. +5. Synchroniser `design-tokens/generated/astrea_tokens.dart` avec `app-mobile/lib/core/design_tokens.dart`. +6. Vérifier les trois dépôts avec leurs commandes locales habituelles. +7. Ouvrir une PR par dépôt, toutes liées au même ticket. + +Les PR Web et Mobile doivent indiquer le commit ou tag de la source utilisée : + +```text +Design tokens source: docs@ +``` + +La CI du dépôt `docs` vérifie les contrastes et échoue si la génération modifie les sorties versionnées. + +## Accessibilité + +- Le texte doit atteindre `4,5:1`. +- Les composants et bordures doivent atteindre `3:1`. +- `color/border` est réservé aux séparateurs et bordures décoratives. +- `color/border-strong` est réservé aux champs, états de focus et composants interactifs. + +Les valeurs Figma qui échouent lorsqu'elles sont utilisées hors de leur contexte restent disponibles, mais leur usage doit respecter ces règles. + +## Polices + +`app-web` et `app-mobile` embarquent localement Montserrat et Inter, avec les graisses `400`, `600` et `700`. Les fichiers sont distribués sous la licence SIL Open Font License ; les notices `OFL.txt` sont conservées dans chaque dépôt applicatif. diff --git a/package.json b/package.json index ed56793..be1d209 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.0.0", "private": true, "scripts": { + "tokens:generate": "node design-tokens/generate.mjs", + "tokens:validate": "node design-tokens/validate.mjs", "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", diff --git a/sidebars.js b/sidebars.js index 51b1589..82a019c 100644 --- a/sidebars.js +++ b/sidebars.js @@ -52,6 +52,16 @@ const sidebars = { }, items: [{ type: 'autogenerated', dirName: 'operations' }], }, + { + type: 'category', + label: 'Standards', + link: { + type: 'generated-index', + title: 'Standards', + description: 'Shared engineering and design standards.', + }, + items: [{ type: 'autogenerated', dirName: 'standards' }], + }, ], }; From f94ece743b0765da97ae7e46c51867d53e269b57 Mon Sep 17 00:00:00 2001 From: CarlosSB2028 Date: Fri, 16 Oct 2026 02:11:35 +0900 Subject: [PATCH 2/2] docs(tokens): translate token documentation to English Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- design-tokens/README.md | 58 ++++++++++++++++----------------- docs/standards/design-tokens.md | 52 ++++++++++++++--------------- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/design-tokens/README.md b/design-tokens/README.md index f1d321a..e706396 100644 --- a/design-tokens/README.md +++ b/design-tokens/README.md @@ -1,61 +1,61 @@ # Astrea design tokens -`tokens.json` est la représentation agnostique des fondations de la page `10 - Foundations` du fichier Figma Astrea. +`tokens.json` is the platform-agnostic representation of the foundations from the `10 - Foundations` page in the Astrea Figma file. -Les fichiers `generated/astrea.tokens.css` et `generated/astrea_tokens.dart` sont produits par : +The `generated/astrea.tokens.css` and `generated/astrea_tokens.dart` files are produced by: ```bash node design-tokens/generate.mjs ``` -La validation des paires de contraste se lance avec : +Contrast-pair validation is run with: ```bash node design-tokens/validate.mjs ``` -Les applications consomment ces sorties dans leur propre dépôt : +The applications consume these outputs in their own repositories: - `app-web/src/styles/tokens.css` - `app-mobile/lib/core/design_tokens.dart` -Une synchronisation doit régénérer les deux fichiers. Les valeurs ne doivent pas être modifiées directement dans les sorties générées. +Synchronization must regenerate both files. Values must not be edited directly in generated outputs. -## Procédure de mise à jour +## Update procedure -Lorsqu'une fondation change dans Figma : +When a foundation changes in Figma: -1. Mettre à jour `tokens.json` avec les valeurs validées de `10 - Foundations`. -2. Lancer `pnpm tokens:validate`. -3. Lancer `pnpm tokens:generate`. -4. Copier `generated/astrea.tokens.css` dans `app-web/src/styles/tokens.css`. -5. Copier `generated/astrea_tokens.dart` dans `app-mobile/lib/core/design_tokens.dart`. -6. Vérifier que les sorties sont identiques aux fichiers synchronisés. -7. Lancer les validations locales des trois dépôts. -8. Ouvrir les PR liées au même ticket. +1. Update `tokens.json` with the approved values from `10 - Foundations`. +2. Run `pnpm tokens:validate`. +3. Run `pnpm tokens:generate`. +4. Copy `generated/astrea.tokens.css` to `app-web/src/styles/tokens.css`. +5. Copy `generated/astrea_tokens.dart` to `app-mobile/lib/core/design_tokens.dart`. +6. Verify that the outputs match the synchronized files. +7. Run the local validations for all three repositories. +8. Open the pull requests linked to the same issue. -Les PR `app-web` et `app-mobile` doivent indiquer la source utilisée dans leur description : +The `app-web` and `app-mobile` pull requests must state the source used in their description: ```text Design tokens source: docs@ ``` -Le commit ou tag correspond à la version de `tokens.json` utilisée pour générer les sorties. +The commit or tag identifies the version of `tokens.json` used to generate the outputs. -## Règles d'usage +## Usage rules -- `color/border` est réservé aux séparateurs et bordures décoratives. -- `color/border-strong` est utilisé pour les champs, focus et composants interactifs. -- `color/primary` et `color/accent` sont utilisés comme fonds. -- `color/primary-strong` et `color/accent-strong` sont utilisés pour le texte, les icônes et les marqueurs. -- Les textes exigent un contraste minimal de `4,5:1`. -- Les bordures et composants graphiques exigent un contraste minimal de `3:1`. +- `color/border` is reserved for separators and decorative borders. +- `color/border-strong` is used for fields, focus states, and interactive components. +- `color/primary` and `color/accent` are used as backgrounds. +- `color/primary-strong` and `color/accent-strong` are used for text, icons, and markers. +- Text must meet a minimum contrast ratio of `4.5:1`. +- Borders and graphical components must meet a minimum contrast ratio of `3:1`. -## Polices +## Fonts -Les applications embarquent localement les graisses suivantes : +The applications bundle the following weights locally: -- Montserrat : `400`, `600`, `700` ; -- Inter : `400`, `600`, `700`. +- Montserrat: `400`, `600`, `700` +- Inter: `400`, `600`, `700` -Les fichiers sont distribués sous la licence SIL Open Font License. Chaque dépôt applicatif conserve la notice `OFL.txt` avec ses fichiers de police. +The files are distributed under the SIL Open Font License. Each application repository keeps the `OFL.txt` notice with its font files. diff --git a/docs/standards/design-tokens.md b/docs/standards/design-tokens.md index 75aa278..1c54c6f 100644 --- a/docs/standards/design-tokens.md +++ b/docs/standards/design-tokens.md @@ -1,49 +1,49 @@ --- title: Design tokens sidebar_position: 1 -description: Source et synchronisation des fondations visuelles partagées par Astrea. +description: Source and synchronization of Astrea's shared visual foundations. --- -# Design tokens partagés +# Shared design tokens -Les fondations visuelles Astrea sont définies dans le fichier Figma `10 - Foundations`, puis représentées dans [`design-tokens/tokens.json`](https://github.com/Astrea-EIP/docs/blob/main/design-tokens/tokens.json). +Astrea's visual foundations are defined in the `10 - Foundations` Figma page and represented in [`design-tokens/tokens.json`](https://github.com/Astrea-EIP/docs/blob/main/design-tokens/tokens.json). -Le JSON est la source consommée par les sorties générées : +The JSON is the source consumed by the generated outputs: -- CSS pour `app-web` ; -- Dart pour `app-mobile`. +- CSS for `app-web` +- Dart for `app-mobile` -Les sorties ne doivent pas être modifiées manuellement. Une mise à jour commence par Figma, puis met à jour `tokens.json`, régénère les sorties et synchronise les deux dépôts applicatifs. +Generated outputs must not be edited manually. An update starts in Figma, then updates `tokens.json`, regenerates the outputs, and synchronizes both application repositories. -## Procédure de synchronisation +## Synchronization procedure -Après une modification validée dans Figma : +After an approved change in Figma: -1. Mettre à jour `design-tokens/tokens.json`. -2. Exécuter `pnpm tokens:validate`. -3. Exécuter `pnpm tokens:generate`. -4. Synchroniser `design-tokens/generated/astrea.tokens.css` avec `app-web/src/styles/tokens.css`. -5. Synchroniser `design-tokens/generated/astrea_tokens.dart` avec `app-mobile/lib/core/design_tokens.dart`. -6. Vérifier les trois dépôts avec leurs commandes locales habituelles. -7. Ouvrir une PR par dépôt, toutes liées au même ticket. +1. Update `design-tokens/tokens.json`. +2. Run `pnpm tokens:validate`. +3. Run `pnpm tokens:generate`. +4. Synchronize `design-tokens/generated/astrea.tokens.css` with `app-web/src/styles/tokens.css`. +5. Synchronize `design-tokens/generated/astrea_tokens.dart` with `app-mobile/lib/core/design_tokens.dart`. +6. Validate all three repositories with their usual local commands. +7. Open one pull request per repository, all linked to the same issue. -Les PR Web et Mobile doivent indiquer le commit ou tag de la source utilisée : +The Web and Mobile pull requests must state the commit or tag of the source used: ```text Design tokens source: docs@ ``` -La CI du dépôt `docs` vérifie les contrastes et échoue si la génération modifie les sorties versionnées. +The `docs` repository CI validates contrast ratios and fails if generation modifies the versioned outputs. -## Accessibilité +## Accessibility -- Le texte doit atteindre `4,5:1`. -- Les composants et bordures doivent atteindre `3:1`. -- `color/border` est réservé aux séparateurs et bordures décoratives. -- `color/border-strong` est réservé aux champs, états de focus et composants interactifs. +- Text must reach `4.5:1`. +- Components and borders must reach `3:1`. +- `color/border` is reserved for separators and decorative borders. +- `color/border-strong` is reserved for fields, focus states, and interactive components. -Les valeurs Figma qui échouent lorsqu'elles sont utilisées hors de leur contexte restent disponibles, mais leur usage doit respecter ces règles. +Figma values that fail when used outside their intended context remain available, but their usage must follow these rules. -## Polices +## Fonts -`app-web` et `app-mobile` embarquent localement Montserrat et Inter, avec les graisses `400`, `600` et `700`. Les fichiers sont distribués sous la licence SIL Open Font License ; les notices `OFL.txt` sont conservées dans chaque dépôt applicatif. +`app-web` and `app-mobile` bundle Montserrat and Inter locally, with weights `400`, `600`, and `700`. The files are distributed under the SIL Open Font License; `OFL.txt` notices are kept in each application repository.