@heylemon/lemonade 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Validate a .docx file for quality, consistency, and standards compliance.
4
+
5
+ Checks for:
6
+ - XML well-formedness and schema validity
7
+ - Font consistency (warns if > 3 fonts)
8
+ - Paragraph length and structure
9
+ - Empty headings
10
+ - Manual bullet characters (should use List APIs)
11
+ - Table consistency
12
+ - Page setup
13
+
14
+ Usage: python validate.py document.docx
15
+ """
16
+
17
+ import sys
18
+ import os
19
+ import zipfile
20
+ import xml.etree.ElementTree as ET
21
+ from pathlib import Path
22
+
23
+ try:
24
+ from docx import Document
25
+ from docx.shared import Pt
26
+ except ImportError:
27
+ print("ERROR: python-docx not installed. Run: pip install python-docx --break-system-packages")
28
+ sys.exit(1)
29
+
30
+
31
+ def validate_xml_wellformedness(docx_path):
32
+ """Check that the document XML is well-formed."""
33
+ issues = []
34
+ try:
35
+ with zipfile.ZipFile(docx_path, 'r') as zip_ref:
36
+ try:
37
+ xml_content = zip_ref.read('word/document.xml')
38
+ ET.fromstring(xml_content)
39
+ except ET.ParseError as e:
40
+ issues.append(f"XML Parse Error: {str(e)}")
41
+ except zipfile.BadZipFile:
42
+ issues.append("File is not a valid ZIP archive (docx must be ZIP)")
43
+ except Exception as e:
44
+ issues.append(f"Could not read document.xml: {str(e)}")
45
+ return issues
46
+
47
+
48
+ def validate_document(docx_path):
49
+ """Validate document structure, content, and formatting."""
50
+ issues = []
51
+ warnings = []
52
+ info = []
53
+
54
+ if not os.path.exists(docx_path):
55
+ print(f"ERROR: File not found: {docx_path}")
56
+ sys.exit(1)
57
+
58
+ # Check XML well-formedness first
59
+ xml_issues = validate_xml_wellformedness(docx_path)
60
+ if xml_issues:
61
+ issues.extend(xml_issues)
62
+ return issues, warnings, info
63
+
64
+ try:
65
+ doc = Document(docx_path)
66
+ except Exception as e:
67
+ issues.append(f"Could not open document: {str(e)}")
68
+ return issues, warnings, info
69
+
70
+ # ─── Page setup checks ───
71
+ section = doc.sections[0] if doc.sections else None
72
+ if section:
73
+ from docx.shared import Inches
74
+ if section.page_width and section.page_width < Inches(7):
75
+ warnings.append("Page width is unusually narrow (< 7 inches)")
76
+ if section.left_margin and section.left_margin < Inches(0.5):
77
+ warnings.append("Left margin is very small (< 0.5 inches) — may look cramped")
78
+ if section.right_margin and section.right_margin < Inches(0.5):
79
+ warnings.append("Right margin is very small (< 0.5 inches) — may look cramped")
80
+
81
+ # ─── Paragraph checks ───
82
+ paragraph_count = 0
83
+ heading_count = 0
84
+ empty_paragraphs = 0
85
+ consecutive_empty = 0
86
+ max_consecutive_empty = 0
87
+ fonts_used = set()
88
+ sizes_used = set()
89
+ manual_bullets = []
90
+
91
+ # Unicode bullet characters
92
+ bullet_chars = {'•', '◦', '▪', '●', '○', '■', '–', '—', '-', '*'}
93
+
94
+ for p in doc.paragraphs:
95
+ paragraph_count += 1
96
+ text = p.text.strip()
97
+
98
+ # Track empty paragraphs
99
+ if not text:
100
+ empty_paragraphs += 1
101
+ consecutive_empty += 1
102
+ max_consecutive_empty = max(max_consecutive_empty, consecutive_empty)
103
+ else:
104
+ consecutive_empty = 0
105
+
106
+ # Check headings
107
+ if p.style.name.startswith("Heading"):
108
+ heading_count += 1
109
+ # Heading shouldn't end with a period
110
+ if text.endswith("."):
111
+ warnings.append(f"Heading ends with period: \"{text[:50]}\"")
112
+ # Heading shouldn't be all caps (unless very short)
113
+ if text == text.upper() and len(text) > 3:
114
+ if "1" not in p.style.name:
115
+ warnings.append(f"Heading is ALL CAPS (use Title Case): \"{text[:50]}\"")
116
+ # Check for empty headings
117
+ if not text:
118
+ issues.append(f"Empty heading found in style {p.style.name}")
119
+
120
+ # Detect manual bullet characters
121
+ if not p.style.name.startswith("List"):
122
+ # Check if text starts with bullet-like characters
123
+ for bullet_char in bullet_chars:
124
+ if text.startswith(bullet_char):
125
+ manual_bullets.append(f"Manual bullet '{bullet_char}' in: \"{text[:50]}\"")
126
+ break
127
+
128
+ # Track fonts and sizes
129
+ for run in p.runs:
130
+ if run.font.name:
131
+ fonts_used.add(run.font.name)
132
+ if run.font.size:
133
+ sizes_used.add(run.font.size)
134
+
135
+ # Check for very long paragraphs (over 500 chars)
136
+ if len(text) > 500:
137
+ word_count = len(text.split())
138
+ warnings.append(f"Very long paragraph ({len(text)} chars, {word_count} words): \"{text[:50]}...\" — consider breaking up")
139
+
140
+ # Check for excessive spacing/empty paragraphs
141
+ if len(text) == 0 and paragraph_count > 1:
142
+ # This is tracked separately
143
+ pass
144
+
145
+ # Report manual bullet issues
146
+ if manual_bullets:
147
+ for bullet_issue in manual_bullets[:5]: # Show first 5
148
+ issues.append(f"Manual bullet character detected: {bullet_issue} — use List Bullet style instead")
149
+ if len(manual_bullets) > 5:
150
+ issues.append(f"... and {len(manual_bullets) - 5} more manual bullets")
151
+
152
+ # ─── Table checks ───
153
+ table_issues = 0
154
+ for ti, table in enumerate(doc.tables, 1):
155
+ if len(table.rows) == 0:
156
+ issues.append(f"Table {ti} has no rows")
157
+ table_issues += 1
158
+ if len(table.columns) == 0:
159
+ issues.append(f"Table {ti} has no columns")
160
+ table_issues += 1
161
+ # Check for empty cells in header row
162
+ if table.rows:
163
+ first_row = table.rows[0]
164
+ for ci, cell in enumerate(first_row.cells):
165
+ if not cell.text.strip():
166
+ warnings.append(f"Table {ti}: empty header cell in column {ci+1}")
167
+
168
+ # ─── Font consistency checks ───
169
+ if len(fonts_used) > 3:
170
+ warnings.append(f"Too many fonts ({len(fonts_used)}): {', '.join(sorted(fonts_used))} — keep to 2 fonts max (heading + body)")
171
+ if len(sizes_used) > 8:
172
+ warnings.append(f"Many different font sizes used ({len(sizes_used)}) — use a consistent typography scale")
173
+
174
+ # ─── Structure checks ───
175
+ if max_consecutive_empty > 2:
176
+ warnings.append(f"Found {max_consecutive_empty} consecutive empty paragraphs — use spacing instead")
177
+
178
+ if heading_count == 0 and paragraph_count > 15:
179
+ warnings.append("No headings found in a longer document — add headings for structure")
180
+
181
+ if paragraph_count < 3:
182
+ warnings.append("Document is very short — consider if this is complete")
183
+
184
+ # ─── Summary info ───
185
+ info.append(f"Paragraphs: {paragraph_count}")
186
+ info.append(f"Headings: {heading_count}")
187
+ info.append(f"Tables: {len(doc.tables)}")
188
+ info.append(f"Sections: {len(doc.sections)}")
189
+ info.append(f"Fonts: {', '.join(sorted(fonts_used)) if fonts_used else 'default only'}")
190
+ if empty_paragraphs > 0:
191
+ info.append(f"Empty paragraphs: {empty_paragraphs}")
192
+
193
+ return issues, warnings, info
194
+
195
+
196
+ def main():
197
+ if len(sys.argv) < 2:
198
+ print("Usage: python validate.py document.docx")
199
+ sys.exit(1)
200
+
201
+ docx_path = sys.argv[1]
202
+ issues, warnings, info = validate_document(docx_path)
203
+
204
+ # ─── Report ───
205
+ print(f"\n{'═' * 60}")
206
+ print(f" Document Validation: {os.path.basename(docx_path)}")
207
+ print(f"{'═' * 60}\n")
208
+
209
+ for item in info:
210
+ print(f" ℹ {item}")
211
+ print()
212
+
213
+ if issues:
214
+ print(f" ✘ {len(issues)} ISSUE(S):")
215
+ for issue in issues:
216
+ print(f" • {issue}")
217
+ print()
218
+
219
+ if warnings:
220
+ print(f" ⚠ {len(warnings)} WARNING(S):")
221
+ for w in warnings:
222
+ print(f" • {w}")
223
+ print()
224
+
225
+ if not issues and not warnings:
226
+ print(" ✔ No issues found — document looks clean!\n")
227
+ return 0
228
+ elif not issues:
229
+ print(" ✔ No critical issues (warnings only)\n")
230
+ return 0
231
+ else:
232
+ print(f" ✘ {len(issues)} critical issue(s) need fixing\n")
233
+ return 1
234
+
235
+
236
+ if __name__ == "__main__":
237
+ sys.exit(main())
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  Validate a .docx file for common formatting issues.
4
+ Checks typography, spacing, structure, and consistency.
5
+
6
+ Usage: python validate_doc.py document.docx
4
7
  """
