Initial : marketplace + 2 plugins (syncfusion-ribbon-accent, offscreen-winforms-render)

This commit is contained in:
Julien Cluzel
2026-06-08 19:30:32 +02:00
commit c3330204e3
8 changed files with 401 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
{
"$schema": "https://code.claude.com/docs/plugins/marketplace-schema.json",
"name": "ara-claude-plugins",
"description": "Plugins Claude Code pour le développement WinForms / Syncfusion (ARA Informatique).",
"owner": {
"name": "Julien Cluzel"
},
"plugins": [
{
"name": "syncfusion-ribbon-accent",
"source": "./plugins/syncfusion-ribbon-accent",
"description": "Couleur d'accent personnalisée (live) sur un ruban Syncfusion WinForms Office2016 : ruban, backstage (dont le SURVOL des items), onglets de contenu, grilles SfDataGrid. Le bon levier par élément + les pièges.",
"version": "1.0.0",
"license": "MIT",
"keywords": ["syncfusion", "winforms", "ribbon", "office2016", "theme", "accent", "backstage", "sfdatagrid"],
"category": "dev"
},
{
"name": "offscreen-winforms-render",
"source": "./plugins/offscreen-winforms-render",
"description": "Vérifier/déboguer l'aspect d'une IHM WinForms sans bureau interactif (agent/CI/headless) : rendu hors écran via PrintWindow → PNG relisible et échantillonnable au pixel.",
"version": "1.0.0",
"license": "MIT",
"keywords": ["winforms", "render", "offscreen", "printwindow", "headless", "screenshot", "testing"],
"category": "dev"
}
]
}
+3
View File
@@ -0,0 +1,3 @@
node_modules/
bin/
*.log
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Julien Cluzel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+43
View File
@@ -0,0 +1,43 @@
# ara-claude-plugins
Marketplace de **plugins Claude Code** pour le développement **WinForms / Syncfusion**.
Chaque plugin apporte un *skill* réutilisable, issu d'un projet réel (migration WinDev → .NET + Syncfusion).
## Plugins
| Plugin | Skill | Pour quoi |
|---|---|---|
| **syncfusion-ribbon-accent** | `/syncfusion-ribbon-accent:syncfusion-ribbon-accent` | Appliquer une **couleur d'accent personnalisée** (live) à un ruban Syncfusion WinForms Office2016 : ruban, backstage (dont le **survol** des items), onglets de contenu, grilles `SfDataGrid`. Le bon levier par élément + les pièges. |
| **offscreen-winforms-render** | `/offscreen-winforms-render:offscreen-winforms-render` | **Vérifier/déboguer une IHM WinForms sans bureau interactif** (agent, CI, headless) : rendu hors écran via `PrintWindow` → PNG relisible et échantillonnable au pixel. |
Réf. testée : Syncfusion Essential Studio **WinForms 33.2.x**, .NET, C#.
## Installation
```text
/plugin marketplace add https://repos.ara-it.info/ARA-IT/ara-claude-plugins.git
/plugin install syncfusion-ribbon-accent@ara-claude-plugins
/plugin install offscreen-winforms-render@ara-claude-plugins
```
Puis `/reload-plugins` si installé en cours de session. Les skills s'invoquent ensuite via
`/<plugin>:<skill>` (ou se déclenchent automatiquement selon le contexte).
Mettre à jour : `/plugin marketplace update ara-claude-plugins`.
## Structure du repo
```
.claude-plugin/marketplace.json # catalogue (liste les 2 plugins)
plugins/
├── syncfusion-ribbon-accent/
│ ├── .claude-plugin/plugin.json
│ └── skills/syncfusion-ribbon-accent/SKILL.md
└── offscreen-winforms-render/
├── .claude-plugin/plugin.json
└── skills/offscreen-winforms-render/SKILL.md
```
## Licence
MIT — voir [LICENSE](LICENSE).
@@ -0,0 +1,11 @@
{
"$schema": "https://code.claude.com/docs/plugins/plugin-schema.json",
"name": "offscreen-winforms-render",
"description": "Vérifier ou déboguer l'aspect visuel d'une IHM WinForms (.NET, y compris Syncfusion) sans bureau interactif (agent, CI, headless) — rendu hors écran via PrintWindow + thread STA → PNG relisible ET échantillonnable au pixel. Inclut : forcer un état de survol, ouvrir une backstage, injecter une couleur de test.",
"version": "1.0.0",
"author": {
"name": "Julien Cluzel"
},
"license": "MIT",
"repository": "https://repos.ara-it.info/ARA-IT/ara-claude-plugins"
}
@@ -0,0 +1,89 @@
---
name: offscreen-winforms-render
description: À utiliser pour VÉRIFIER ou DÉBOGUER l'aspect visuel d'une IHM WinForms (.NET, y compris Syncfusion) quand il n'y a PAS de bureau interactif (agent, CI, headless) — où CopyFromScreen échoue. Rend la fenêtre HORS ÉCRAN dans un PNG (PrintWindow + thread STA) que l'on peut relire ET échantillonner au pixel (preuve empirique des couleurs/du layout). Inclut : forcer un état de survol, ouvrir une backstage, injecter une couleur de test.
---
# Rendu d'artefact offscreen (vérifier une IHM WinForms sans la lancer)
**But** : produire un **PNG** du rendu réel d'une fenêtre WinForms sans bureau interactif, pour le **relire**
et l'**échantillonner pixel par pixel** (preuve chiffrée). Portable : tout projet .NET WinForms (Syncfusion ou non).
## Pourquoi
- En agent/CI, pas de bureau interactif : `Graphics.CopyFromScreen` échoue (« Descripteur non valide »).
- Sans retour visuel, on tourne en rond sur les bugs d'IHM (couleur, layout).
- Parade : **dessiner la fenêtre hors écran dans un bitmap** via `PrintWindow`, sauver un PNG, puis (1) le relire,
(2) l'échantillonner (`bmp.GetPixel(x,y)`) pour comparer des valeurs RGB attendues.
## Prérequis
- Console jetable `net10.0-windows` (`OutputType=WinExe`, `UseWindowsForms=true`). Dossier préfixé `_` (gitignoré).
- Si l'IHM a une **licence** (Syncfusion…) : l'enregistrer, sinon un dialogue modal **gèle** le rendu.
- Si l'IHM a un **thème** chargé au démarrage : le charger pareil que l'app.
- Pour rendre la VRAIE fenêtre : référencer le projet applicatif (`ProjectReference`).
## Gabarit
```csharp
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
var th = new Thread(Run); // 1) thread STA obligatoire
th.SetApartmentState(ApartmentState.STA);
th.Start(); th.Join();
[DllImport("user32.dll")] static extern bool PrintWindow(IntPtr h, IntPtr hdc, uint flags);
void Run()
{
// 2) Licence éventuelle (sinon dialogue d'essai modal => blocage)
// Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("...");
// 3) Thème éventuel, chargé comme dans l'app réelle
// SfSkinManager.LoadAssembly(typeof(Office2016Theme).Assembly);
var f = new MyForm { StartPosition = FormStartPosition.Manual, Location = new Point(-3000, 0) }; // HORS écran
f.Show();
for (int i = 0; i < 10; i++) { Application.DoEvents(); Thread.Sleep(80); } // 4) laisser peindre (PAS Application.Run)
using var bmp = new Bitmap(f.Width, f.Height);
using (var g = Graphics.FromImage(bmp))
{
var hdc = g.GetHdc();
PrintWindow(f.Handle, hdc, 2); // 5) flag 2 = PW_RENDERFULLCONTENT (obligatoire)
g.ReleaseHdc(hdc);
}
bmp.Save(@"C:\chemin\rendu.png");
var c = bmp.GetPixel(450, 12); // 6) preuve empirique chiffrée
Console.WriteLine($"pixel = {c.R},{c.G},{c.B}");
f.Close();
}
```
## Recettes utiles
- **Plusieurs variantes en une exécution** : une fonction `Capture(name, build)` par variante, échantillonner le pixel décisif → comparer les RGB. Injecter une **couleur de test distinctive** (rouge/orange/violet), pas la couleur par défaut, pour distinguer « appliqué » de « inchangé ».
- **Rendre le VRAI form** (un mock minimal ne reproduit pas certains bugs de thème/skin) : référencer le projet, instancier la fenêtre. Accéder aux champs privés par réflexion si besoin :
```csharp
var fld = typeof(MyForm).GetField("backStageView", BindingFlags.NonPublic | BindingFlags.Instance);
```
- **Ouvrir une backstage Syncfusion** : `backStageView.ShowBackStage();`
- **Forcer un état de survol** (capturer un hover sans souris) : `button.State = ButtonAdvState.MouseOver;` puis `Invalidate()`. (⚠️ un `State` forcé n'est pas toujours fidèle à 100 % au survol réel selon le contrôle — recouper avec la valeur de propriété et/ou un test live.)
## Pièges (vérifiés)
- **Thread STA obligatoire** (`SetApartmentState(STA)`), sinon plantage à l'instanciation.
- **Licence avant tout** : sans enregistrement, le dialogue d'essai modal gèle le rendu.
- **Thème chargé** comme dans l'app, sinon rendu non représentatif.
- **`PrintWindow` flag = 2** (`PW_RENDERFULLCONTENT`) : `0`/`1` ratent les contrôles peints en différé (noir/partiel).
- **Fenêtre hors écran** (`Location` négatif + `StartPosition=Manual`).
- **Laisser peindre** : `Show()` + boucle `Application.DoEvents()`/`Sleep` (PAS `Application.Run`).
- **Coordonnées d'échantillonnage** : repérer les bons `(x,y)` en regardant d'abord le PNG (un contrôle peut être repositionné au runtime ≠ ses `Bounds` de design).
- **Pas de `CopyFromScreen`** en headless (échoue) — c'est ce que `PrintWindow` contourne.
## Workflow
1. Créer `_render/` (gitignoré).
2. Composer un contrôle minimal OU instancier la vraie fenêtre ; faire plusieurs variantes ; échantillonner.
3. **Relire** le PNG (juger l'aspect) **et** lire la sortie console (preuve chiffrée).
4. Itérer, puis **supprimer** le dossier jetable.
@@ -0,0 +1,11 @@
{
"$schema": "https://code.claude.com/docs/plugins/plugin-schema.json",
"name": "syncfusion-ribbon-accent",
"description": "Couleur d'accent personnalisée (live) sur un ruban Syncfusion WinForms Office2016 — ruban, backstage (dont le survol des items), onglets de contenu (TabControlAdv), grilles SfDataGrid. Donne le bon levier par élément et les pièges (ThemeStyle ignoré, ThemeName à conserver, BackStageItemHoverColor, FlattenHierarchy…). Réf. testée : Syncfusion WinForms 33.2.x.",
"version": "1.0.0",
"author": {
"name": "Julien Cluzel"
},
"license": "MIT",
"repository": "https://repos.ara-it.info/ARA-IT/ara-claude-plugins"
}
@@ -0,0 +1,195 @@
---
name: syncfusion-ribbon-accent
description: À utiliser pour IMPLÉMENTER ou DÉBOGUER une couleur d'accent personnalisée (live, choisie par l'utilisateur) sur une IHM Syncfusion WinForms Office2016 — ruban (RibbonControlAdv), backstage (BackStage/menu Fichier, y compris le SURVOL des items), onglets de contenu (TabControlAdv) et grilles (SfDataGrid). Donne le bon levier par élément et les pièges (ThemeStyle ignoré, ThemeName à ne pas vider, BackStageItemHoverColor, FlattenHierarchy…). Réf. testée : Syncfusion WinForms 33.2.x.
---
# Couleur d'accent sur un ruban Syncfusion WinForms (Office2016)
**But** : faire suivre une **couleur d'accent libre** à TOUTE l'IHM (ruban, backstage, onglets, grilles), en live.
**Testé** : Syncfusion Essential Studio **WinForms 33.2.10**, .NET 10, C#.
**Prérequis** : `SfSkinManager.LoadAssembly(typeof(Office2016Theme).Assembly);` au démarrage.
## Règle d'or — chaque zone a SON mécanisme (ne pas les confondre)
| Élément | Bon levier (33.2.x) | À NE PAS faire |
|---|---|---|
| **Ruban** (bandeau, onglets, Fichier, boutons système) | `ribbon.Office2016ColorTable` (**Clear + Add d'UNE table**), en **gardant** `ThemeName="Office2016"` | `ribbon.ThemeStyle` (ignoré en live) ; vider `ThemeName` (ruban plat « touch ») |
| **Backstage — titre + bouton retour ←** | `ribbon.ThemeStyle.BackStageStyle` (`TitleBarBackColor`, `ControlBoxBackColor`…), **ré-assigner** `ribbon.ThemeStyle` | — |
| **Backstage — items (onglets+boutons) repos/SURVOL/sélection** | `ribbon.Office2016ColorTable.BackStageItem*` (le renderer `BackStage2016` lit cette table) | `BackStageButton.BackColor`/`MetroColor`/`OverrideFormManagedColor`/`ThemeStyle` (écrasés par le renderer) |
| **Backstage — onglet actif** | `backStage.ActiveTabColor` / `ActiveTabForeColor` | — |
| **Onglets de contenu** (`TabControlAdv`) | `content.ActiveTabColor` / `ActiveTabForeColor` | — |
| **Grilles** (`SfDataGrid`) | surcharge directe de `grid.Style` | chercher un `ThemeSettings` (inexistant en 33.2.10) |
| **QuickAccess Toolbar** (retrait) | `ribbon.QuickPanelVisible=false` + `ribbon.ShowQuickItemsDropDownButton=false` | — |
**3 principes** : (1) **garder** `ribbon.ThemeName="Office2016"` (le vider casse le rendu) ; (2) `Office2016ColorTable` est une `BindingList` **sans clé****une seule table** (`Clear()`+`Add()`) ; (3) le renderer Office2016 lit cette table pour le ruban **ET** la backstage.
## Pièges (à ne pas redécouvrir)
1. `ribbon.ThemeStyle` n'a **aucun effet live** sur le bandeau du ruban (le skin repeint par-dessus) → `Office2016ColorTable`.
2. Vider `ribbon.ThemeName` colore le bandeau MAIS retire le skin → rendu plat « touch », laid. **Garder `"Office2016"`.**
3. `Office2016ColorTable` = `BindingList` sans clé/scheme → **une seule entrée** suffit.
4. **Survol des items backstage** : ni `BackStageButton.BackColor`/`MetroColor`/`OverrideFormManagedColor`+`CustomManagedColor`/`Appearance=Metro`/`ButtonAdvVisualStyle.HoverBackColor` — le renderer peint et lit la **color table****`BackStageItemHoverColor`**.
5. `backStage.ThemeName` se fait **réécraser** par SfSkinManager (`BackStage2016Renderer`) ; le vider est inopérant.
6. **Noms `[Obsolete]`** : `BackStage*Tab*Color`/`BackStage*Button*Color` → unifiés en **`BackStageItem*`**.
7. `SfSkinManager` 33.2.10 = `LoadAssembly` + `SetTheme` seulement (pas de `ThemeSettings`/`RegisterThemeSettings`).
8. `Office2016ColorTable` ne s'applique que sous `Office2016ColorScheme.Colorful`.
9. **Réflexion incomplète = fausse conclusion** : lister les propriétés d'un type Syncfusion SANS `BindingFlags.FlattenHierarchy` rate les **membres hérités** (piège qui avait fait conclure à tort que `BackStageItemHoverColor` n'existait pas). **Toujours `Public|Instance|FlattenHierarchy`** + signaler les `[Obsolete]`.
## Vérification (environnement sans bureau / agent)
- **Rendre le VRAI form** offscreen (un mock minimal ne reproduit PAS le bleu backstage). `PrintWindow` (flag 2) + thread STA → PNG relisible + `bmp.GetPixel`. Cf. méthode de rendu offscreen.
- Ouvrir la backstage par réflexion : `backStageView.ShowBackStage()` (champ privé).
- **Forcer un survol** pour le capturer sans souris : `btnX.State = ButtonAdvState.MouseOver;` puis `Invalidate()`.
- Injecter une **couleur de test distinctive** (orange/rouge/violet), pas l'accent par défaut, pour distinguer « appliqué » de « inchangé ».
- **Vérifier l'API par réflexion AVANT de coder** (`MetadataLoadContext`, `FlattenHierarchy`).
## Checklist d'intégration
1. `SfSkinManager.LoadAssembly(typeof(Office2016Theme).Assembly)`.
2. Ruban : `RibbonStyle.Office2016` + `Office2016ColorScheme.Colorful` + `ThemeName="Office2016"` + `CanOverrideStyle=true` + `MenuColor=accent`.
3. **Une** `Office2016ColorTable` (chrome=accent, corps clair, `BackStageItem*`=accent) → `Clear()`+`Add()`.
4. `ThemeStyle.BackStageStyle` : `TitleBarBackColor`/`ControlBoxBackColor` (+hover/pressed) → ré-assigner `ThemeStyle`.
5. `backStage.ActiveTabColor` + `content.ActiveTabColor`.
6. Grilles : `grid.Style.*` (sélection/en-tête/bordure/ascenseurs) + ré-application live.
7. QAT retirée si inutile.
8. Helpers `ForeColorFor` / `Shade` / `Tint`.
9. **Vérifier au rendu offscreen** (vrai form + survol forcé) avant de conclure.
## Implémentation de référence (copiable)
```csharp
using Syncfusion.Windows.Forms; // BackStage, BackStageButton
using Syncfusion.Windows.Forms.Tools; // RibbonForm, RibbonControlAdv, Office2016ColorTable, TabControlAdv
using Syncfusion.WinForms.Controls; // SfSkinManager
using Syncfusion.WinForms.DataGrid; // SfDataGrid
internal static class AppTheme
{
public static Color AccentColor { get; set; } = ColorTranslator.FromHtml("#0099CC");
public const string SfThemeName = "Office2016Colorful";
// === RUBAN + BACKSTAGE (titre/bouton retour) ===
public static void ApplyOffice2016(RibbonForm form, RibbonControlAdv ribbon, Color accent)
{
ribbon.RibbonStyle = RibbonStyle.Office2016;
ribbon.Office2016ColorScheme = Office2016ColorScheme.Colorful;
ribbon.ThemeName = "Office2016";
ribbon.CanOverrideStyle = true;
ribbon.MenuColor = accent;
ribbon.Office2016ColorTable.Clear();
ribbon.Office2016ColorTable.Add(BuildOffice2016ColorTable(accent));
Color fore = ForeColorFor(accent);
var themeStyle = ribbon.ThemeStyle;
var bs = themeStyle.BackStageStyle;
bs.TitleBarBackColor = accent; bs.TitleBarForeColor = fore;
bs.ControlBoxBackColor = accent; // bouton retour ←
bs.HoverControlBoxBackColor = Shade(accent, 0.12);
bs.PressedControlBoxBackColor = Shade(accent, 0.20);
bs.ItemBackColor = accent; bs.ItemForeColor = fore;
bs.SelectedItemBackColor = Shade(accent, 0.20); bs.SelectedItemForeColor = fore;
bs.HoverItemBackColor = Shade(accent, 0.12); bs.HoverItemForeColor = fore;
ribbon.ThemeStyle = themeStyle;
ribbon.Refresh();
ribbon.Invalidate(true);
}
private static Office2016ColorTable BuildOffice2016ColorTable(Color accent)
{
Color fore = ForeColorFor(accent);
Color body = ColorTranslator.FromHtml("#F1F1F1");
Color bodyText = ColorTranslator.FromHtml("#575757");
Color border = ColorTranslator.FromHtml("#D2D2D2");
Color hover = ColorTranslator.FromHtml("#C5C5C5");
return new Office2016ColorTable
{
// Chrome à l'accent
HeaderColor = accent, TabBackColor = accent, TitleColor = fore,
TabForeColor = fore, HoverTabForeColor = fore, SelectedTabColor = accent,
CheckedTabColor = body, CheckedTabForeColor = accent,
FileMenuForeColor = fore, FileMenuHoverForeColor = fore,
SystemButtonBackground = accent, ControlBoxForeColor = fore,
CloseButtonBackground = ColorTranslator.FromHtml("#E81123"),
// Items backstage (le renderer lit cette table) — SURVOL = BackStageItemHoverColor
BackStageItemForeColor = fore,
BackStageItemHoverColor = Shade(accent, 0.16),
BackStageItemHoverForeColor = fore,
BackStageItemSelectionColor = Shade(accent, 0.26),
BackStageItemSelectionForeColor = fore,
BackStageTitleColor = ColorTranslator.FromHtml("#3A3A3A"),
// Corps clair, lisible
CaptionBackColor = body, CaptionForeColor = bodyText,
ToolStripBackColor = body, PanelBackColor = body, RibbonPanelBorderColor = border,
ToolStripItemForeColor = bodyText, ToolStripSpliterColor = border,
ButtonBackgroundHovered = hover, ButtonBackgroundSelected = hover,
ButtonBackgroundPressed = ColorTranslator.FromHtml("#969696"),
};
}
// === ONGLETS DE CONTENU ===
public static void ApplyContentTabs(TabControlAdv content, Color accent)
{
content.ActiveTabColor = accent;
content.ActiveTabForeColor = ForeColorFor(accent);
content.Refresh(); content.Invalidate(true);
}
// === BACKSTAGE : onglet actif (le reste via la color table) ===
public static void ApplyBackStageItems(BackStage backStage, Color accent)
{
backStage.ActiveTabColor = accent;
backStage.ActiveTabForeColor = ForeColorFor(accent);
backStage.Invalidate(true);
}
// === GRILLES ===
public static void ApplySfControlTheme(Control control) => SfSkinManager.SetTheme(control, SfThemeName);
public static void ApplyGridAccent(SfDataGrid grid, Color accent)
{
grid.Style.SelectionStyle.BackColor = Tint(accent, 0.55);
grid.Style.SelectionStyle.TextColor = Color.Black;
grid.Style.HeaderStyle.SortIconColor = accent;
grid.Style.HeaderStyle.FilterIconColor = accent;
grid.Style.BorderColor = accent;
grid.Style.VerticalScrollBar.ThumbColor = Tint(accent, 0.45);
grid.Style.VerticalScrollBar.ThumbHoverColor = accent;
grid.Style.HorizontalScrollBar.ThumbColor = Tint(accent, 0.45);
grid.Style.HorizontalScrollBar.ThumbHoverColor = accent;
grid.Invalidate();
}
public static void RefreshGridAccents(Control root, Color accent)
{
foreach (Control c in root.Controls)
{
if (c is SfDataGrid g) ApplyGridAccent(g, accent);
if (c.HasChildren) RefreshGridAccents(c, accent);
}
}
// === HELPERS ===
private static Color ForeColorFor(Color c)
=> (0.299 * c.R + 0.587 * c.G + 0.114 * c.B) / 255.0 > 0.55 ? Color.Black : Color.White;
public static Color Tint(Color c, double t) => Color.FromArgb(c.A,
(int)(c.R + (255 - c.R) * t), (int)(c.G + (255 - c.G) * t), (int)(c.B + (255 - c.B) * t));
private static Color Shade(Color c, double f) => Color.FromArgb(c.A,
(int)(c.R * (1 - f)), (int)(c.G * (1 - f)), (int)(c.B * (1 - f)));
}
```
### Câblage (au démarrage ET dans le picker de couleur)
```csharp
AppTheme.ApplyOffice2016(this, ribbon, accent);
AppTheme.ApplyContentTabs(content, accent);
AppTheme.ApplyBackStageItems(backStage, accent);
AppTheme.RefreshGridAccents(content, accent); // grilles déjà ouvertes
// QAT : ribbon.QuickPanelVisible = false; ribbon.ShowQuickItemsDropDownButton = false;
// + persister la couleur et la relire au démarrage
```
> Guide long + récit complet : `docs/syncfusion-ribbon-couleur-accent.md` (dans le repo d'origine CapToilettage).
> Technique de vérification : `docs/methode-rendu-offscreen.md`.