Skip to content

Commit 260af2e

Browse files
ofriwclaude
andcommitted
Integrate automatic line-breaking into presentation generation
Changes: - Apply line-breaking post-processing after Claude generation - Update prompt to rely on post-processing instead of manual breaks - Add code source validation to prevent fabricated examples - Change CSS from pre to pre-wrap for better wrapping behavior - Log statistics on lines fixed during generation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent fccd8cd commit 260af2e

File tree

2 files changed

+214
-35
lines changed

2 files changed

+214
-35
lines changed

scripts/generate-presentation.js

Lines changed: 213 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { fileURLToPath } from 'url';
2727
import { spawn } from 'child_process';
2828
import * as readline from 'readline';
2929
import { parseMarkdownContent } from './lib/markdown-parser.js';
30+
import { processPresentation } from './lib/line-breaker.js';
3031

3132
// ES module __dirname equivalent
3233
const __filename = fileURLToPath(import.meta.url);
@@ -146,49 +147,56 @@ For presentation slides:
146147
147148
CODE FORMATTING FOR PRESENTATIONS:
148149
149-
CRITICAL: Add strategic line breaks to prevent horizontal clipping on slides.
150+
✓ Include natural line breaks in code and text (use \\n for newlines in JSON strings)
151+
✓ Use standard formatting - post-processing will optimize line lengths automatically
152+
✓ Preserve semantic meaning and don't break mid-identifier or mid-word
153+
✓ DO NOT output literal newlines in JSON strings - always use the \\n escape sequence
150154
151-
For ALL code examples and prompt text in slides:
152-
✓ Keep lines under 60 characters for readability in presentation view
153-
✓ Break at logical points:
154-
- After commas in parameter lists
155-
- After operators (&&, ||, =, etc.)
156-
- After colons in object literals
157-
- Between method chains (one per line)
158-
- After opening statements before conditions
159-
✓ Maintain proper indentation for wrapped lines (2-4 spaces)
160-
✓ Preserve semantic meaning - don't break mid-identifier or mid-word
161-
✓ Use \\n escape sequence for line breaks in JSON strings (NOT literal newlines)
155+
NOTE: Line length optimization (60-char limit) is handled automatically by post-processing.
156+
Focus on preserving the exact content and structure from the source material.
162157
163-
IMPORTANT: Since you're generating JSON output, use \\n for line breaks.
158+
<CRITICAL_CONSTRAINT>
159+
SOURCE MATERIAL VERIFICATION
164160
165-
When you write the JSON output, use \\n in string literals (escaped backslash-n).
166-
This will be parsed as a newline character (\n) when the JSON is read.
161+
Before generating any slide containing code:
162+
1. Read the source material carefully to locate the code
163+
2. Verify all code examples exist verbatim in the source markdown
164+
3. Copy code exactly as it appears - character for character
167165
168-
Examples showing what to OUTPUT in your generated JSON:
166+
ABSOLUTE PROHIBITIONS:
167+
✗ DO NOT generate hypothetical code examples that aren't in the source
168+
✗ DO NOT fabricate implementations that "would result" from prompts shown in the lesson
169+
✗ DO NOT create code to demonstrate "what the AI would produce"
170+
✗ DO NOT show resulting code unless it explicitly exists in the source markdown
169171
170-
BEFORE (too long):
171-
"Write a TypeScript function that validates email addresses per RFC 5322"
172+
CORRECT BEHAVIOR:
173+
✓ If the lesson shows ONLY a prompt example (text describing what to ask AI):
174+
→ Show the prompt as-is using code/codeComparison type with language: "text"
175+
→ DO NOT generate the code that would result from that prompt
176+
→ The prompt itself IS the educational example
172177
173-
AFTER (with \\n breaks in the output):
174-
"Write a TypeScript function that validates\\nemail addresses per RFC 5322"
178+
✓ If the lesson shows BOTH a prompt AND its resulting code:
179+
→ Show both exactly as they appear in the source
180+
→ Verify both exist in the source before including
175181
176-
BEFORE (code too long):
177-
"const result = validateEmail(userInput) && checkDomain(domain) && verifyMX(server);"
182+
✓ If the lesson shows code WITHOUT a preceding prompt:
183+
→ Show the code exactly as it appears in the source
184+
→ Verify it exists before including
178185
179-
AFTER (with \\n breaks in the output):
180-
"const result = validateEmail(userInput) &&\\n checkDomain(domain) &&\\n verifyMX(server);"
186+
EXAMPLES:
181187
182-
Apply strategic line breaks using \\n to:
183-
- Prompt examples in codeComparison slides (both leftCode and rightCode)
184-
- Code snippets in code slides
185-
- Command examples
186-
- Text content in comparison slides if lines exceed 60 characters
188+
❌ WRONG - Fabricating implementation:
189+
Source contains: "Write a TypeScript function that validates email addresses per RFC 5322..."
190+
Slide shows: Complete validateEmail() function with regex and edge case handling
191+
Problem: The implementation was FABRICATED - it doesn't exist in the source
187192
188-
TECHNICAL NOTE: When you output \\n in a JSON string, it represents an escaped newline.
189-
After JSON parsing, it becomes a single \n (newline character in the string value).
190-
DO NOT output literal newlines in JSON strings - always use the \\n escape sequence.
191-
This prevents horizontal scrolling and ensures all content is visible without clipping.
193+
✅ CORRECT - Showing only what exists:
194+
Source contains: "Write a TypeScript function that validates email addresses per RFC 5322..."
195+
Slide shows: The prompt text only (type: "code", language: "text")
196+
Result: Authentic prompt example preserved, no fabrication
197+
198+
Remember: You are extracting and organizing existing content, NOT generating new examples.
199+
</CRITICAL_CONSTRAINT>
192200
193201
<MANDATORY_RULES>
194202
CRITICAL: PRESERVING PROMPT EXAMPLES
@@ -262,6 +270,35 @@ COMMON MISTAKE - DO NOT DO THIS:
262270
263271
If you see prompt examples in the source (text showing what to write to an AI), you MUST use "code" or "codeComparison" slide types. NEVER use "concept" with bullet points for prompts.
264272
273+
<VERIFICATION_PROTOCOL>
274+
CHAIN-OF-THOUGHT FOR CODE SLIDES
275+
276+
For each code example you consider including in a slide, follow this verification process:
277+
278+
Step 1: LOCATE - Where does this code appear in the source material?
279+
→ Identify the section and approximate line range
280+
→ If you cannot find it, STOP - do not include this code
281+
282+
Step 2: EXTRACT - Copy the exact text from the source
283+
→ Character-by-character match
284+
→ Preserve all whitespace, formatting, and syntax
285+
286+
Step 3: VERIFY - Does your extracted code match what you're about to include?
287+
→ Compare character by character
288+
→ If there's any mismatch, re-extract from source
289+
290+
Step 4: CONFIRM - Is this code explicitly in the source, or did you generate it?
291+
→ If you generated it (even to "demonstrate" something), DELETE IT
292+
→ Only include code that passed Steps 1-3
293+
294+
Apply this process to:
295+
- Every "code" slide
296+
- Every "codeComparison" leftCode and rightCode
297+
- Every "codeExecution" step that contains code
298+
299+
This verification prevents fabrication and ensures educational integrity.
300+
</VERIFICATION_PROTOCOL>
301+
265302
COMPONENT DETECTION (CRITICAL):
266303
267304
The source content contains markers for visual React components in the format:
@@ -376,11 +413,21 @@ SPEAKER NOTES GUIDELINES:
376413
377414
For each slide, provide speaker notes with:
378415
1. **Talking points**: What to say (2-4 sentences)
416+
✓ Explain what IS shown on the slide
417+
✗ Do NOT describe "what the model would generate" for prompts
418+
✗ Do NOT fabricate hypothetical outcomes or implementations
379419
2. **Timing**: Estimated time to spend (e.g., "2 minutes")
380420
3. **Discussion prompts**: Questions to engage students
381421
4. **Real-world context**: Production scenarios to reference
382422
5. **Transition**: How to move to next slide
383423
424+
CRITICAL CONSTRAINTS FOR SPEAKER NOTES:
425+
✗ NEVER say "Notice what the model generated" when showing prompt examples alone
426+
✗ NEVER describe hypothetical code that would result from a prompt (unless that code exists in the source)
427+
✗ NEVER fabricate examples or scenarios not present in the source material
428+
✓ Focus on explaining the content that IS on the slide
429+
✓ Reference only examples and code that exist in the source
430+
384431
Example speaker notes:
385432
\`\`\`
386433
Talking points: This slide shows the difference between vague and specific prompts. The vague version gives no context, forcing the AI to guess. The specific version provides language, standard, edge cases, and return type.
@@ -674,7 +721,22 @@ async function generatePresentationWithClaude(prompt, outputPath) {
674721
}
675722

676723
console.log(` ✅ Valid presentation JSON (${presentation.slides.length} slides)`);
677-
resolve(presentation);
724+
725+
// Apply deterministic line breaking
726+
console.log(' 🔧 Applying line breaking...');
727+
const { presentation: processedPresentation, stats } = processPresentation(presentation);
728+
729+
// Log statistics
730+
if (stats.linesShortened > 0) {
731+
console.log(` ✂️ Fixed ${stats.linesShortened} long lines (max reduction: ${stats.maxReduction} chars)`);
732+
} else {
733+
console.log(' ✅ No lines exceeded 60 characters');
734+
}
735+
736+
// Write back the processed presentation
737+
writeFileSync(outputPath, JSON.stringify(processedPresentation, null, 2), 'utf-8');
738+
739+
resolve(processedPresentation);
678740
} catch (parseError) {
679741
reject(new Error(`Failed to parse JSON: ${parseError.message}\nContent preview: ${fileContent?.slice(0, 200)}`));
680742
return;
@@ -1009,6 +1071,106 @@ function validatePromptExamples(content, presentation) {
10091071
};
10101072
}
10111073

1074+
/**
1075+
* Normalize whitespace for comparison (remove extra spaces, normalize line breaks)
1076+
*/
1077+
function normalizeWhitespace(str) {
1078+
return str
1079+
.replace(/\r\n/g, '\n') // Normalize line endings
1080+
.replace(/\s+/g, ' ') // Collapse multiple spaces
1081+
.trim();
1082+
}
1083+
1084+
/**
1085+
* Check if a code block is a prompt example (text showing what to write to AI)
1086+
*/
1087+
function isPromptExample(slide) {
1088+
// If language is "text" or "markdown" and contains prompt verbs, it's likely a prompt example
1089+
const promptVerbs = ['Write a', 'You are', 'Calculate', 'Review', 'Debug', 'Add ', 'Create', 'Implement', 'Refactor'];
1090+
const slideCode = slide.code || '';
1091+
const isTextLang = slide.language === 'text' || slide.language === 'markdown';
1092+
1093+
return isTextLang && promptVerbs.some(verb => slideCode.includes(verb));
1094+
}
1095+
1096+
/**
1097+
* Validate that code examples in slides exist in source material
1098+
* This prevents fabrication of hypothetical implementations
1099+
* @param {string} content - Parsed markdown content
1100+
* @param {object} presentation - Generated presentation object
1101+
* @returns {object} Validation result with issues
1102+
*/
1103+
function validateCodeExamplesExistInSource(content, presentation) {
1104+
const issues = [];
1105+
1106+
// Extract all code blocks from source markdown
1107+
const codeBlockRegex = /```[\s\S]*?```/g;
1108+
const sourceCodeBlocks = content.match(codeBlockRegex) || [];
1109+
1110+
// Normalize source code blocks for comparison
1111+
const normalizedSourceBlocks = sourceCodeBlocks.map(block => {
1112+
// Remove the backticks and language specifier
1113+
const codeContent = block.replace(/^```[^\n]*\n/, '').replace(/\n```$/, '');
1114+
return normalizeWhitespace(codeContent);
1115+
});
1116+
1117+
// Check each code slide
1118+
const codeSlides = presentation.slides.filter(s =>
1119+
s.type === 'code' || s.type === 'codeComparison'
1120+
);
1121+
1122+
for (const slide of codeSlides) {
1123+
// For code slides with actual code (not prompt examples)
1124+
if (slide.type === 'code' && slide.code) {
1125+
// Skip prompt examples (text-based prompts are valid educational content)
1126+
if (isPromptExample(slide)) {
1127+
continue;
1128+
}
1129+
1130+
const codeContent = normalizeWhitespace(slide.code);
1131+
1132+
// Check if this code exists in any source block (allow partial matches for excerpts)
1133+
const existsInSource = normalizedSourceBlocks.some(sourceBlock =>
1134+
sourceBlock.includes(codeContent) || codeContent.includes(sourceBlock)
1135+
);
1136+
1137+
if (!existsInSource && codeContent.length > 20) { // Ignore very short snippets
1138+
issues.push(`Code in slide "${slide.title}" (${codeContent.substring(0, 50)}...) not found in source`);
1139+
}
1140+
}
1141+
1142+
// For code comparison slides
1143+
if (slide.type === 'codeComparison') {
1144+
const checkCodeBlock = (codeBlock, label) => {
1145+
if (!codeBlock || !codeBlock.code) return;
1146+
1147+
// Skip prompt examples
1148+
if (codeBlock.language === 'text' || codeBlock.language === 'markdown') {
1149+
return;
1150+
}
1151+
1152+
const codeContent = normalizeWhitespace(codeBlock.code);
1153+
const existsInSource = normalizedSourceBlocks.some(sourceBlock =>
1154+
sourceBlock.includes(codeContent) || codeContent.includes(sourceBlock)
1155+
);
1156+
1157+
if (!existsInSource && codeContent.length > 20) {
1158+
issues.push(`${label} code in slide "${slide.title}" (${codeContent.substring(0, 50)}...) not found in source`);
1159+
}
1160+
};
1161+
1162+
checkCodeBlock(slide.leftCode, 'Left');
1163+
checkCodeBlock(slide.rightCode, 'Right');
1164+
}
1165+
}
1166+
1167+
return {
1168+
valid: issues.length === 0,
1169+
issues,
1170+
codeSlidesChecked: codeSlides.length
1171+
};
1172+
}
1173+
10121174
/**
10131175
* Generate presentation for a file
10141176
*/
@@ -1118,6 +1280,23 @@ async function generatePresentation(filePath, manifest, config) {
11181280
console.log(` ✅ All prompt examples preserved as code blocks (${promptValidation.codeSlideCount} code slide(s))`);
11191281
}
11201282

1283+
// Validate code examples exist in source material
1284+
// CRITICAL: This validation prevents fabrication of hypothetical code implementations
1285+
// All code shown in slides must exist verbatim in the source lesson markdown
1286+
const codeSourceValidation = validateCodeExamplesExistInSource(content, presentation);
1287+
if (!codeSourceValidation.valid) {
1288+
console.log(` ❌ BUILD FAILURE: ${codeSourceValidation.issues.length} fabricated code issue(s):`);
1289+
codeSourceValidation.issues.forEach(issue => {
1290+
console.log(` - ${issue}`);
1291+
});
1292+
console.log(` ℹ️ All code in slides MUST exist verbatim in the source markdown`);
1293+
console.log(` ℹ️ DO NOT generate hypothetical implementations to demonstrate prompts`);
1294+
console.log(` ℹ️ The presentation was not saved. Fix the generation and try again.`);
1295+
throw new Error('Code source validation failed - slides contain fabricated code not in source');
1296+
} else if (codeSourceValidation.codeSlidesChecked > 0) {
1297+
console.log(` ✅ All ${codeSourceValidation.codeSlidesChecked} code slide(s) verified against source`);
1298+
}
1299+
11211300
// Copy to static directory for deployment
11221301
const staticPath = join(STATIC_OUTPUT_DIR, dirname(relativePath), outputFileName);
11231302
mkdirSync(dirname(staticPath), { recursive: true });

website/src/components/PresentationMode/RevealSlideshow.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@
142142
overflow-y: auto;
143143
overflow-x: auto;
144144
max-height: 400px;
145-
white-space: pre;
145+
white-space: pre-wrap;
146146
}
147147

148148
.codeBlockSmall code {

0 commit comments

Comments
 (0)