5
8
 
6
- import os
7
9
  import sys
10
+ import os
8
11
 
9
12
  try:
10
13
  from docx import Document
11
- from docx.shared import Inches
14
+ from docx.shared import Pt, Inches
12
15
  except ImportError:
13
16
  print("ERROR: python-docx not installed. Run: pip install python-docx --break-system-packages")
14
17
  sys.exit(1)
@@ -17,55 +20,133 @@ except ImportError:
17
20
  def validate(path):
18
21
  issues = []
19
22
  warnings = []
23
+ info = []
20
24
 
21
25
  if not os.path.exists(path):
22
26
  print(f"ERROR: File not found: {path}")
23
- return 1
27
+ sys.exit(1)
24
28
 
25
29
  doc = Document(path)
26
30
 
31
+ # ── Page setup checks ──
27
32
  section = doc.sections[0]
33
+ if section.page_width and section.page_width < Inches(7):
34
+ warnings.append("Page width is unusually narrow (< 7 inches)")
28
35
  if section.left_margin and section.left_margin < Inches(0.5):
29
- warnings.append("Left margin is very small (< 0.5 inches)")
36
+ warnings.append("Left margin is very small (< 0.5 inches) — may look cramped")
30
37
  if section.right_margin and section.right_margin < Inches(0.5):
