@thanh01.pmt/presentation-kit 0.2.11 → 0.2.13

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.
package/dist/ai/index.cjs CHANGED
@@ -412,7 +412,326 @@ When a precise coordinate layout is specified, include the clean YAML block at t
412
412
  pos: [670, 150, 530, 510]
413
413
  -->
414
414
  \`\`\`
415
+
416
+ ---
417
+
418
+ ## 7. STEP REVEAL & PROGRESSIVE DISCLOSURE
419
+ When presenting lists, comparisons, or multi-phase concepts, support sequential reveals:
420
+ - Annotate lists or elements with \`<!-- v-click -->\` or apply class \`.step-reveal\` / \`[data-step]\`.
421
+ - Clicking or pressing forward arrow reveals items sequentially before transitioning to the next slide.
422
+ - Keep list items concise (< 15 words) to maintain visual rhythm during progressive steps.
415
423
  `;
424
+ var validateSlideDeckSchema = zod.z.object({
425
+ format: zod.z.enum(["auto", "html", "markdown"]).default("auto").describe('Deck format to validate: "html", "markdown", or "auto"'),
426
+ markdown: zod.z.string().optional().describe("Raw Markdown / Marp slide presentation content"),
427
+ htmlDeck: zod.z.object({
428
+ title: zod.z.string().optional(),
429
+ subtitle: zod.z.string().optional(),
430
+ theme: zod.z.string().optional(),
431
+ slides: zod.z.array(zod.z.object({
432
+ layoutId: zod.z.string(),
433
+ slots: zod.z.record(zod.z.any()),
434
+ notes: zod.z.string().optional()
435
+ })).optional()
436
+ }).optional().describe("Structured HTML deck data matching HtmlDeckData"),
437
+ strict: zod.z.boolean().default(false).describe("If true, warnings cause valid to be false")
438
+ });
439
+ function countWords(str) {
440
+ if (!str) return 0;
441
+ return str.trim().split(/\s+/).filter(Boolean).length;
442
+ }
443
+ function validateHtmlSlideDeck(deck, strict = false) {
444
+ const issues = [];
445
+ const suggestions = [];
446
+ const slides = deck.slides || [];
447
+ const slideCount = slides.length;
448
+ if (slideCount === 0) {
449
+ return {
450
+ success: false,
451
+ valid: false,
452
+ score: 0,
453
+ format: "html",
454
+ slideCount: 0,
455
+ notesCoveragePercent: 0,
456
+ issues: [{
457
+ slideIndex: 0,
458
+ severity: "error",
459
+ rule: "EMPTY_DECK",
460
+ message: "Presentation deck contains no slides."
461
+ }],
462
+ suggestions: ["Add at least one slide (e.g. hero-cover) to the deck."]
463
+ };
464
+ }
465
+ let notesCount = 0;
466
+ let hasCover = false;
467
+ let hasQuiz = false;
468
+ let hasSummary = false;
469
+ slides.forEach((slide, idx) => {
470
+ const slideNumber = idx + 1;
471
+ const layoutId = slide.layoutId;
472
+ const preset = chunk53GLUCYC_cjs.HTML_SLIDE_PRESETS[layoutId];
473
+ if (!preset) {
474
+ issues.push({
475
+ slideIndex: slideNumber,
476
+ severity: "error",
477
+ rule: "INVALID_LAYOUT_PRESET",
478
+ message: `Unknown layoutId "${slide.layoutId}". Available presets: ${Object.keys(chunk53GLUCYC_cjs.HTML_SLIDE_PRESETS).join(", ")}`
479
+ });
480
+ return;
481
+ }
482
+ if (preset.category === "cover" && idx === 0) hasCover = true;
483
+ if (preset.id === "checkpoint-quiz") hasQuiz = true;
484
+ if (preset.id === "summary-takeaways") hasSummary = true;
485
+ for (const slotDef of preset.slots) {
486
+ if (slotDef.required) {
487
+ const val = slide.slots?.[slotDef.name];
488
+ if (val === void 0 || val === null || typeof val === "string" && val.trim() === "") {
489
+ issues.push({
490
+ slideIndex: slideNumber,
491
+ severity: "error",
492
+ rule: "MISSING_REQUIRED_SLOT",
493
+ message: `Slide ${slideNumber} (${layoutId}) is missing required slot "${slotDef.name}" (${slotDef.label}).`
494
+ });
495
+ }
496
+ }
497
+ }
498
+ const slots = slide.slots || {};
499
+ for (const [key, val] of Object.entries(slots)) {
500
+ if (typeof val === "string") {
501
+ const words = countWords(val);
502
+ if (key !== "code" && words > 50) {
503
+ issues.push({
504
+ slideIndex: slideNumber,
505
+ severity: "warning",
506
+ rule: "ZERO_SCROLL_WORD_BUDGET",
507
+ message: `Slot "${key}" on slide ${slideNumber} has ${words} words. Recommended max is 35-40 words to prevent vertical scrolling.`
508
+ });
509
+ }
510
+ } else if (Array.isArray(val)) {
511
+ if (val.length > 5) {
512
+ issues.push({
513
+ slideIndex: slideNumber,
514
+ severity: "warning",
515
+ rule: "EXCESSIVE_LIST_ITEMS",
516
+ message: `List slot "${key}" on slide ${slideNumber} has ${val.length} items. Keep to 3-4 items for optimal readability.`
517
+ });
518
+ }
519
+ val.forEach((item, itemIdx) => {
520
+ if (typeof item === "string") {
521
+ const w = countWords(item);
522
+ if (w > 20) {
523
+ issues.push({
524
+ slideIndex: slideNumber,
525
+ severity: "warning",
526
+ rule: "BULLET_WORD_BUDGET",
527
+ message: `Bullet #${itemIdx + 1} in "${key}" on slide ${slideNumber} is ${w} words. Keep bullet points concise (< 15 words).`
528
+ });
529
+ }
530
+ }
531
+ });
532
+ }
533
+ }
534
+ const rawNotes = Array.isArray(slide.notes) ? slide.notes.join("\n") : typeof slide.notes === "string" ? slide.notes : "";
535
+ if (rawNotes && rawNotes.trim().length > 0) {
536
+ notesCount++;
537
+ const notes = rawNotes.trim();
538
+ const hasQuestion = notes.includes("?") || /cold-?call|question/i.test(notes);
539
+ const hasTip = /scaffold|tip|analogy|hint/i.test(notes);
540
+ if (!hasQuestion || !hasTip) {
541
+ issues.push({
542
+ slideIndex: slideNumber,
543
+ severity: "warning",
544
+ rule: "INCOMPLETE_PRESENTER_NOTES",
545
+ message: `Presenter notes for slide ${slideNumber} should follow the 3-part contract: Talk Track, Cold-Call Question, and Scaffolding Tip.`
546
+ });
547
+ }
548
+ } else {
549
+ issues.push({
550
+ slideIndex: slideNumber,
551
+ severity: "error",
552
+ rule: "MISSING_PRESENTER_NOTES",
553
+ message: `Slide ${slideNumber} is missing presenter notes. 100% presenter notes coverage is required.`
554
+ });
555
+ }
556
+ });
557
+ if (slideCount >= 3 && !hasCover) {
558
+ issues.push({
559
+ slideIndex: 1,
560
+ severity: "warning",
561
+ rule: "MISSING_HERO_COVER",
562
+ message: 'Slide 1 should ideally use the "hero-cover" layout preset to introduce the topic and objectives.'
563
+ });
564
+ }
565
+ if (slideCount >= 6 && !hasQuiz) {
566
+ issues.push({
567
+ slideIndex: Math.floor(slideCount / 2),
568
+ severity: "warning",
569
+ rule: "MISSING_FORMATIVE_CHECKPOINT",
570
+ message: `Deck has ${slideCount} slides but lacks a "checkpoint-quiz" for mid-lesson active recall.`
571
+ });
572
+ }
573
+ if (slideCount >= 4 && !hasSummary) {
574
+ issues.push({
575
+ slideIndex: slideCount,
576
+ severity: "warning",
577
+ rule: "MISSING_SUMMARY_SLIDE",
578
+ message: 'Deck lacks a "summary-takeaways" wrap-up slide at the conclusion of the presentation.'
579
+ });
580
+ }
581
+ const notesCoveragePercent = Math.round(notesCount / slideCount * 100);
582
+ const errorCount = issues.filter((i) => i.severity === "error").length;
583
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
584
+ let score = 100 - errorCount * 15 - warningCount * 4;
585
+ score = Math.max(0, Math.min(100, score));
586
+ const valid = strict ? issues.length === 0 : errorCount === 0;
587
+ if (errorCount > 0) {
588
+ suggestions.push(`Resolve ${errorCount} blocking error(s) before presenting or publishing.`);
589
+ }
590
+ if (notesCoveragePercent < 100) {
591
+ suggestions.push(`Increase presenter notes coverage from ${notesCoveragePercent}% to 100%.`);
592
+ }
593
+ if (!hasQuiz && slideCount >= 6) {
594
+ suggestions.push("Add an interactive checkpoint quiz around the midpoint of the lesson.");
595
+ }
596
+ return {
597
+ success: true,
598
+ valid,
599
+ score,
600
+ format: "html",
601
+ slideCount,
602
+ notesCoveragePercent,
603
+ issues,
604
+ suggestions
605
+ };
606
+ }
607
+ function validateMarkdownSlideDeck(markdown, strict = false) {
608
+ const issues = [];
609
+ const suggestions = [];
610
+ if (!markdown || markdown.trim().length === 0) {
611
+ return {
612
+ success: false,
613
+ valid: false,
614
+ score: 0,
615
+ format: "markdown",
616
+ slideCount: 0,
617
+ notesCoveragePercent: 0,
618
+ issues: [{
619
+ slideIndex: 0,
620
+ severity: "error",
621
+ rule: "EMPTY_MARKDOWN",
622
+ message: "Markdown slide presentation is empty."
623
+ }],
624
+ suggestions: ['Provide Markdown slide content delimited by "---".']
625
+ };
626
+ }
627
+ const { slides: rawSlides } = chunk53GLUCYC_cjs.splitMarkdownSlides(markdown);
628
+ const activeSlides = rawSlides.filter((s) => s.trim().length > 0);
629
+ const slideCount = activeSlides.length;
630
+ if (slideCount === 0) {
631
+ return {
632
+ success: false,
633
+ valid: false,
634
+ score: 0,
635
+ format: "markdown",
636
+ slideCount: 0,
637
+ notesCoveragePercent: 0,
638
+ issues: [{
639
+ slideIndex: 0,
640
+ severity: "error",
641
+ rule: "EMPTY_MARKDOWN",
642
+ message: "No slide content detected after stripping frontmatter."
643
+ }],
644
+ suggestions: ['Add slide content separated by "---".']
645
+ };
646
+ }
647
+ let notesCount = 0;
648
+ activeSlides.forEach((slideContent, idx) => {
649
+ const slideNumber = idx + 1;
650
+ const trimmed = slideContent.trim();
651
+ const notesMatch = trimmed.match(/<!--\s*(?:Presenter Notes:?[\s\S]*?|[\s\S]*?notes[\s\S]*?)-->/i);
652
+ if (notesMatch) {
653
+ notesCount++;
654
+ const noteBody = notesMatch[0];
655
+ const hasQuestion = noteBody.includes("?") || /cold-?call|question/i.test(noteBody);
656
+ const hasTip = /scaffold|tip|analogy|hint/i.test(noteBody);
657
+ if (!hasQuestion || !hasTip) {
658
+ issues.push({
659
+ slideIndex: slideNumber,
660
+ severity: "warning",
661
+ rule: "INCOMPLETE_PRESENTER_NOTES",
662
+ message: `Presenter notes on slide ${slideNumber} should include Talk Track, a Cold-Call Question, and a Scaffolding Tip.`
663
+ });
664
+ }
665
+ } else {
666
+ issues.push({
667
+ slideIndex: slideNumber,
668
+ severity: "error",
669
+ rule: "MISSING_PRESENTER_NOTES",
670
+ message: `Slide ${slideNumber} is missing presenter notes comments (<!-- Presenter Notes: ... -->).`
671
+ });
672
+ }
673
+ const codeBlockTokens = trimmed.match(/```/g);
674
+ if (codeBlockTokens && codeBlockTokens.length % 2 !== 0) {
675
+ issues.push({
676
+ slideIndex: slideNumber,
677
+ severity: "error",
678
+ rule: "UNCLOSED_CODE_BLOCK",
679
+ message: `Slide ${slideNumber} contains unclosed code fence backticks (\`\`\`).`
680
+ });
681
+ }
682
+ const cleanBody = trimmed.replace(/<!--[\s\S]*?-->/g, "").replace(/```[\s\S]*?```/g, "");
683
+ const words = countWords(cleanBody);
684
+ if (words > 100) {
685
+ issues.push({
686
+ slideIndex: slideNumber,
687
+ severity: "warning",
688
+ rule: "ZERO_SCROLL_WORD_BUDGET",
689
+ message: `Slide ${slideNumber} body has ${words} words. Keep within 40-70 words per slide to prevent vertical scroll.`
690
+ });
691
+ }
692
+ });
693
+ const notesCoveragePercent = Math.round(notesCount / slideCount * 100);
694
+ const errorCount = issues.filter((i) => i.severity === "error").length;
695
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
696
+ let score = 100 - errorCount * 15 - warningCount * 4;
697
+ score = Math.max(0, Math.min(100, score));
698
+ const valid = strict ? issues.length === 0 : errorCount === 0;
699
+ return {
700
+ success: true,
701
+ valid,
702
+ score,
703
+ format: "markdown",
704
+ slideCount,
705
+ notesCoveragePercent,
706
+ issues,
707
+ suggestions
708
+ };
709
+ }
710
+ function executeValidateSlideDeck(input) {
711
+ const format = input.format || "auto";
712
+ if (format === "html" || format === "auto" && input.htmlDeck && input.htmlDeck.slides) {
713
+ return validateHtmlSlideDeck(input.htmlDeck || { slides: [] }, input.strict);
714
+ }
715
+ if (format === "markdown" || format === "auto" && input.markdown) {
716
+ return validateMarkdownSlideDeck(input.markdown || "", input.strict);
717
+ }
718
+ return {
719
+ success: false,
720
+ valid: false,
721
+ score: 0,
722
+ format: "html",
723
+ slideCount: 0,
724
+ notesCoveragePercent: 0,
725
+ issues: [{
726
+ slideIndex: 0,
727
+ severity: "error",
728
+ rule: "INVALID_INPUT",
729
+ message: "Neither htmlDeck nor markdown was supplied for slide deck validation."
730
+ }],
731
+ suggestions: ['Provide either "htmlDeck" object or "markdown" string in the validation parameters.']
732
+ };
733
+ }
734
+ var handleValidateSlideDeck = executeValidateSlideDeck;
416
735
  var listHtmlSlidePresetsSchema = zod.z.object({
417
736
  category: zod.z.enum(["all", "cover", "content", "split", "grid", "timeline", "callout", "assessment"]).optional().describe("Optional category filter for HTML layout presets")
418
737
  });
