Files
dsh-skills/skills/drawio-skill/references/style-presets.md

11 KiB

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/<name>.json — user presets (survive git pull).
  2. <this-skill-dir>/styles/built-in/<name>.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 <built-in-name> my default", copy the built-in JSON to ~/.drawio-skill/styles/<name>.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 <name> — 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[<slot>] 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=<preset.font.fontFamily>;fontSize=<preset.font.fontSize> to every vertex style. Container headers and swimlane titles additionally get fontSize=<preset.font.titleFontSize>;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=<n> to every vertex style and every edge style.
  • preset.extras.fontColor (present) → append fontColor=<hex> to every vertex and container style. Required for dark palettes — without it, dark fills render unreadable black text.
  • preset.extras.edgeColor (present) → append strokeColor=<hex>;fontColor=<hex> to every edge style (edges otherwise default to black, invisible on dark backgrounds).
  • preset.extras.background (present) → set background="<hex>" on the <mxGraphModel> 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 <path> as <name>", "save this as <name> style", "remember this style as <name>".

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-<name>.json (where <name> is the already-normalized name). Do not save to ~/.drawio-skill/styles/<name>.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-<name>-sample.png using the same drawio -x -f png -e -s 2 -o ./preset-<name>-sample.png /tmp/drawio-preset-<name>.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/<name>.json. Create ~/.drawio-skill/styles/ if it doesn't exist. Delete tempfile and sample PNG.
    • "change <field> to <value>" → 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 <name>, <a>, <b> 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 <this-skill-dir>/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 <name> style", "what's in <name>" Print the preset JSON (pretty-printed) + a one-line summary (source, confidence, is-default).
"make <name> the default", "set <name> as default" If <name> is a user preset: set default: true on it; clear default on any other user preset that had it; save both files. If <name> is a built-in: copy <this-skill-dir>/styles/built-in/<name>.json → ~/.drawio-skill/styles/<name>.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 <name>", "remove <name>" Confirm first. Then rm ~/.drawio-skill/styles/<name>.json. Refuse to delete files under <this-skill-dir>/styles/built-in/ — suggest shadowing with a user preset of the same name.
"rename <a> to <b>" mv ~/.drawio-skill/styles/<a>.json ~/.drawio-skill/styles/<b>.json, then update the name field inside. Fails if <a> is a built-in (offer to copy-then-rename instead).
"learn my style from <path> as <name>" 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.