31
- warnings.append("Right margin is very small (< 0.5 inches)")
38
+ warnings.append("Right margin is very small (< 0.5 inches) — may look cramped")
32
39
 
40
+ # ── Paragraph checks ──
41
+ paragraph_count = 0
33
42
  heading_count = 0
43
+ empty_paragraphs = 0
44
+ consecutive_empty = 0
45
+ max_consecutive_empty = 0
34
46
  fonts_used = set()
47
+ sizes_used = set()
35
48
 
36
49
  for p in doc.paragraphs:
50
+ paragraph_count += 1
51
+
52
+ # Track empty paragraphs
53
+ if not p.text.strip():
54
+ empty_paragraphs += 1
55
+ consecutive_empty += 1
56
+ max_consecutive_empty = max(max_consecutive_empty, consecutive_empty)
57
+ else:
58
+ consecutive_empty = 0
59
+
60
+ # Check headings
37
61
  if p.style.name.startswith("Heading"):
38
62
  heading_count += 1
63
+ # Heading shouldn't end with a period
39
64
  if p.text.strip().endswith("."):
40
- warnings.append(f'Heading ends with period: "{p.text.strip()[:50]}"')
41
-
42
- if p.text.strip().startswith(("•", "◦", "▪", "●", "○", "■", "–", "—")):
43
- if not p.style.name.startswith("List"):
44
- issues.append(f'Manual bullet character detected: "{p.text.strip()[:50]}"')
65
+ warnings.append(f"Heading ends with period: \"{p.text.strip()[:50]}\"")
66
+ # Heading shouldn't be all caps (unless H1)
67
+ if p.text.strip() == p.text.strip().upper() and len(p.text.strip()) > 3:
68
+ if "1" not in p.style.name:
69
+ warnings.append(f"Heading is all caps (consider title case): \"{p.text.strip()[:50]}\"")
45
70
 