@@ -521,8 +840,8 @@ Instead of generating free-form HTML or brittle Markdown hacks, you MUST fill st
521
840
  - \`takeaways\` (string[]): 3 to 4 memorable bullet points
522
841
  - \`nextStep\` (string): Teaser for the next lesson or project
523
842
 
524
- ## Aesthetic Rules ("Anti-AI-Slop"):
525
- - **Brevity is King:** Each slide fits a 1920\xD71080 canvas. Keep sentences punchy. Never write dense paragraphs that overflow.
843
+ ## Aesthetic & Quality Invariants ("Anti-AI-Slop"):
844
+ - **Brevity is King (Zero-Scroll Invariant):** Each slide is calibrated for a 1920\xD71080 canvas. Maximum **35\u201340 words** per card or column; maximum **15 words** per bullet item. Never write dense multi-sentence paragraphs that cause vertical scrolling.
526
845
  - **Pedagogical Pacing:** Start with \`hero-cover\`, alternate between \`split-concept-code\`, \`two-columns-compare\` and \`three-cards-grid\`, insert a \`checkpoint-quiz\` midway, and finish with \`summary-takeaways\`.
527
846
  - **Theme Selection:**
528
847
  - \`blue-professional\`: General academic, clean engineering, trustworthy.
@@ -530,6 +849,24 @@ Instead of generating free-form HTML or brittle Markdown hacks, you MUST fill st
530
849
  - \`editorial-forest\`: Thoughtful design, literature, ethics, science.
531
850
  - \`studio\`: Minimalist product design, modern software.
532
851
  - \`monochrome\`: Pure high-contrast dark mode.
852
+
853
+ ## \u{1FA84} Step Reveal & Progressive Disclosure:
854
+ - The presentation shell supports sequential step reveal via mouse click or Space/Arrow keys.
855
+ - Bullet lists (\`points\`, \`col1Items\`, \`col2Items\`), cards in \`three-cards-grid\`, and steps in \`timeline-steps\` reveal progressively one by one before navigating to the next slide.
856
+ - In custom HTML slots, you can attach \`class="step-reveal"\`, \`data-step\`, \`data-reveal\`, or \`data-auto-reveal="true"\` on parent containers to enable click-by-click stepped appearance.
857
+
858
+ ## \u{1F399}\uFE0F Mandatory 3-Part Presenter Notes Contract:
859
+ Every single slide MUST include the \`notes\` property structured with these 3 explicit components:
860
+ 1. **Talk Track:** 2-3 sentences of exact conversational speaking script for the teacher/presenter.
861
+ 2. **Cold-Call Check Question:** 1 diagnostic question to check understanding with a named student or attendee.
862
+ 3. **Scaffolding Tip:** 1 intuitive analogy or troubleshooting hint in case learners struggle with the concept.
863
+
864
+ Example Notes:
865
+ \`\`\`markdown
866
+ Talk Track: Here we contrast synchronous blocking I/O with asynchronous event-driven handling. Notice how threads remain idle while waiting for network responses.
867
+ Cold-Call: "Jordan, looking at the left column, what happens to thread pool throughput when network latency quadruples?"
868
+ Scaffolding Tip: Compare it to ordering coffee at a counter (async buzzer) versus waiting at the register until the cup is brewed (sync blocking).
869
+ \`\`\`
533
870
  `;
