From workflows
This skill should be used when the user asks to 'create a workshop presentation', 'prepare a workshop talk', 'make slides for a workshop', 'presentation for faculty workshop', 'workshop slides from paper', or needs to create academic workshop presentation slides and speaker notes from a research paper.
npx claudepluginhub edwinhu/workflows --plugin workflowsThis skill uses the workspace's default tool permissions.
**Announce:** "I'm using workshop to create academic presentation slides and speaker notes."
Implements Playwright E2E testing patterns: Page Object Model, test organization, configuration, reporters, artifacts, and CI/CD integration for stable suites.
Guides Next.js 16+ Turbopack for faster dev via incremental bundling, FS caching, and HMR; covers webpack comparison, bundle analysis, and production builds.
Discovers and evaluates Laravel packages via LaraPlugins.io MCP. Searches by keyword/feature, filters by health score, Laravel/PHP compatibility; fetches details, metrics, and version history.
Announce: "I'm using workshop to create academic presentation slides and speaker notes."
Load ALL Typst conventions before any slide or notes work:
!uv run python3 ${CLAUDE_SKILL_DIR}/../../scripts/load-constraints.py workshop
You MUST have these constraints loaded before proceeding to Phase 3. No claiming you "remember" them.
Check if .planning/HANDOFF.md exists:
Phase 1 Phase 2 Phase 3 Phase 4
gather → structure → generate → verify
(sources) (outline) (slides+notes) (compile+check)
│ │ │ │
▼ ▼ ▼ ▼
GATE: GATE: GATE: GATE:
Metadata User Both .typ Both compile,
extracted, approves files metadata matches
symlinks outline written source paper
created
Every gate is mandatory. Skipping a gate means the next phase operates on bad inputs.
After completing each phase, IMMEDIATELY proceed to the next phase. Do not pause for user approval except where explicitly required (Phase 2: user approves outline).
Smart Discuss: If multiple questions arise in Phase 1 (e.g., paper path unclear, venue unknown, desired structure), batch them into a SINGLE user interaction. Do NOT ask one question, wait, ask another, wait. Present all ambiguities at once:
Before proceeding, I need to clarify:
1. Paper path: [what I found vs. what's unclear]
2. Venue/date: [known or unknown]
3. Structure preference: [default or ask]
Please answer all at once so we can proceed efficiently.
Before starting each phase, check context availability:
| Level | Remaining Context | Action |
|---|---|---|
| Normal | >35% | Proceed normally |
| Warning | 25-35% | Complete current task, then write .planning/HANDOFF.md and pause |
| Critical | <=25% | Write .planning/HANDOFF.md immediately — no new phase |
Phase 3 (generate) is the most context-intensive phase. If context is at Warning level before Phase 3, write .planning/HANDOFF.md:
---
workflow: workshop
phase: [current phase number]
phase_name: [current phase name]
status: context_exhaustion
last_updated: [timestamp]
---
## Current State
[What phase we're in, what's been completed in this phase]
## Completed Work
- Phase 1: [status — SOURCES.md path, inventory count]
- Phase 2: [status — OUTLINE.md path, section count]
- Phase 3: [status — slides written? notes written?]
## Remaining Work
[Specific tasks left in current phase + all subsequent phases]
## Decisions Made
[Any user decisions captured — structure proportions, venue, etc.]
## Next Action
[Specific enough to start immediately — e.g., "Write notes.typ sections 3-5 following OUTLINE.md"]
Skipping handoff to "finish faster" means the last slides are garbage. The user debugs context-degraded output instead of resuming from a clean handoff. That is anti-helpful.
| Phase | Gate | Type | Behavior |
|---|---|---|---|
| Phase 1 | Sources gathered | human-verify | Auto-advanceable |
| Phase 2 | Outline approved | decision | Pause for user input |
| Phase 3 | Slides reviewed | human-verify | Auto-advanceable (independent reviewer) |
| Phase 4 | Verified | human-verify | Auto-advanceable |
Create .planning/ACTIVE_WORKFLOW.md:
---
workflow: workshop
phase: 1
phase_name: gather
started: [current timestamp]
project_root: [current directory]
implements: "4-phase workshop creation (gather → structure → generate → verify)"
requires: "source paper (PDF), user structure preferences"
provides: "slides.typ, notes.typ, slides.pdf, notes.pdf"
affects: "presentation/ directory"
---
Responsibility: Collect ALL source materials and extract paper metadata.
## The Iron Law of Title ExtractionNEVER hallucinate, infer, or guess the paper title, subtitle, authors, or affiliations. ALWAYS extract from the source document using look-at or Read. This is not negotiable.
Inferring metadata from filenames is fabrication. The user got burned by hallucinated titles. Every title, every author name, every affiliation MUST come from reading the actual paper.
Skipping extraction to "help faster" is anti-helpful — it ships wrong metadata that the user has to debug and fix. That's rework you created, not time you saved.
| Excuse | Reality | Do Instead |
|---|---|---|
| "The filename tells me the title" | Filenames are abbreviated, incomplete, or wrong | Use look-at on the actual paper |
| "I can infer the authors from context" | Context may be wrong; co-authors change between drafts | Extract from the paper's title page |
| "I'll fix the title later" | Wrong titles propagate to slides AND notes | Get it right first |
| "The user told me the title" | Verify against the paper — user may have paraphrased | Extract and confirm |
Identify the source paper. Ask the user if not obvious from context.
Extract metadata using look-at:
uv run python3 "${CLAUDE_SKILL_DIR}/../look-at/scripts/look_at.py" \
--file "/path/to/paper.pdf" \
--goal "Extract: (1) full title, (2) subtitle if any, (3) all author names, (4) each author's affiliation/institution, (5) abstract summary in 2-3 sentences"
Search for related teaching materials:
# Search ~/areas/ for topic keywords from the paper
rg -l "keyword1|keyword2" ~/areas/
Check for predecessor slides:
gdrive or google-drive symlinks in project directorypresentation/ directoriesCheck Obsidian notes:
notes symlink in project directorySet up theme infrastructure:
# Create presentation directory if needed
mkdir -p presentation/templates presentation/assets
# Copy bundled workshop templates
cp "${CLAUDE_SKILL_DIR}/templates/theme.typ" presentation/templates/
cp "${CLAUDE_SKILL_DIR}/templates/custom-outline.typ" presentation/templates/
The assets/ directory starts empty — add project-specific logos and images there.
Inventory the paper's figures, tables, and key results:
uv run python3 "${CLAUDE_SKILL_DIR}/../look-at/scripts/look_at.py" \
--file "/path/to/paper.pdf" \
--goal "List ALL: (1) figures with figure numbers and captions, (2) tables with table numbers and captions, (3) key empirical results with specific numbers (coefficients, percentages, sample sizes), (4) main theoretical propositions or hypotheses"
This inventory is the authoritative source for all content in slides and notes. Every figure, table, statistic, and claim in the presentation must trace back to this inventory.
Write SOURCES.md in .planning/:
---
title: [extracted title]
subtitle: [extracted subtitle or none]
authors:
- name: [author 1]
affiliation: [affiliation 1]
marker: "*"
- name: [author 2]
affiliation: [affiliation 2]
marker: "†"
venue: [workshop venue if known]
date: [workshop date if known]
---
## Source Paper
- Path: [path to paper]
- Key sections: [list]
## Paper Inventory
Every item gets a unique ID (F1, T1, R1, A1...) for traceability.
Each slide in the presentation must reference at least one inventory ID.
### Figures
- **F1:** Figure 1: [caption] (p. XX)
- **F2:** Figure 2: [caption] (p. XX)
...
### Tables
- **T1:** Table 1: [caption] (p. XX)
- **T2:** Table 2: [caption] (p. XX)
...
### Key Empirical Results
- **R1:** [Result 1 with specific numbers] (Table/Figure X, p. XX)
- **R2:** [Result 2 with specific numbers] (Table/Figure X, p. XX)
...
### Main Arguments / Hypotheses
- **A1:** [Argument 1] (Section X)
- **A2:** [Argument 2] (Section X)
...
## Related Teaching Materials
- [list of found materials with paths]
## Predecessor Slides
- [list or "none found"]
## Obsidian Notes
- [list or "none found"]
Structural gate artifact: After verifying all checks pass, write .planning/SOURCES_VERIFIED.md:
---
status: VERIFIED
phase: gather
verified_at: [timestamp]
title_source: look-at (NOT inferred)
implements: "Phase 1 — source gathering and metadata extraction"
requires: "source paper PDF"
provides: "SOURCES.md with paper inventory (F/T/R/A IDs)"
affects: "presentation/templates/, presentation/assets/"
inventory_count:
figures: [N]
tables: [N]
results: [N]
arguments: [N]
---
Sources gathered and verified. Paper metadata extracted from source document.
Phase 2 will refuse to start without this file.
IMMEDIATELY proceed to Phase 2.
Responsibility: Create section-level outline with content allocation based on user's desired structure.
.planning/SOURCES_VERIFIED.md exists with status: VERIFIED.planning/SOURCES.md exists with paper metadata and inventoryIf .planning/SOURCES_VERIFIED.md is missing, STOP. Return to Phase 1 and complete the sources gate.
Ask the user for desired structure (if not already specified):
Read the paper's structure — use look-at to get the table of contents / section headings:
uv run python3 "${CLAUDE_SKILL_DIR}/../look-at/scripts/look_at.py" \
--file "/path/to/paper.pdf" \
--goal "List all section headings and subheadings in order"
Map paper sections to presentation structure. Distribute content according to user's proportions.
Write OUTLINE.md in .planning/:
## Presentation Outline
Total time: [X] minutes
### Part 1: [Section Name] (~[Y] minutes, [N] slides)
= [Touying section heading]
== [Subsection 1]
- Slide: [slide title] — [content source: paper §X / teaching material / predecessor] → [F1, R2, A1]
- Slide: [slide title] — [content source] → [T1, R3]
### Part 2: [Section Name] (~[Y] minutes, [N] slides)
...
Present outline to user for approval.
Producing an outline from memory instead of the paper's structure means the presentation won't match the paper. The user discovers misaligned sections during Phase 3, requiring rework of both the outline AND the slides. Getting the structure right here saves hours downstream.
Structural gate artifact: After user approves, write .planning/OUTLINE_APPROVED.md:
---
status: APPROVED
phase: structure
approved_at: [timestamp]
checkpoint_type: decision
implements: "Phase 2 — outline structure with content allocation"
requires: "SOURCES_VERIFIED.md, user structure preferences"
provides: "OUTLINE.md with section proportions, timing, and inventory ID mapping"
affects: ".planning/OUTLINE.md"
total_time: [N] minutes
section_count: [N]
slide_count: [N]
---
Outline approved by user. Structure: [brief summary of proportions].
Phase 3 will refuse to start without this file.
IMMEDIATELY proceed to Phase 3 after user approval.
Responsibility: Write slides.typ and notes.typ following ALL Typst conventions.
.planning/OUTLINE_APPROVED.md exists with status: APPROVED.planning/SOURCES_VERIFIED.md exists with status: VERIFIED.planning/SOURCES.md exists with paper inventory.planning/OUTLINE.md exists with approved structureIf any prerequisite is missing, STOP. Do not generate slides without approved outline and verified sources.
## The Iron Law of Typst ConventionsALL bullet items MUST have blank lines between them. This is not negotiable.
Wrong:
- First point
- Second point
- Third point
Correct:
- First point
- Second point
- Third point
This applies to EVERY list in EVERY slide. No exceptions.
File header (slides.typ):
#import "templates/theme.typ": *
#show: university-theme.with(
aspect-ratio: "16-9",
footer-a: self => self.info.author,
config-info(
title: [#text(size: 0.85em)[Paper Title]],
subtitle: [Workshop Venue],
author: (
[Author1#super[\*]],
[Author2#super[†]],
),
date: datetime.today(),
institution: [#text(size: 0.8em)[#super[\*]Affiliation1 #h(1em) #super[†]Affiliation2]],
logo: image("assets/logo.png"), // place your institution logo in assets/
qr: none,
),
)
#show link: underline
#set list(marker: ([•], [--]))
#set heading(numbering: numbly("{1}.", "{1}.{2}.", "{3}."))
#show selector(heading.where(level: 3)): set heading(numbering: none)
#show selector(heading.where(level: 4)): set heading(numbering: none)
#title-slide()
CRITICAL: qr: none MUST be included in config-info. The secreg theme expects this field.
Heading hierarchy:
= — Section (Part separator, e.g., = Motivation & Background)== — Subsection (topic group, e.g., == The Rise of Proxy Advisors)=== — Slide title as a takeaway sentence (inside #slide[], e.g., === Proxy advisors emerged to fill this gap.). If the subtitle states the takeaway, do NOT add a conclusion bullet restating the same point — the subtitle already carries it. Body bullets should add new information (evidence, examples, applications).Slide structure:
== Subsection Title
#slide[
=== Slide title as a complete sentence ending with a period.
- First bullet point with *bold* for emphasis
- Second bullet point with _italic_ for key terms
- Third bullet point
#callout[
Key takeaway or important quote.
]
]
Available Typst features:
#slide[] — standard slide#pause — reveal animation#callout[] — highlighted callout box#set text(20pt) or #set text(size: 0.85em) — font size control within a slide#table() — for data display (use INSTEAD of cetz-plot), minimum inset: 10pt#super[] — superscript for author markers#h(1em) — horizontal spacecetz.canvas — from theme's bundled cetz import (NOT cetz-plot). Minimum length: 2em. Requires // Storytelling: comment within 3 lines before diagram code.#align(center)[#image(...)] — center-align ALL imagesSkipping conventions to "finish faster" is anti-helpful — it ships slides with formatting errors that the presenter has to fix at their desk instead of rehearsing. Every convention violation you leave behind is rework you're creating for the user.
If you wrote slides.typ or notes.typ WITHOUT having read the paper (Phase 1), DELETE them and start over from Phase 1. Content written without source material is hallucinated content — it cannot be patched, only rewritten.
| Rule | Trigger | Action | Permission |
|---|---|---|---|
| R1: Bug | Typst compilation error, syntax error, broken import | Fix → recompile → verify | Auto |
| R2: Missing Critical | Missing template file, missing asset, broken theme reference | Add/fix → recompile → verify → track [R2] | Auto |
| R3: Blocking | Typst version incompatibility, font not found, package conflict | Fix blocker → verify proceeds → track [R3] | Auto |
| R4: Structural | Outline restructuring, section reordering, changing presentation proportions | STOP → present to user → track [R4] | Ask user |
Priority: R4 (STOP) > R1-R3 (auto) > unsure = R4
After completing Phase 3, report: Total deviations: N auto-fixed (R1: X, R2: Y, R3: Z). Impact: [assessment].
| Excuse | Reality | Do Instead |
|---|---|---|
| "Blank lines between bullets waste space" | This is the project's strict convention | Add blank lines. Adjust font size if needed. |
| "cetz-plot would look better for this chart" | cetz-plot conflicts with secreg's cetz 0.3.2 | Use #table() for data visualization |
| "I'll use ## for slide titles" | Typst uses = not # for headings | Use === for slide titles |
| "I don't need qr: none" | The theme expects it; compilation may fail | Always include qr: none |
#import "@preview/cetz-plot" or any cetz-plot → STOP. Use tables instead.## or ### for headings → STOP. Typst uses =, ==, ===.qr: none from config-info → STOP. Add it.cetz.canvas(length: 1cm, ...) or similar small lengths → STOP. Use 2em minimum.cetz.canvas without // Storytelling: comment → STOP. Add it.)'s or ]'s → STOP. Use \u{2019}s for smart apostrophe.$100 without escaping → STOP. Use \$100.#image() without #align(center) → STOP. Center it.#callout[] to a slide with 3+ #pause → STOP. Split the slide.calc expression.File header:
// Speaker Notes: [Paper Title]
// [Authors]
// Presentation at [Venue], [Date]
#set page(
number-align: center,
numbering: "1 of 1",
margin: (x: 1in, y: 1in),
header: text(size: 10pt, fill: gray)[_Paper Title --- Speaker Notes_],
)
#set text(size: 12pt)
#set heading(numbering: "I.A.1.")
#show heading.where(level: 1): set text(size: 16pt)
#show heading.where(level: 2): set text(size: 14pt)
#show heading.where(level: 3): set text(size: 12pt)
Notes style: Flowing prose with conversational tone — as if talking to the audience. NOT slide bullet recaps. Each section starts with a timing target:
= Section Title
#text(size: 10pt, fill: gray)[_Target: ~15 minutes. Brief description of section goal._]
== Subsection
- Opening remark that sets context for this subsection. Explain the key idea in plain language, as if speaking to a faculty audience.
- Transition to the next point. Connect it to what was just said. Use specific numbers and citations from the paper.
Notes bullet spacing: Same convention — blank lines between all bullet items.
- (not --)qr: none present in config-infoinset: 10pt minimum#align(center)calc module)#callout[] + 3+ #pause on same slideIf convention violations persist after 3 fix-and-recheck cycles, escalate to user.
Before proceeding to Phase 4, dispatch an independent reviewer subagent:
Agent(prompt="""
You are an independent reviewer. Check these files against the Typst workshop constraints.
Step 1 — Constraint checks (hard block):
Run: cd [presentation directory] && uv run python3 ${CLAUDE_SKILL_DIR}/../../references/constraints/check-all.py .
Report any failures.
Step 2 — Convention review (judgment):
Run: uv run python3 ${CLAUDE_SKILL_DIR}/../../scripts/load-constraints.py workshop
Review slides.typ and notes.typ against loaded conventions.
Step 3 — Cross-check:
- Every slide in slides.typ has corresponding coverage in notes.typ
- Slide titles are complete sentences
- Notes are flowing prose, not bullet recaps
- Every factual claim traces to a paper inventory ID (F/T/R/A)
Report violations:
| # | Severity | Constraint | Location | Issue |
Be thorough. Do NOT soften findings.
""", subagent_type="general-purpose",
allowed_tools=["Read", "Grep", "Glob", "Bash"])
The reviewer MUST NOT have Write or Edit access. A reviewer that "fixes" issues it finds bypasses the plan-execute-verify cycle. Issues go back to the generator for fixing.
After the reviewer subagent returns, main chat follows these boundaries:
| Verification (main chat CAN do) | Investigation (main chat CANNOT do) |
|---|---|
| Read reviewer's violation report | Read slides.typ/notes.typ directly |
| Check compilation (typst compile) | Grep through .typ files |
| Check gate artifact exists | Edit slides.typ/notes.typ directly |
| Run convention check scripts | "Quick fix" a reported issue |
If fixes are needed, dispatch a new subagent to make the fixes, then re-dispatch the reviewer.
Structural gate artifact: After reviewer approves, write .planning/SLIDES_REVIEWED.md:
---
status: APPROVED
phase: generate
reviewed_at: [timestamp]
reviewer: independent subagent
implements: "Phase 3 — slide and notes generation with independent review"
requires: "OUTLINE_APPROVED.md, SOURCES_VERIFIED.md"
provides: "slides.typ, notes.typ (reviewed and convention-compliant)"
affects: "presentation/slides.typ, presentation/notes.typ"
iterations: [N]
slides_count: [N]
notes_sections: [N]
deviations: {r1: [X], r2: [Y], r3: [Z], r4: [W]}
---
Slides and notes reviewed by independent subagent. [Summary of review outcome].
Phase 4 will refuse to start without this file.
IMMEDIATELY proceed to Phase 4 after review gate passes.
Responsibility: Compile both files, run widow detection, and verify correctness.
.planning/SLIDES_REVIEWED.md exists with status: APPROVEDslides.typ exists in presentation directorynotes.typ exists in presentation directoryIf .planning/SLIDES_REVIEWED.md is missing, STOP. Return to Phase 3 and complete the artifact review gate.
Compile slides:
cd [presentation directory] && typst compile slides.typ
Compile notes:
cd [presentation directory] && typst compile notes.typ
If compilation fails: Read the error, fix the issue, recompile (max 3 attempts per file).
Run PDF widow detection (mandatory after every successful compile):
DETECT_WIDOWS=$(command ls -d ~/.claude/plugins/cache/tinymist-plugin/tinymist/*/skills/typst-widow-orphan/scripts/detect_widows.py 2>/dev/null | sort -V | tail -1) && uv run python3 "$DETECT_WIDOWS" slides.pdf
Run overflow detection (after successful compile):
# Compile in handout mode and query for overflow metadata
cd [presentation directory] && typst compile slides.typ --input handout=true slides-handout.pdf && \
typst query slides.typ '<val>' --field value --root . 2>/dev/null | \
uv run python3 ${CLAUDE_SKILL_DIR}/../../scripts/checks/overflow.py
Visual-verify all diagrams (if any CeTZ or Fletcher diagrams exist):
# Check if diagrams exist
rg -c 'cetz.canvas\|fletcher-diagram' slides.typ
If diagrams found, run visual-verify loop for each:
Verify source fidelity:
Verify metadata:
Two-leg verification:
Leg 1 — Constraint checks (hard block): Run all auto-discovered .py check scripts:
cd [presentation directory] && uv run python3 ${CLAUDE_SKILL_DIR}/../../references/constraints/check-all.py .
Leg 2 — Convention review (judgment): For each convention listed by check-all.py (.md without .py), manually verify:
Skipping verification steps to "finish faster" is anti-helpful — the presenter discovers widows, overflow, or wrong numbers at the podium. Every skipped check is a defect you shipped instead of caught. Verification is the service, not overhead.
qr: none present in config-info-- as marker))'s / ]'s)calc module)length: 2em minimum + // Storytelling: comment (if used)\$)*Label:* before bullets)Present results to user:
Workshop presentation complete:
- slides.typ: [N] slides across [M] sections
- notes.typ: speaker notes with timing targets
- Both files compile cleanly
- PDF widow detection: 0 widows
Files: [presentation directory]/slides.typ, notes.typ
PDFs: [presentation directory]/slides.pdf, notes.pdf
After the user reviews, they can use /workshop-revise to make targeted changes to the slides or notes.
Discover and read the workshop-revise skill:
Read ${CLAUDE_SKILL_DIR}/../workshop-revise/SKILL.md for midpoint re-entry.