71
+ # Track fonts and sizes
46
72
  for run in p.runs:
47
73
  if run.font.name:
48
74
  fonts_used.add(run.font.name)
75
+ if run.font.size:
76
+ sizes_used.add(run.font.size)
77
+
78
+ # Check for very long paragraphs (over 300 words)
79
+ word_count = len(p.text.split())
80
+ if word_count > 300:
81
+ warnings.append(f"Very long paragraph ({word_count} words): \"{p.text.strip()[:50]}...\" — consider breaking up")
49
82
 
83
+ # Check for unicode bullet characters (should use list styles instead)
84
+ if p.text.strip().startswith(("•", "◦", "▪", "●", "○", "■", "–", "—")):
85
+ if not p.style.name.startswith("List"):
86
+ issues.append(f"Manual bullet character detected: \"{p.text.strip()[:50]}\" — use List Bullet style instead")
87
+
88
+ # ── Table checks ──
89
+ for ti, table in enumerate(doc.tables):
90
+ if len(table.rows) == 0:
91
+ issues.append(f"Table {ti+1} has no rows")
92
+ if len(table.columns) == 0:
93
+ issues.append(f"Table {ti+1} has no columns")
94
+ # Check for empty cells in header row
95
+ if table.rows:
96
+ first_row = table.rows[0]
97
+ for ci, cell in enumerate(first_row.cells):
98
+ if not cell.text.strip():
99
+ warnings.append(f"Table {ti+1}: empty header cell in column {ci+1}")
100
+
101
+ # ── Consistency checks ──
50
102
  if len(fonts_used) > 3:
51
- warnings.append(f"Too many fonts used ({len(fonts_used)}): {', '.join(sorted(fonts_used))}")
52
- if heading_count == 0 and len(doc.paragraphs) > 10:
53
- warnings.append("No headings found in a long document")
103
+ warnings.append(f"Too many fonts used ({len(fonts_used)}): {', '.join(sorted(fonts_used))} — keep to 2 max")
104
+ if len(sizes_used) > 6:
105
+ warnings.append(f"Many font sizes used ({len(sizes_used)}) — consider a tighter typography scale")
106
+
107
+ if max_consecutive_empty > 2:
108
+ warnings.append(f"Found {max_consecutive_empty} consecutive empty paragraphs — use spacing instead")
109
+
110
+ if heading_count == 0 and paragraph_count > 10:
111
+ warnings.append("No headings found in a long document — add headings for structure")
112
+
113
+ # ── Summary ──
114
+ info.append(f"Paragraphs: {paragraph_count}")
115
+ info.append(f"Headings: {heading_count}")
116
+ info.append(f"Tables: {len(doc.tables)}")
117
+ info.append(f"Sections: {len(doc.sections)}")
118
+ info.append(f"Fonts: {', '.join(sorted(fonts_used)) if fonts_used else 'default only'}")
119
+
120
+ # ── Report ──
121
+ print(f"\n{'═' * 50}")
122
+ print(f" Document Validation: {os.path.basename(path)}")
123
+ print(f"{'═' * 50}\n")
124
+
125
+ for i in info:
126
+ print(f" ℹ {i}")
127
+ print()
54
128
 
55
129
  if issues:
56
- print("Validation failed:")
130
+ print(f" ✖ {len(issues)} ISSUE(S):")
57
131
  for issue in issues:
58
- print(f"- {issue}")
59
- return 1
132
+ print(f" {issue}")
133
+ print()
60
134
 
61
135
  if warnings:
