--- name: generate-slides description: Generate content/.html slide fragments from slides/.md briefs using the layout library. Skips locked and catalog slides, reports stale slides and asks before overwriting. --- # Generate Slides (Pass 1) ## Purpose Turn the briefs in `slides/.md` (ordered by `deck.yml`) into HTML fragments in `content/.html`. The brief is the source of truth; the HTML is a regenerable artifact that records which version of the brief it came from. This skill handles **layout slides** only. Catalog slides (`use: catalog/`) are resolved by the catalog resolver and must be run first: ```bash python3 tools/resolve-catalog.py resolve . --catalog=/catalog ``` Write `` as the resolved absolute path (see Plugin directory), so the command stays literal. This skill replaces the old "read SLIDES.md and write slides by conversation" step. Legacy decks that only have `SLIDES.md` are not handled here: migrate them first (mapping in `/references/slide-schema.md`, section 6), or keep writing fragments by hand. ## Plugin directory Paths written as `/...` are files in the uw-slides plugin, not in the deck. Resolve `PLUGIN` once per invocation. The first match wins, and `/tools/deckparse.py` must exist: 1. `$UW_SLIDES_HOME`, if set (`printenv UW_SLIDES_HOME`). Use this to point at a development checkout. 2. The plugin this skill was loaded from, two directories above this `SKILL.md` (`/skills//SKILL.md`). Take the skill's path from your client, for example Claude Code's "Base directory for this skill", and resolve symlinks. This is where Claude Code, Codex, Copilot and other Agent Plugins clients install the plugin. 3. `~/.claude/plugins/local/uw-slides`, the legacy symlink install. If none of these has `tools/deckparse.py`, stop and ask the user where the plugin is. ## Brand Resolution Resolve the brand in this order: 1. `--brand=` flag if provided 2. `.brand` file in the current directory if it exists 3. Default to `uw` ## Usage ``` /uw-slides:generate-slides # generate missing slides, ask about stale ones /uw-slides:generate-slides [...] # only these slides ``` Run from inside the presentation directory. All `deckparse.py` commands use the vendored copy at `tools/deckparse.py` (put there by `/uw-slides:new-deck`). The recommended allow rule is `Bash(python3 tools/deckparse.py *)`. ## Read first - `/design-systems/-brand/DESIGN.md`: the brand rules - `/references/layouts.md`: the layout library, slot limits, fill procedure (section 2) and `custom` rules (section 6) - `/references/slide-schema.md`: brief fields, staleness hash, generation rules (section 5) - `/references/accessibility-requirements.md` - the font-size rules in `/CLAUDE.md` (minimum `1.5rem`; `1rem` only for source lines and uppercase labels) ## Procedure **Start fresh on every invocation.** Briefs, `deck.yml` and `content/` change between runs: the user adds briefs, edits wording and hand-edits HTML. Re-read them from disk every time this skill runs, even if you read them earlier in the same conversation, and never decide what to generate from memory of a previous run. Step 1 gives the current state of every slide. ### 1. Get the state of every slide ```bash python3 tools/deckparse.py status . ``` It prints one line per slide in `deck.yml` order. Act on the state, never on guesses: | State | Meaning | Action | |-------|---------|--------| | `missing` | no `content/.html` | generate | | `stale` | the brief changed since the HTML was generated | list these, **ask before overwriting**, then generate the confirmed ones | | `untracked` | HTML exists without `data-brief-hash` (hand-written or older) | **ask**: overwrite, or keep it by setting `locked: true` in the brief | | `fresh` | HTML matches the brief | skip | | `locked` | brief has `locked: true` (hand-edited HTML) | **never write**; report it. To regenerate, the user sets `locked: false` | | `catalog` | brief has `use: catalog/` | skip; catalog slides are copied by the catalog resolver, never generated | | `no-brief`, `invalid` | `slides/.md` missing or does not parse | skip and report the file and line; the user fixes it | If the user named slide IDs, restrict to those. If `deck.yml` itself does not parse, stop and show the error. `orphan-brief` and `orphan-fragment` lines are reported, not acted on. ### 2. Generate each slide For every slide you are going to write: 1. **Read the brief**: `python3 tools/deckparse.py brief slides/.md` (JSON: front matter and body). 2. **Resolve the layout.** - A layout ID: it must be a file in `/templates/layouts/` (`layouts.md`, section 1). Unknown ID: stop and report. - `auto`: choose with `layouts.md`, section 5, **write the chosen ID and a `layout_rationale` back into the brief's front matter**, and only then continue (the hash covers `layout`). - `custom`: follow `layout_intent` and the rules in `layouts.md`, section 6. 3. **Check the content fits** the layout's limits (`layouts.md`, section 4). If it does not, report it and propose either shorter text (for the user to approve in the brief) or a different layout. Never shrink fonts or silently cut the brief's text. 4. **Fill the layout** exactly as in `layouts.md`, section 2: copy `/templates/layouts/.html`, rename `layout-` to the slide ID in `data-slide` and every CSS selector, fill each `data-slot` / `data-param`, remove optional elements the brief leaves empty. Replace every sample list item (never append to the template's `
  • `s) and render Markdown to HTML. Before writing, check the output: the number of list items equals the brief's, none of the template's sample text remains, and no literal `-`, `**` or `1.` is left in a text node. `custom` slides are written from scratch to the same standard (scoped `section[data-slide=""]` styles, brand tokens, one `

    `, accent bar). 5. **Apply the generation rules** (`slide-schema.md`, section 5): - the key message is the headline and usually the main text shown; - talking points are **never** rendered verbatim; - **speaker notes**: if the brief has a `## Notes` section, render its content as `