534
871
 
535
872
  // src/ai/html/tools.ts
@@ -588,6 +925,9 @@ function handleCreateHtmlSlide(input) {
588
925
  }
589
926
  };
590
927
  }
928
+ var executeListHtmlSlidePresets = handleListHtmlSlidePresets;
929
+ var executeGenerateHtmlDeck = handleGenerateHtmlDeck;
930
+ var executeCreateHtmlSlide = handleCreateHtmlSlide;
591
931
 
592
932
  exports.SLIDE_DESIGN_PROMPT_CONTRACT = SLIDE_DESIGN_PROMPT_CONTRACT;
593
933
  exports.SLIDE_HTML_PROMPT_CONTRACT = SLIDE_HTML_PROMPT_CONTRACT;
@@ -595,18 +935,26 @@ exports.applySlideLayoutSchema = applySlideLayoutSchema;
595
935
  exports.createHtmlSlideSchema = createHtmlSlideSchema;
596
936
  exports.createSlideFromPresetSchema = createSlideFromPresetSchema;
597
937
  exports.executeApplySlideLayout = executeApplySlideLayout;
938
+ exports.executeCreateHtmlSlide = executeCreateHtmlSlide;
598
939
  exports.executeCreateSlideFromPreset = executeCreateSlideFromPreset;