62
- print("Validation warnings:")
63
- for warning in warnings:
64
- print(f"- {warning}")
65
- return 0
136
+ print(f" ⚠ {len(warnings)} WARNING(S):")
137
+ for w in warnings:
138
+ print(f" {w}")
139
+ print()
66
140
 
67
- print("Validation passed: no issues found")
68
- return 0
141
+ if not issues and not warnings:
142
+ print(" ✔ No issues found — document looks clean!\n")
143
+ return 0
144
+ elif not issues:
145
+ print(" ✔ No critical issues (warnings only)\n")
146
+ return 0
147
+ else:
148
+ print(f" ✖ {len(issues)} issue(s) need fixing\n")
149
+ return 1
69
150
 
70
151
 
71
152
  if __name__ == "__main__":
@@ -1,24 +1,181 @@
1
1
  ---
2
- name: pptx
3
- description: "Create professional, polished slide decks using a deterministic PptxGenJS script. Trigger for presentation, slides, deck, and .pptx requests."
4
- license: Proprietary. LICENSE.txt has complete terms
2
+ name: pro-slides
3
+ description: "Create professional, visually polished slide decks pitch decks, board updates, keynotes, team presentations, and sales decks. Use this skill whenever the user wants to create a presentation, slide deck, pitch deck, keynote, or slides. Also trigger when someone says 'make me a deck', 'create a presentation', 'build slides', 'pitch deck', 'board update deck', or needs any visual presentation deliverable. Trigger for 'pptx', 'powerpoint', 'slides', 'deck', 'presentation'."
5
4
  ---
6
5
 
7
- # Pro Slides for PPTX
6
+ # Pro Slides Professional Presentation Creation
8
7
 
9
- Use the scripted path:
8
+ This skill creates polished slide decks using PptxGenJS, a powerful JavaScript presentation engine. It generates visually designed presentations with consistent color themes, typography, spacing, and data visualization — not plain text slides.
10
9
 
