Skip to content

Commit b2c5e2a

Browse files
committed
Updated lesson 10 - debugging and fixed a bug that caused invalid components to be generated in slideshows
1 parent e19070f commit b2c5e2a

File tree

10 files changed

+464
-203
lines changed

10 files changed

+464
-203
lines changed

scripts/audit-presentations.js

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,25 @@ const MIN_ITEMS = 3;
1515
const MAX_ITEMS = 5;
1616
const MAX_WORDS = 5;
1717

18+
// Single source of truth: RevealSlideshow.tsx
19+
const REVEAL_SLIDESHOW_PATH = join(
20+
__dirname,
21+
'../website/src/components/PresentationMode/RevealSlideshow.tsx'
22+
);
23+
24+
function getValidVisualComponents() {
25+
const content = readFileSync(REVEAL_SLIDESHOW_PATH, 'utf-8');
26+
const match = content.match(/const VISUAL_COMPONENTS = \{([^}]+)\}/);
27+
if (!match) {
28+
throw new Error('Could not find VISUAL_COMPONENTS in RevealSlideshow.tsx');
29+
}
30+
return match[1]
31+
.split(',')
32+
.map(line => line.trim())
33+
.filter(line => line && !line.startsWith('//'))
34+
.map(line => line.split(':')[0].trim());
35+
}
36+
1837
function auditPresentation(filePath) {
1938
const content = readFileSync(filePath, 'utf-8');
2039
const presentation = JSON.parse(content);
@@ -108,10 +127,24 @@ function auditPresentation(filePath) {
108127
}
109128
});
110129

