# Style Presets — Learn, Apply, Manage A **style preset** is a named JSON file capturing a user's visual preferences — palette, shape vocabulary, fonts, edge style. When a preset is active, it fully replaces the built-in conventions in SKILL.md's color/shape/edge tables. Read this file when: - The user asks to "learn", "save", "remember", or "extract" a style from a file - The user wants to manage existing presets (list, set default, delete, rename) - You've resolved an active preset in Step 0 and need the application rules - You need to validate a preset file before loading it ## Locations and lookup order 1. `~/.drawio-skill/styles/.json` — user presets (survive `git pull`). 2. `/styles/built-in/.json` — built-ins shipped with the skill (`default`, `corporate`, `handdrawn`, `colorblind-safe` — Okabe-Ito palette, distinguishable under color-vision deficiency, thicker strokes; `dark` — dark fills + page background, light strokes, needs the `extras.fontColor`/`edgeColor`/`background` rules below). A user preset shadows a built-in of the same name. Only user presets can have `"default": true`. When the user says *"make `` my default"*, copy the built-in JSON to `~/.drawio-skill/styles/.json` first, then set `default: true` on the copy — leave the shipped built-in untouched. **Name normalisation:** always lowercase the user-provided name before writing or looking up files (the preset schema enforces lowercase; uppercase names will fail validation). ## Applying a preset > **Existing diagrams:** these rules apply at generation time. To re-theme a `.drawio` that already exists, run `python3 scripts/restyle.py diagram.drawio --preset ` — it applies the palette (hue-mapped), font, and extras without touching layout or edge routing. When SKILL.md's Step 0 identified a preset, it fully replaces the built-in palette, shape keywords, edge defaults, and font for this diagram — do not mix values from the built-in color table. **Color lookup.** For each role a shape plays (service / database / queue / gateway / error / external / security), resolve `preset.roles[role]` to a slot name, then `preset.palette[]` to the `(fillColor, strokeColor)` pair. If `roles[role]` is unset or the resolved slot is `null`, follow this fallback ladder: 1. Try the role's canonical slot (`service→primary`, `database→success`, `queue→warning`, `gateway→accent`, `error→danger`, `external→neutral`, `security→secondary`). 2. If that slot is also empty, pick the most-populated non-null slot in the preset. 3. Never reach into the built-in color table — the preset is authoritative. **Decision and container shapes** are not in `preset.roles` — they have shape vocabulary (`preset.shapes.decision`, `preset.shapes.container`) but no role-to-slot mapping. Pick their colors as follows: - **Decision** (rhombus) → use `preset.palette.warning` (the canonical yellow slot in the built-in conventions). If `warning` is empty, apply the slot-fallback ladder above starting from `warning`. - **Container** (swimlane) → use the palette slot matching the tier/grouping the container represents (e.g. a "Services" tier container uses `primary`; a "Data" tier uses `success`). If no tier signal is available, default to `primary`. **Shape keywords.** Use `preset.shapes[role]` as the **prefix** of the vertex style string (before `whiteSpace=wrap;html=1;...`). Example: for a database role, if `preset.shapes.database = "shape=cylinder3"`, the vertex style starts `shape=cylinder3;whiteSpace=wrap;html=1;fillColor=...`. The six named shape keys are `service`, `database`, `queue`, `decision`, `external`, `container`. Roles `gateway`, `error`, and `security` reuse `preset.shapes.service` unless the preset explicitly populates a key with their name. **Edges.** Use `preset.edges.style` as the base edge style string. Append `preset.edges.arrow`. Per-edge routing keys (`exitX/exitY/entryX/entryY/...`) are still added by the usual routing rules in SKILL.md. If the flow between two shapes matches a token from `preset.edges.dashedFor` (either because the user's prompt used that word, or because one end of the edge plays a role whose typical relation is "optional"), append `;dashed=1` to the edge style. **Fonts.** Append `fontFamily=;fontSize=` to every vertex style. Container headers and swimlane titles additionally get `fontSize=;fontStyle=1` when `preset.font.titleBold` is `true`. **Extras.** - `preset.extras.sketch === true` → append `sketch=1` to every vertex style and every edge style. - `preset.extras.globalStrokeWidth !== 1` (any value other than the drawio default of 1, including `0.5`) → append `strokeWidth=` to every vertex style and every edge style. - `preset.extras.fontColor` (present) → append `fontColor=` to every vertex and container style. Required for dark palettes — without it, dark fills render unreadable black text. - `preset.extras.edgeColor` (present) → append `strokeColor=;fontColor=` to every edge style (edges otherwise default to black, invisible on dark backgrounds). - `preset.extras.background` (present) → set `background=""` on the `` element, and export PNG **without** `-t` (transparent) so the background is actually painted — a dark diagram on a transparent PNG looks broken in white viewers. **Interaction with diagram-type presets** (ERD / UML / Sequence / ML / Flowchart). Diagram-type presets set structural style keywords that the user preset must preserve (e.g. ERD tables rely on `shape=table;startSize=30;container=1;childLayout=tableLayout;...`). The rule: keep the diagram-type preset's structural keywords, then layer the user preset's color / font / edge / extras on top. When a diagram-type preset hardcodes a color (`fillColor=#dae8fc`, etc.) that conflicts with the user preset, the user preset's color wins. Exception: `fillColor=none` is structural — do not replace it with a palette color. ## Learn flow **Triggers:** "learn my style from `` as ``", "save this as `` style", "remember this style as ``". **Dispatch by file extension:** - `.drawio`, `.xml` → XML path - `.png`, `.jpg`, `.jpeg`, `.svg` (rasterized flat image) → image path **Steps:** 1. **Load the extraction reference.** Read `references/style-extraction.md` into context. 2. **Extract** following the XML path or image path procedure in the reference. 3. **Normalize and build candidate.** Convert the user-provided preset name to lowercase. Use this normalized name for ALL file paths in this flow. Build the candidate preset JSON and write it to `/tmp/drawio-preset-.json` (where `` is the already-normalized name). Do **not** save to `~/.drawio-skill/styles/.json` yet. 4. **Render a sample** using the sample-diagram skeleton in `references/style-extraction.md`, parameterized by the candidate preset. Export PNG to `./preset--sample.png` using the same `drawio -x -f png -e -s 2 -o ./preset--sample.png /tmp/drawio-preset-.drawio` command the main workflow uses, then run `repair_png.py` on it (see the Rendering the sample steps in `style-extraction.md`). 5. **Show the user:** - Preset summary table (palette hex values, shapes per role, font, edge style, extras). - The sample PNG path (and embed the image if the environment supports it). - Provenance line: `source.type`, `source.path`, `extracted_at`, `confidence`. 6. **Wait for approval:** - "save" / "looks good" → write candidate to `~/.drawio-skill/styles/.json`. Create `~/.drawio-skill/styles/` if it doesn't exist. Delete tempfile and sample PNG. - "change `` to ``" → edit the in-memory candidate, re-render, re-ask. - "cancel" / "abort" / "no" → delete tempfile and sample PNG; nothing saved. **Error behavior:** | Failure | Behavior | |---|---| | Source path does not exist | Stop; report path not found. | | XML parse fails | Stop; report the parse error; suggest opening the file in drawio desktop to repair. | | Image vision unavailable | Stop; tell user to re-run on a vision-capable model or provide the `.drawio` file. | | Extraction yields 0 vertices / shapes | Stop; refuse to save. | | Extraction yields <3 distinct color pairs | Continue; mark `confidence: "low"` (image) or `"medium"` (XML); warn in summary. | | Preset name collides with existing user preset | Ask: overwrite, or pick a new name. | | Preset name collides with a built-in preset | Save to user dir (shadows the built-in); warn once. | | Sample render fails | Still show summary; note "could not render sample — saving on your OK anyway". Do not block. | ## Management operations All operations are natural language — no slash commands. *Apply name normalisation (lowercase) to all ``, ``, `` arguments before any file operation.* | User says | Agent does | |---|---| | "list my styles", "what styles do I have", "show me my style presets" | Read `~/.drawio-skill/styles/` and `/styles/built-in/`. Print a table: `name`, `location` (user/built-in), `source.type`, `confidence`, `default` flag. Built-ins shadowed by a user preset are marked so. | | "show my `` style", "what's in ``" | Print the preset JSON (pretty-printed) + a one-line summary (source, confidence, is-default). | | "make `` the default", "set `` as default" | If `` is a user preset: set `default: true` on it; clear `default` on any other user preset that had it; save both files. If `` is a built-in: copy `/styles/built-in/.json` → `~/.drawio-skill/styles/.json` first, then set `default: true` on the copy. Never mutate the shipped built-in. | | "remove default", "unset default" | Clear `default: true` from whichever user preset has it. | | "delete ``", "remove ``" | Confirm first. Then `rm ~/.drawio-skill/styles/.json`. Refuse to delete files under `/styles/built-in/` — suggest shadowing with a user preset of the same name. | | "rename `` to ``" | `mv ~/.drawio-skill/styles/.json ~/.drawio-skill/styles/.json`, then update the `name` field inside. Fails if `` is a built-in (offer to copy-then-rename instead). | | "learn my style from `` as ``" | Dispatch to the Learn flow above. | ## Preset file validation When loading any preset (for generation or management), do a lightweight structural check: - Required top-level fields present (`name`, `version`, `palette`, `roles`, `shapes`, `font`, `edges`). - `version === 1`. - Every populated palette slot has both `fillColor` and `strokeColor` as `#RRGGBB`. - `confidence` ∈ {`"low"`, `"medium"`, `"high"`} if present. On validation failure: - **During generation:** warn the user, fall back to built-in conventions for this one diagram, do not mutate the file. - **During learn:** refuse to save the candidate; report which field failed.