10
+ ## Quick Reference
11
+
12
+ | Task | Documentation | Command |
13
+ |------|---|---|
14
+ | **Analyze existing presentation** | — | `python -m markitdown presentation.pptx` |
15
+ | **Edit slides from a template** | Read `editing.md` | Unpack → Edit → Pack |
16
+ | **Create presentation from scratch** | Read `pptxgenjs.md` | Write PptxGenJS code inline |
17
+ | **Convert to images for QA** | See QA section | `soffice --headless --convert-to pdf output.pptx && pdftoppm -jpeg -r 150 output.pdf slide` |
18
+
19
+ ## Design-First Approach
20
+
21
+ **Do not create boring slides.** The difference between great presentations and forgettable ones comes down to deliberate design choices: color, contrast, hierarchy, and visual consistency.
22
+
23
+ ### Before Starting
24
+
25
+ Before writing any code, make these decisions:
26
+
27
+ 1. **Pick a bold color palette** — Don't default to blue. Match the topic. Sustainability reports should feel natural (greens). Sales decks should feel urgent (warm reds/oranges). Finance should feel trustworthy (teals/navy). See palette table below.
28
+
29
+ 2. **Establish visual dominance** — Don't create visual equality between elements. One color should dominate 60-70% of the slides (usually the accent color in callouts and key numbers). Everything else recedes.
30
+
31
+ 3. **Design the contrast sandwich** — Title slides and closing slides have dark backgrounds with light text. Content slides have light backgrounds with dark text. This creates visual rhythm and keeps viewers engaged.
32
+
33
+ 4. **Commit to a visual motif** — Every slide should have at least one deliberate visual element: an accent bar, a stat card with color, an icon, or a highlighted quote. No empty slides.
34
+
35
+ ### Color Palettes
36
+
37
+ Choose one palette for the entire deck. Consistency beats variety.
38
+
39
+ | Palette Name | Primary | Secondary | Accent | Best For |
40
+ |---|---|---|---|---|
41
+ | **Midnight Executive** | `1E2761` | `CADCFC` | `F5A623` | Board decks, investor pitches, formal settings |
42
+ | **Forest & Moss** | `2C5F2D` | `97BC62` | `F5F5F5` | Sustainability, growth, environmental topics |
43
+ | **Coral Energy** | `F96167` | `F9E795` | `2F3C7E` | Marketing, launches, startups, excitement |
44
+ | **Warm Terracotta** | `B85042` | `E7E8D1` | `A7BEAE` | Culture, HR, team building, warmth |
45
+ | **Ocean Gradient** | `065A82` | `1C7293` | `21295C` | Tech, product, data, trustworthiness |
46
+ | **Charcoal Minimal** | `36454F` | `F2F2F2` | `212121` | Design, minimal aesthetics, agency work |
47
+ | **Teal Trust** | `028090` | `00A896` | `02C39A` | Healthcare, finance, security, reliability |
48
+ | **Cherry Bold** | `990011` | `FCF6F5` | `2F3C7E` | Sales, urgency, high-stakes decisions |
49
+
50
+ ### For Each Slide: Design Checklist
51
+
52
+ Every content slide needs visual structure. Choose from these approaches:
53
+
54
+ **Layout Options:**
55
+ - **Two-column layout** — Left text, right callout. Left bullets, right image. Left data, right quote.
56
+ - **Icon + text rows** — Small icons on left, supporting text on right. Great for feature lists or process steps.
57
+ - **2x2 grid** — Four quadrants for options, scenarios, or related concepts.
58
+ - **Half-bleed image** — Image on right, text on left. Image extends to slide edge for drama.
59
+ - **Large stat + context** — Big number top-center, supporting bullets below.
60
+
61
+ **Data Display:**
62
+ - **Large stat callouts** — Use color (from palette accent). 60pt numbers, 12pt labels.
63
+ - **Comparison columns** — Two cards, one accent color each, bullets inside.
64
+ - **Timeline** — Vertical or horizontal bars with milestones.
65
+ - **Simple table** — Only 2-3 columns. Striped rows for readability.
66
+
67
+ **Visual Polish:**
68
+ - **Icons in circles** — Accent color background, white icons inside.
69
+ - **Italic accents** — Key phrases in italic to break up text uniformity.
70
+ - **Subtle shadows** — Cards have soft drop shadows (blur 6, offset 2, opacity 0.08).
71
+ - **Accent bars** — Thin colored rectangles (0.06 inches) above card titles.
72
+ - **Breathing room** — 0.5" margins minimum. 0.3-0.5" gap between content blocks.
73
+
74
+ ### Typography
75
+
76
+ Use one font pairing for the entire deck. Consistency.
77
+
78
+ | Pairing | Heading Font | Body Font | Best For |
79
+ |---|---|---|---|
80
+ | Professional | Arial Black | Arial | Corporate, finance, formal |
81
+ | Friendly | Trebuchet | Calibri | Tech, startups, modern |
82
+ | Classic | Georgia | Calibri | Editorial, thoughtful content |
83
+ | Modern | Arial Black | Helvetica | Design, tech, minimal |
84
+
85
+ **Size Guide:**
86
+ - Slide titles: 20pt (content slides) or 28pt (section slides)
87
+ - Section headings: 18pt
88
+ - Body text: 14pt
89
+ - Captions / footnotes: 10-11pt
90
+ - Stat numbers: 60pt
91
+ - Stat labels: 12pt
92
+
93
+ **Spacing:**
94
+ - 0.5" minimum margins on all sides
95
+ - 0.3-0.5" vertical gap between content blocks
96
+ - Never center body text (left-align only)
97
+ - Use `paraSpaceAfter: 6` for bullet spacing, never `lineSpacing`
98
+
99
+ ### What to Avoid
100
+
101
+ - **Don't repeat the same layout** — Two content slides in a row can feel monotonous. Vary between text-heavy, stat cards, comparisons, and two-column layouts.
102
+ - **Don't center body text** — Always left-align body copy. Centers work only for titles, stats, and closing slides.
103
+ - **Don't skimp on size contrast** — If you have a headline and body text, the headline must be significantly larger (at least 1.4x). Avoid small variations that create visual confusion.
104
+ - **Don't default to blue** — Blue is overdone. It's the safe choice. Pick a palette that matches the content's emotional tone.
105
+ - **Never add accent lines under titles** — This is the hallmark of AI-generated slides. If you add an underline, make it purposeful (color bar on stat cards) not decorative.
106
+ - **Don't create text-only slides** — Every slide needs at least one visual anchor: a stat card, an accent color, an icon, or a highlighted quote.
107
+
108
+ ## QA Process
109
+
110
+ **Assume there are problems. Your job is to find them.**
111
+
112
+ The QA process is not optional. It's the difference between generating slides and delivering presentation-ready decks.
113
+
114
+ ### Content QA
115
+
116
+ After generation, extract and verify the text:
11
117
  ```bash
12
- npm install -g pptxgenjs 2>/dev/null
13
- node scripts/create_pptx.js spec.json output.pptx
118
+ python -m markitdown output.pptx > extracted.txt
14
119
  ```