130+
// Check visual component references
131+
const validComponents = getValidVisualComponents();
132+
const componentViolations = [];
133+
const visualSlides = presentation.slides.filter(s => s.type === 'visual');
134+
for (const slide of visualSlides) {
135+
if (slide.component && !validComponents.includes(slide.component)) {
136+
componentViolations.push({
137+
slide: slide.title,
138+
component: slide.component
139+
});
140+
}
141+
}
142+
111143
return {
112144
title: presentation.metadata?.title || 'Unknown',
113145
violations,
114146
wordCountViolations,
147+
componentViolations,
115148
totalSlides: presentation.slides.length
116149
};
117150
}
@@ -136,11 +169,13 @@ function main() {
136169
console.log('Checking:');
137170
console.log(' • Content arrays (3-5 items rule)');
138171
console.log(' • Takeaway word counts (5 words max)');
139-
console.log(' • Learning objectives word counts (5 words max)\n');
172+
console.log(' • Learning objectives word counts (5 words max)');
173+
console.log(' • Visual component references (must be registered)\n');
140174

141175
const results = [];
142176
let totalViolations = 0;
143177
let totalWordCountViolations = 0;
178+
let totalComponentViolations = 0;
144179

145180
for (const file of files) {
146181
const filePath = join(presentationsDir, file);
@@ -150,8 +185,9 @@ function main() {
150185

151186
const hasViolations = result.violations.length > 0;
152187
const hasWordViolations = result.wordCountViolations.length > 0;
188+
const hasComponentViolations = result.componentViolations.length > 0;
153189

154-
if (hasViolations || hasWordViolations) {
190+
if (hasViolations || hasWordViolations || hasComponentViolations) {
155191
console.log(`❌ ${file}`);
156192
console.log(` Title: ${result.title}`);
157193

@@ -183,6 +219,14 @@ function main() {
183219
});
184220
}
185221

222+
if (hasComponentViolations) {
223+
totalComponentViolations += result.componentViolations.length;
224+
console.log(` Invalid visual components (${result.componentViolations.length}):`);
225+
result.componentViolations.forEach(v => {
226+
console.log(` - Slide "${v.slide}": component "${v.component}" is not registered`);
227+
});
228+
}
229+
186230
console.log('');
187231
}
188232
} catch (error) {
@@ -192,7 +236,9 @@ function main() {
192236

193237
// Summary
194238
console.log('═══════════════════════════════════════════════════════════');
195-
const violatingFiles = results.filter(r => r.violations.length > 0 || r.wordCountViolations.length > 0);
239+
const violatingFiles = results.filter(r =>
240+
r.violations.length > 0 || r.wordCountViolations.length > 0 || r.componentViolations.length > 0
241+
);
196242

197243
if (violatingFiles.length === 0) {
198244
console.log('✅ All presentations pass validation!');
@@ -202,15 +248,18 @@ function main() {
202248
console.log(`Files with violations: ${violatingFiles.length}`);
203249
console.log(` • Content array violations: ${totalViolations}`);
204250
console.log(` • Word count violations: ${totalWordCountViolations}`);
205-
console.log(` • Total: ${totalViolations + totalWordCountViolations}\n`);
251+
console.log(` • Invalid component violations: ${totalComponentViolations}`);
252+
console.log(` • Total: ${totalViolations + totalWordCountViolations + totalComponentViolations}\n`);
206253
console.log('Files needing regeneration:');
207254
violatingFiles.forEach(r => {
208255
const arrayViolations = r.violations.length;
209256
const wordViolations = r.wordCountViolations.length;
210-
const total = arrayViolations + wordViolations;
257+
const compViolations = r.componentViolations.length;
258+
const total = arrayViolations + wordViolations + compViolations;
211259
const details = [];
212260
if (arrayViolations > 0) details.push(`${arrayViolations} array`);
213261
if (wordViolations > 0) details.push(`${wordViolations} word`);
262+
if (compViolations > 0) details.push(`${compViolations} component`);
214263
console.log(` - ${r.file} (${total} total: ${details.join(', ')})`);
215264
});
216265
}

scripts/generate-presentation.js

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,33 @@ const DOCS_DIR = join(__dirname, "../website/docs");
4545
const OUTPUT_DIR = join(__dirname, "output/presentations");
4646
const STATIC_OUTPUT_DIR = join(__dirname, "../website/static/presentations");
4747
const MANIFEST_PATH = join(OUTPUT_DIR, "manifest.json");
48+
const REVEAL_SLIDESHOW_PATH = join(
49+
__dirname,
50+
"../website/src/components/PresentationMode/RevealSlideshow.tsx",
51+
);
52+
53+
/**
54+
* Extract valid visual component names from RevealSlideshow.tsx
55+
* Single source of truth: the VISUAL_COMPONENTS object in the renderer
56+
*/
57+
function getValidVisualComponents() {
58+
const content = readFileSync(REVEAL_SLIDESHOW_PATH, "utf-8");
59+
const match = content.match(
60+
/const VISUAL_COMPONENTS = \{([^}]+)\}/,
61+
);
62+
if (!match) {
63+
throw new Error(
64+
"Could not find VISUAL_COMPONENTS in RevealSlideshow.tsx",
65+
);
66+
}
67+
// Extract component names (the keys of the object)
68+
const componentNames = match[1]
69+
.split(",")
70+
.map((line) => line.trim())
71+
.filter((line) => line && !line.startsWith("//"))
72+
.map((line) => line.split(":")[0].trim());
73+
return componentNames;
74+
}
4875

4976
// Parse command-line arguments
5077
function parseArgs() {
@@ -114,7 +141,8 @@ PRESENTATION STRUCTURE REQUIREMENTS:
114141
- Discussion prompts or questions to ask students
115142
- Real-world examples to reference
116143
✓ DO: Preserve important code examples as slide content
117-
✓ DO: Identify which visual components to use (CapabilityMatrix, UShapeAttentionCurve, WorkflowCircle, GroundingComparison, ContextWindowMeter, AbstractShapesVisualization, etc.)
144+
✓ DO: ONLY use these registered visual components: ${getValidVisualComponents().join(", ")}
145+
✗ DO NOT: Invent or reference visual components not in this list
118146
✓ DO: Generate exactly 4 learning objectives (no more, no less)
119147
✓ DO: Keep each learning objective to 5 words or fewer - THIS IS STRICTLY ENFORCED
120148
- Good: "Master active context engineering" (4 words) ✓
@@ -134,7 +162,7 @@ SLIDE TYPES:
134162
4. **Code Comparison Slide**: Side-by-side code examples (especially for prompt examples)
135163
5. **Code Execution Slide**: Step-by-step visualization of execution flows (agent loops, algorithms, workflows)
136164
6. **Comparison Slide**: Effective vs ineffective patterns (bullet points)
137-
7. **Visual Slide**: Custom component (CapabilityMatrix, etc.)
165+
7. **Visual Slide**: ONLY when source has [VISUAL_COMPONENT: X] marker - NEVER invent components
138166
8. **Key Takeaway Slide**: Summary of section or lesson
139167
140168
HANDLING CODE BLOCKS:
@@ -354,6 +382,8 @@ Example:
354382
355383
If you see [VISUAL_COMPONENT: X] anywhere in the content, it MUST become a visual slide.
356384
385+
CRITICAL CONSTRAINT: NEVER create a "visual" slide type unless there is an explicit [VISUAL_COMPONENT: X] marker in the source content. Do NOT invent visual components. If no marker exists, use "concept", "comparison", or "codeExecution" slide types instead.
386+
357387
CODE EXECUTION SLIDES:
358388
359389
Use the "codeExecution" slide type to visualize step-by-step processes like:
@@ -660,7 +690,7 @@ Like regular comparison slides, codeComparison also supports the "neutral" flag:
660690
{
661691
"type": "visual",
662692
"title": "Visual Component",
663-
"component": "CapabilityMatrix | UShapeAttentionCurve | WorkflowCircle | GroundingComparison | ContextWindowMeter | AbstractShapesVisualization",
693+
"component": "${getValidVisualComponents().join(" | ")}",
664694
"caption": "Description of what the visual shows",
665695
"speakerNotes": { ... }
666696
},
@@ -974,6 +1004,35 @@ function validateComponents(content, presentation) {
9741004
};
9751005
}
9761006

1007+
/**
1008+
* Validate that visual slides reference registered components
1009+
* Prevents AI from inventing non-existent component names
1010+
* @param {object} presentation - Generated presentation object
1011+
* @returns {object} Validation result with invalid component references
1012+
*/
1013+
function validateVisualComponentsExist(presentation) {
1014+
const validComponents = getValidVisualComponents();
1015+
const visualSlides = presentation.slides.filter((s) => s.type === "visual");
1016+
const issues = [];
1017+
1018+
for (const slide of visualSlides) {
1019+
if (slide.component && !validComponents.includes(slide.component)) {
1020+
issues.push({
1021+
slide: slide.title,
1022+
component: slide.component,
1023+
reason: `Component "${slide.component}" is not registered`,
1024+
});
1025+
}
1026+
}
1027+
1028+
return {
1029+
valid: issues.length === 0,
1030+
issues,
1031+
totalVisualSlides: visualSlides.length,
1032+
validComponents,
1033+
};
1034+
}
1035+
9771036
/**
9781037
* Validate semantic correctness of comparison slides
9791038
* Checks that better/effective options are on the RIGHT (green ✓)
@@ -1492,6 +1551,29 @@ async function generatePresentation(filePath, manifest, config) {
14921551
// This allows us to write the presentation file even when validation fails
14931552
const validationErrors = [];
14941553

1554+
// Validate visual components exist in registry
1555+
// CRITICAL: This prevents AI from inventing non-existent components
1556+
const componentRegistryValidation =
1557+
validateVisualComponentsExist(presentation);
1558+
if (!componentRegistryValidation.valid) {
1559+
console.log(
1560+
` ❌ BUILD FAILURE: ${componentRegistryValidation.issues.length} invalid visual component(s):`,
1561+
);
1562+
componentRegistryValidation.issues.forEach((issue) => {
1563+
console.log(` - Slide "${issue.slide}": ${issue.reason}`);
1564+
});
1565+
console.log(
1566+
` ℹ️ Valid components: ${componentRegistryValidation.validComponents.join(", ")}`,
1567+
);
1568+
validationErrors.push(
1569+
"Visual component validation failed - slides reference non-existent components",
1570+
);
1571+
} else if (componentRegistryValidation.totalVisualSlides > 0) {
1572+
console.log(
1573+
` ✅ All ${componentRegistryValidation.totalVisualSlides} visual slide(s) reference valid components`,
1574+
);
1575+
}
1576+
14951577
// Validate content array lengths (3-5 items)
14961578
// CRITICAL: This validation is intentionally strict and throws an error because
14971579
// slides with too many bullets become unreadable and overflow the layout.

0 commit comments

Comments
 (0)