599
940
  exports.executeGenerateDeckOutline = executeGenerateDeckOutline;
941
+ exports.executeGenerateHtmlDeck = executeGenerateHtmlDeck;
942
+ exports.executeListHtmlSlidePresets = executeListHtmlSlidePresets;
600
943
  exports.executeListSlidePresets = executeListSlidePresets;
601
944
  exports.executeUpdatePresenterNotes = executeUpdatePresenterNotes;
945
+ exports.executeValidateSlideDeck = executeValidateSlideDeck;
602
946
  exports.generateDeckOutlineSchema = generateDeckOutlineSchema;
603
947
  exports.generateHtmlDeckSchema = generateHtmlDeckSchema;
604
948
  exports.handleCreateHtmlSlide = handleCreateHtmlSlide;
605
949
  exports.handleGenerateHtmlDeck = handleGenerateHtmlDeck;
606
950
  exports.handleListHtmlSlidePresets = handleListHtmlSlidePresets;
951
+ exports.handleValidateSlideDeck = handleValidateSlideDeck;
607
952
  exports.htmlSlideSlotDataSchema = htmlSlideSlotDataSchema;
608
953
  exports.listHtmlSlidePresetsSchema = listHtmlSlidePresetsSchema;
609
954
  exports.listSlidePresetsSchema = listSlidePresetsSchema;
610
955
  exports.updatePresenterNotesSchema = updatePresenterNotesSchema;
956
+ exports.validateHtmlSlideDeck = validateHtmlSlideDeck;
957
+ exports.validateMarkdownSlideDeck = validateMarkdownSlideDeck;
958
+ exports.validateSlideDeckSchema = validateSlideDeckSchema;
611
959
  //# sourceMappingURL=index.cjs.map
612
960
  //# sourceMappingURL=index.cjs.map