120
+ Check:
121
+ - All intended content is present
122
+ - No text truncation or overflow
123
+ - No garbled Unicode or smart quotes breaking bullets
124
+ - Slide order matches the intended narrative
125
+ - Speaker notes are complete
15
126
 
16
- Use `references/spec-format.md` for slide schema and themes.
17
-
18
- Recommended QA:
127
+ ### Visual QA
19
128
 
129
+ Convert to images and inspect with your eyes:
20
130
  ```bash
21
- python -m markitdown output.pptx
22
- python scripts/office/soffice.py --headless --convert-to pdf output.pptx
131
+ soffice --headless --convert-to pdf output.pptx
23
132
  pdftoppm -jpeg -r 150 output.pdf slide
24
133
  ```
134
+
135
+ This generates `slide-1.jpg`, `slide-2.jpg`, etc. Open each image and check:
136
+ - **Text overflow** — Is any text cut off at slide edges?
137
+ - **Element overlap** — Are shapes, text, or images on top of each other?
138
+ - **Spacing consistency** — Do margins and gaps look balanced?
139
+ - **Color contrast** — Can you read all text easily? Light text on light background?
140
+ - **Missing elements** — Should there be an icon, image, or accent color that's not there?
141
+ - **Typography** — Do font sizes and weights feel right?
142
+ - **Alignment** — Are elements left-aligned where they should be?
143
+
144
+ ### Verification Loop
145
+
146
+ 1. **Generate** — Write PptxGenJS code, save presentation
147
+ 2. **Convert** — Run soffice + pdftoppm to get images
148
+ 3. **Inspect** — Open slide images in an image viewer
149
+ 4. **Fix** — Identify problems and update the code
150
+ 5. **Re-verify** — Regenerate and check again
151
+ 6. **Repeat** — Continue until at least one fix-and-verify cycle is complete
152
+
153
+ **Do not declare success until you've completed at least one fix-and-verify cycle.** Even a "simple" request often has spacing issues, contrast problems, or missing visual elements that only appear when you see the actual slides.
154
+
155
+ ## Dependencies
156
+
157
+ - **Node.js** (for running PptxGenJS)
158
+ - **pptxgenjs** — JavaScript presentation generation library
159
+ - **Python** with `markitdown` — For text extraction and QA
160
+ - **LibreOffice** (`soffice`) — For PDF conversion
161
+ - **pdftoppm** — For PDF to image conversion (part of poppler-utils)
162
+
163
+ Installation (first time only):
164
+ ```bash
165
+ npm install -g pptxgenjs
166
+ pip install markitdown
167
+ apt-get install libreoffice poppler-utils # Linux
168
+ brew install libreoffice poppler # macOS
169
+ ```
170
+
171
+ ## Workflow Summary
172
+
173
+ 1. **Understand** — Who, what, how long?
174
+ 2. **Design** — Pick a palette, decide on visual approach, sketch slide structure
175
+ 3. **Write** — Create PptxGenJS code inline (see `pptxgenjs.md`)
176
+ 4. **Generate** — Save .pptx file
177
+ 5. **Convert** — Create .jpg images for visual inspection
178
+ 6. **Inspect** — Open images, look for problems
179
+ 7. **Fix** — Update code based on findings
180
+ 8. **Re-verify** — Regenerate and confirm fixes
181
+ 9. **Deliver** — Provide final .pptx to user