slidemind 0.3.12 → 0.3.14

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.
Files changed (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/AGENTS.md +86 -21
  4. package/README.md +14 -17
  5. package/bin/slidemind.mjs +65 -4969
  6. package/package.json +1 -1
  7. package/skills/create-slides/SKILL.md +113 -74
  8. package/skills/search/SKILL.md +25 -7
  9. package/src/bridge/build-and-push.mjs +108 -15
  10. package/src/bridge/bundle.mjs +69 -0
  11. package/src/bridge/capture.mjs +71 -8
  12. package/src/bridge/client/brands.mjs +622 -0
  13. package/src/bridge/client/engine-routing.mjs +237 -0
  14. package/src/bridge/client/engine-search.mjs +310 -0
  15. package/src/bridge/client/engine-slides.mjs +435 -0
  16. package/src/bridge/client/pane-ops.mjs +335 -0
  17. package/src/bridge/client/presentations.mjs +587 -0
  18. package/src/bridge/client/render.mjs +317 -0
  19. package/src/bridge/client/transport.mjs +695 -0
  20. package/src/bridge/client.mjs +80 -3452
  21. package/src/bridge/config.mjs +1 -1
  22. package/src/bridge/engine-download.mjs +15 -2
  23. package/src/builders/build.mjs +16 -669
  24. package/src/builders/draft-check.mjs +376 -0
  25. package/src/builders/lint.mjs +38 -2
  26. package/src/builders/preview.html +799 -0
  27. package/src/builders/review.mjs +23 -4
  28. package/src/builders/visual-families.mjs +6 -8
  29. package/src/cli/common.mjs +179 -0
  30. package/src/cli/concepts.mjs +310 -0
  31. package/src/cli/connection.mjs +147 -0
  32. package/src/cli/deck.mjs +608 -0
  33. package/src/cli/designs.mjs +113 -0
  34. package/src/cli/diagnose.mjs +141 -0
  35. package/src/cli/download-bundle.mjs +144 -0
  36. package/src/cli/engine-helpers.mjs +429 -0
  37. package/src/cli/engine-results.mjs +320 -0
  38. package/src/cli/help.mjs +201 -0
  39. package/src/cli/install.mjs +257 -0
  40. package/src/cli/read.mjs +120 -0
  41. package/src/cli/ready.mjs +805 -0
  42. package/src/cli/search.mjs +608 -0
  43. package/src/cli/session.mjs +177 -0
  44. package/src/cli/setup.mjs +282 -0
  45. package/src/cli/slide-reuse.mjs +334 -0
  46. package/src/cli/status.mjs +118 -0
  47. package/src/cli/workspace.mjs +302 -0
  48. package/src/installers/codex.mjs +1 -1
  49. package/src/update-check.mjs +231 -0
@@ -3,8 +3,8 @@
3
3
  // Pure Node stdlib — no external dependencies.
4
4
  //
5
5
  // Usage:
6
- // slidemind build <deck>
7
- // slidemind build <deck> --open
6
+ // slidemind view-draft <deck> (the CLI entry point; it builds, then captures)
7
+ // node src/builders/build.mjs --deck <deck> [--open] [--fix]
8
8
  //
9
9
  // Convention:
10
10
  // <deck>/
@@ -34,7 +34,7 @@ import path from "node:path";
34
34
  import { fileURLToPath, pathToFileURL } from "node:url";
35
35
 
36
36
  import { resolveBundleDirForDeck } from "../bridge/bundle.mjs";
37
- import { loadChartPreview } from "../bridge/capture.mjs";
37
+ import { loadChartPreview, loadConnectorPreview } from "../bridge/capture.mjs";
38
38
  import { FAVICON_LINK } from "./gate-server.mjs";
39
39
  import { lintSlidesOrThrow } from "./lint.mjs";
40
40
  import { resolveSkillForDeck } from "./resolve-skill.mjs";
@@ -286,670 +286,13 @@ function collectSvgAssets(deckDir) {
286
286
  return map;
287
287
  }
288
288
 
289
- const HTML_TEMPLATE = `<!doctype html>
290
- <html lang="en">
291
- <head>
292
- <meta charset="utf-8" />
293
- <meta name="viewport" content="width=device-width, initial-scale=1" />
294
- <title>__TITLE__ — slidemind</title>
295
- ${FAVICON_LINK}
296
- <!-- Arimo: open-source Arial-metric-compatible font. We alias it to
297
- "Arial" so master text pinned to Arial gets a font with the same
298
- horizontal extents PowerPoint/LibreOffice use, instead of macOS
299
- Chrome's slightly wider system Arial that pushes long strings
300
- (addresses, single-line headers) past their bounding boxes. -->
301
- <link rel="preconnect" href="https://fonts.googleapis.com" />
302
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
303
- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400..700;1,400..700&display=swap" />
304
- <link rel="stylesheet" href="./corporate-design.css" />
305
- <link rel="stylesheet" href="./layouts.css" />
306
- <style>
307
- /* Compatibility aliases for decks/slides authored against the older
308
- statworx token names. New materialized bundles expose semantic tokens
309
- (--fg-*, --bg-*, --border-*); existing slide JSX may still refer
310
- to --sw-*. Keep those declarations valid in the HTML preview. */
311
- :root {
312
- --sw-black: var(--fg-1, #000000);
313
- --sw-white: var(--bg-1, #ffffff);
314
- --sw-ink: var(--fg-2, #283440);
315
- --sw-slate: var(--fg-3, #6C7D8C);
316
- --sw-gray: var(--fg-4, #B6BDCC);
317
- --sw-fog: var(--bg-2, #EBF0F2);
318
- --sw-ice-blue: var(--bg-3, #E6E6FF);
319
- --sw-tech-blue: var(--fg-accent, #0000FF);
320
- --sw-tech-blue-10: #E6E6FF;
321
- --sw-tech-blue-20: #CCCCFF;
322
- --sw-tech-blue-40: #9999FF;
323
- --sw-tech-blue-80: #3333FF;
324
- --sw-magenta: #E6007E;
325
- }
326
- </style>
327
- <style>
328
- /* Substitute Arial with Arimo (a metric clone) so wrap behaviour
329
- matches PowerPoint's rendering. Local Arial wins only if Arimo
330
- hasn't loaded yet. */
331
- @font-face {
332
- font-family: "Arial";
333
- src: local("Arimo"), local("Arial");
334
- font-weight: 400;
335
- font-style: normal;
336
- }
337
- @font-face {
338
- font-family: "Arial";
339
- src: local("Arimo Bold"), local("Arial Bold");
340
- font-weight: 700;
341
- font-style: normal;
342
- }
343
- @font-face {
344
- font-family: "Arial";
345
- src: local("Arimo Italic"), local("Arial Italic");
346
- font-weight: 400;
347
- font-style: italic;
348
- }
349
- @font-face {
350
- font-family: "Arial";
351
- src: local("Arimo Bold Italic"), local("Arial Bold Italic");
352
- font-weight: 700;
353
- font-style: italic;
354
- }
355
- </style>
356
- <style id="brand-font-faces">__BRAND_FONT_FACES__</style>
357
- <!-- Chart palette for the <Chart> stand-in (from the bundle's capture engine;
358
- empty when the bundle carries no chart runtime). -->
359
- <style id="chart-theme-tokens">__CHART_THEME_TOKENS__</style>
360
- <style>
361
- html, body { margin: 0; padding: 0; height: 100%; background: #0a0a0a; overflow: hidden; }
362
- :root {
363
- --deck-rail-w: 220px;
364
- --deck-chrome-h: 62px;
365
- --deck-chrome-bg: #111113;
366
- --deck-chrome-bg-2: #161618;
367
- --deck-chrome-border: #2b2b30;
368
- --deck-chrome-fg: #ededed;
369
- --deck-chrome-dim: #9a9aa0;
370
- }
371
- .deck {
372
- position: fixed;
373
- inset: 0 0 0 var(--deck-rail-w);
374
- display: flex;
375
- align-items: center;
376
- justify-content: center;
377
- }
378
- .deck__stage {
379
- width: 1280px;
380
- height: 720px;
381
- transform-origin: center center;
382
- background: var(--bg-1);
383
- box-shadow: 0 20px 60px rgba(0,0,0,0.35);
384
- }
385
- .deck__nav {
386
- position: fixed;
387
- bottom: 16px;
388
- right: 16px;
389
- padding: 6px 12px;
390
- border-radius: 999px;
391
- color: rgba(255,255,255,0.65);
392
- background: rgba(255,255,255,0.08);
393
- backdrop-filter: blur(6px);
394
- font-family: var(--font-sans, sans-serif);
395
- font-size: 12px;
396
- letter-spacing: 0.02em;
397
- user-select: none;
398
- }
399
- .deck__rail {
400
- position: fixed;
401
- top: 0;
402
- left: 0;
403
- bottom: 0;
404
- width: var(--deck-rail-w);
405
- z-index: 2147483590;
406
- display: flex;
407
- flex-direction: column;
408
- background: var(--deck-chrome-bg);
409
- border-right: 1px solid var(--deck-chrome-border);
410
- color: var(--deck-chrome-fg);
411
- font-family: var(--font-sans, sans-serif);
412
- }
413
- .deck__thumbs {
414
- flex: 1 1 auto;
415
- overflow-y: auto;
416
- padding: 12px 14px 18px;
417
- scrollbar-width: none;
418
- -ms-overflow-style: none;
419
- }
420
- .deck__thumbs::-webkit-scrollbar {
421
- width: 0;
422
- height: 0;
423
- display: none;
424
- }
425
- .deck__thumb {
426
- width: 100%;
427
- margin: 0 0 12px;
428
- padding: 8px;
429
- border: 1px solid var(--deck-chrome-border);
430
- border-radius: 8px;
431
- background: rgba(255,255,255,0.025);
432
- color: inherit;
433
- cursor: pointer;
434
- text-align: left;
435
- transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease;
436
- }
437
- .deck__thumb:hover {
438
- background: rgba(255,255,255,0.06);
439
- border-color: #3a3a40;
440
- }
441
- .deck__thumb.is-active {
442
- border-color: rgba(237,237,237,0.7);
443
- background: rgba(255,255,255,0.085);
444
- box-shadow: 0 0 0 2px rgba(237,237,237,0.08);
445
- }
446
- .deck__thumb-preview {
447
- width: 176px;
448
- height: 99px;
449
- overflow: hidden;
450
- background: #fff;
451
- box-shadow: 0 4px 14px rgba(0,0,0,0.28);
452
- pointer-events: none;
453
- }
454
- .deck__thumb-frame {
455
- display: block;
456
- width: 176px;
457
- height: 99px;
458
- border: 0;
459
- background: #fff;
460
- pointer-events: none;
461
- }
462
- .deck__thumb-meta {
463
- display: flex;
464
- align-items: baseline;
465
- gap: 7px;
466
- margin-top: 7px;
467
- min-width: 0;
468
- font-size: 11px;
469
- line-height: 1.25;
470
- }
471
- .deck__thumb-number {
472
- flex: 0 0 auto;
473
- color: var(--deck-chrome-dim);
474
- font-variant-numeric: tabular-nums;
475
- }
476
- .deck__thumb-name {
477
- min-width: 0;
478
- overflow: hidden;
479
- text-overflow: ellipsis;
480
- white-space: nowrap;
481
- }
482
- .deck__error {
483
- padding: 32px;
484
- color: #ff6b6b;
485
- background: #1a0000;
486
- font-family: var(--font-mono, monospace);
487
- font-size: 14px;
488
- white-space: pre-wrap;
489
- }
490
- /* Export mode (?export=1) — used by the PPTX exporter's headless Chrome
491
- so it captures a clean 1280x720 stage with no letterbox/nav/scaling. */
492
- body.deck--export { background: #ffffff !important; overflow: hidden; }
493
- body.deck--export .deck { position: static; display: block; inset: auto; }
494
- body.deck--export .deck__stage {
495
- transform: none !important;
496
- box-shadow: none !important;
497
- margin: 0;
498
- position: static;
499
- }
500
- body.deck--export .deck__nav,
501
- body.deck--export .deck__rail { display: none !important; }
502
- /* Thumbnail mode runs inside the left rail's isolated iframes. It must
503
- render exactly one slide and no chrome, so per-slide <style> tags do not
504
- leak into the main review document. */
505
- body.deck--thumb { background: transparent !important; overflow: hidden; }
506
- body.deck--thumb .deck { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
507
- body.deck--thumb .deck__stage { box-shadow: none !important; }
508
- body.deck--thumb .deck__nav,
509
- body.deck--thumb .deck__rail { display: none !important; }
510
- /* Print mode (?print=1) — stacks every slide as its own page so the
511
- browser's Print / Cmd-P renders one slide per page (proofing). */
512
- body.deck--print {
513
- background: #ffffff !important;
514
- overflow: visible !important;
515
- height: auto !important;
516
- }
517
- body.deck--print .deck,
518
- body.deck--print .deck__nav,
519
- body.deck--print .deck__rail { display: none !important; }
520
- .print-deck { background: #ffffff; }
521
- .print-page {
522
- width: 1280px;
523
- height: 720px;
524
- position: relative;
525
- overflow: hidden;
526
- background: var(--bg-1);
527
- page-break-after: always;
528
- break-after: page;
529
- }
530
- .print-page:last-child {
531
- page-break-after: auto;
532
- break-after: auto;
533
- }
534
- .print-page__error {
535
- padding: 32px;
536
- color: #ff6b6b;
537
- background: #1a0000;
538
- font-family: var(--font-mono, monospace);
539
- font-size: 14px;
540
- white-space: pre-wrap;
541
- width: 100%;
542
- height: 100%;
543
- box-sizing: border-box;
544
- }
545
- @media print {
546
- @page { size: 1280px 720px; margin: 0; }
547
- html, body { background: #ffffff !important; }
548
- }
549
- </style>
550
- </head>
551
- <body>
552
- <aside class="deck__rail" id="slide-rail" aria-label="Slide thumbnails">
553
- <div class="deck__thumbs" id="slide-thumbs"></div>
554
- </aside>
555
- <div class="deck">
556
- <div class="deck__stage" id="stage"></div>
557
- </div>
558
- <div class="deck__nav" id="nav">loading…</div>
559
-
560
- <!-- Layout sources (inlined from skills/create-slides/layouts/*.jsx).
561
- Each one is evaluated once and exposed under its binding name
562
- (SlideCover, SlideAgendaBlack, ...) so slides can reference it. -->
563
- __LAYOUTS__
564
-
565
- <!-- Slide sources (inlined from this deck's slides/ folder, filename order). -->
566
- __SLIDES__
567
-
568
- <!-- Runtime: React + Babel-standalone from CDN. -->
569
- <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
570
- <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
571
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
572
- <!-- The <Chart> stand-in runtime: the add-in's own vendored scripts, inlined
573
- from the bundle's capture engine (this CLI ships no chart source). They
574
- install window.SlidemindChart, which the bootstrap below binds as Chart
575
- before any slide is evaluated. -->
576
- __CHART_PREVIEW_SCRIPTS__
577
- <script>window.__SLIDE_EDITOR_OVERRIDES__ = __OVERRIDES__;</script>
578
- <script>window.SVG_ASSETS = __SVG_ASSETS__;</script>
579
- <script>
580
- __RUNTIME_APPLIER__
581
- </script>
582
-
583
- <script>
584
- (function () {
585
- // Map kebab-case filename stem -> PascalCase layout binding name.
586
- function layoutBindingName(stem) {
587
- return "Slide" + stem.split("-").map(function (p) {
588
- return p ? p[0].toUpperCase() + p.slice(1) : "";
589
- }).join("");
590
- }
591
-
592
- // Stamp every intrinsic JSX element with data-sm-loc="<prefix>:<line>:<col>"
593
- // so the preview-review overlay (review-overlay.js) can resolve a click back
594
- // to the exact slide source location. Runs as a Babel plugin BEFORE the react
595
- // preset (plugins run before presets) so it sees JSX nodes, not createElement
596
- // calls. Inert without the overlay. Mirrors the add-in's instrument-source-loc.
597
- function makeSourceLocPlugin(locPrefix) {
598
- return function (babel) {
599
- var t = babel.types;
600
- return {
601
- visitor: {
602
- JSXOpeningElement: function (path) {
603
- var node = path.node;
604
- var nameNode = node.name;
605
- // Intrinsic elements only: a lowercase JSXIdentifier. Skip
606
- // components (<SlideCover>), member/namespaced names, fragments.
607
- // <Chart> is the one component that opts in (as in the add-in's
608
- // instrument-source-loc): it forwards the attribute onto its
609
- // root div, so a review pin on a chart resolves to its tag.
610
- if (!nameNode || nameNode.type !== "JSXIdentifier") return;
611
- if (!/^[a-z]/.test(nameNode.name) && nameNode.name !== "Chart") return;
612
- for (var i = 0; i < node.attributes.length; i++) {
613
- var a = node.attributes[i];
614
- if (a.type === "JSXAttribute" && a.name &&
615
- a.name.name === "data-sm-loc") return;
616
- }
617
- var start = node.loc && node.loc.start;
618
- if (!start) return;
619
- var value = locPrefix + ":" + start.line + ":" + (start.column + 1);
620
- node.attributes.push(
621
- t.jsxAttribute(t.jsxIdentifier("data-sm-loc"),
622
- t.stringLiteral(value))
623
- );
624
- }
625
- }
626
- };
627
- };
628
- }
629
-
630
- function evalJsx(src, filename, extra) {
631
- // The inliner wraps each source as "\\n" + src + "\\n" inside the <script>
632
- // block; strip that one leading newline so Babel's line numbers (and thus
633
- // data-sm-loc) align with the author's pristine workspace file.
634
- if (src.charAt(0) === "\\n") src = src.slice(1);
635
- var result = Babel.transform(src, {
636
- presets: [["react", { runtime: "classic" }]],
637
- plugins: [makeSourceLocPlugin(filename)],
638
- sourceType: "module",
639
- filename: filename
640
- });
641
- var body = result.code.replace(
642
- /export\\s+default\\s+/g,
643
- "__module.default = "
644
- );
645
- var names = ["__module", "React"].concat(Object.keys(extra || {}));
646
- var values = [{}, React].concat(Object.keys(extra || {}).map(function (k) {
647
- return extra[k];
648
- }));
649
- var factory = new Function(
650
- names.join(","),
651
- body + "\\nreturn __module.default;"
652
- );
653
- return factory.apply(null, values);
654
- }
655
-
656
- // Preview parity: replace <img src="*.svg"> whose file we inlined into
657
- // window.SVG_ASSETS with the SVG markup itself, carrying the <img>'s box
658
- // and color onto a wrapper so fill="currentColor" resolves — the same
659
- // tint the .pptx renderer applies. Re-runs on every React render via a
660
- // MutationObserver so navigating slides keeps icons inlined.
661
- function smInlineSvgAssets() {
662
- var ASSETS = (typeof window !== "undefined" && window.SVG_ASSETS) || null;
663
- if (!ASSETS || !Object.keys(ASSETS).length) return;
664
-
665
- function lookup(src) {
666
- if (!src) return null;
667
- var s = src.split("?")[0].split("#")[0];
668
- var alt = s.indexOf("./") === 0 ? s.slice(2) : s;
669
- var base = s.split("/").pop();
670
- return ASSETS[s] || ASSETS[alt] || ASSETS[base] || null;
671
- }
672
-
673
- function inlineWithin(root) {
674
- if (!root || !root.querySelectorAll) return;
675
- var imgs = root.querySelectorAll("img");
676
- for (var i = 0; i < imgs.length; i++) {
677
- var img = imgs[i];
678
- if (img.getAttribute("data-sm-inlined") === "1") continue;
679
- var markup = lookup(img.getAttribute("src"));
680
- if (!markup) continue;
681
- var span = document.createElement("span");
682
- span.setAttribute("data-sm-inlined", "1");
683
- if (img.className) span.className = img.className;
684
- var styleAttr = img.getAttribute("style");
685
- if (styleAttr) span.setAttribute("style", styleAttr);
686
- // Pull the laid-out box + colour even when they came from CSS, not
687
- // an inline style, so sizing holds and currentColor resolves.
688
- var cs = window.getComputedStyle ? window.getComputedStyle(img) : null;
689
- var hasW = !!span.style.width;
690
- var hasH = !!span.style.height;
691
- if (cs) {
692
- if (!hasW && cs.width && cs.width !== "auto" && cs.width !== "0px") {
693
- span.style.width = cs.width;
694
- hasW = true;
695
- }
696
- if (!hasH && cs.height && cs.height !== "auto" && cs.height !== "0px") {
697
- span.style.height = cs.height;
698
- hasH = true;
699
- }
700
- if (!span.style.color && cs.color) span.style.color = cs.color;
701
- }
702
- if (!span.style.display || span.style.display === "inline") {
703
- span.style.display = "inline-flex";
704
- }
705
- if (!span.style.alignItems) span.style.alignItems = "center";
706
- if (!span.style.justifyContent) span.style.justifyContent = "center";
707
- span.innerHTML = markup;
708
- var svg = span.querySelector("svg");
709
- if (svg) {
710
- svg.style.display = "block";
711
- if (hasW || hasH) {
712
- svg.style.width = "100%";
713
- svg.style.height = "100%";
714
- svg.removeAttribute("width");
715
- svg.removeAttribute("height");
716
- }
717
- }
718
- if (img.parentNode) img.parentNode.replaceChild(span, img);
719
- }
720
- }
721
-
722
- var scheduled = false;
723
- var raf =
724
- window.requestAnimationFrame ||
725
- function (cb) { return setTimeout(cb, 16); };
726
- function schedule() {
727
- if (scheduled) return;
728
- scheduled = true;
729
- raf(function () {
730
- scheduled = false;
731
- inlineWithin(document.body);
732
- });
733
- }
734
- if (typeof MutationObserver !== "undefined") {
735
- new MutationObserver(schedule).observe(document.body, {
736
- childList: true,
737
- subtree: true,
738
- });
739
- }
740
- schedule();
741
- }
742
-
743
- var LAYOUTS = {};
744
- document.querySelectorAll('script[data-kind="layout"]').forEach(function (s) {
745
- var name = s.dataset.binding || layoutBindingName(s.dataset.name);
746
- try {
747
- LAYOUTS[name] = evalJsx(
748
- s.textContent,
749
- "layouts/" + s.dataset.name + ".jsx",
750
- {}
751
- );
752
- window[name] = LAYOUTS[name];
753
- } catch (err) {
754
- console.error("Layout " + name + " failed to load:", err);
755
- throw err;
756
- }
757
- });
758
-
759
- // The Chart binding slide jsx is compiled against — installed by the
760
- // bundle's chart-preview scripts above; without them a chart renders
761
- // nothing, exactly as the add-in's page behaves.
762
- var Chart = window.SlidemindChart
763
- ? window.SlidemindChart.createComponent(React)
764
- : function () { return null; };
765
- var slideScope = Object.assign({ Chart: Chart }, LAYOUTS);
766
-
767
- var slides = [];
768
- document.querySelectorAll('script[data-kind="slide"]').forEach(function (s) {
769
- var idx = parseInt(s.dataset.index, 10);
770
- var name = s.dataset.name;
771
- try {
772
- var Component = evalJsx(
773
- s.textContent,
774
- "slides/" + name + ".jsx",
775
- slideScope
776
- );
777
- slides[idx] = { name: name, Component: Component };
778
- } catch (err) {
779
- console.error("Slide " + name + " failed to load:", err);
780
- slides[idx] = { name: name, error: err };
781
- }
782
- });
783
-
784
- var stage = document.getElementById("stage");
785
- var nav = document.getElementById("nav");
786
- var rail = document.getElementById("slide-rail");
787
- var thumbs = document.getElementById("slide-thumbs");
788
- var thumbButtons = [];
789
- var params = new URLSearchParams(location.search);
790
- var exportMode = params.get("export") === "1";
791
- var printMode = params.get("print") === "1";
792
- var thumbMode = params.get("thumb") === "1";
793
- if (exportMode) document.body.classList.add("deck--export");
794
- if (thumbMode) document.body.classList.add("deck--thumb");
795
-
796
- smInlineSvgAssets();
797
-
798
- if (printMode) {
799
- document.body.classList.add("deck--print");
800
- var printDeck = document.createElement("div");
801
- printDeck.className = "print-deck";
802
- document.body.appendChild(printDeck);
803
- slides.forEach(function (slide) {
804
- var page = document.createElement("div");
805
- page.className = "print-page";
806
- printDeck.appendChild(page);
807
- if (slide.error) {
808
- var errEl = document.createElement("div");
809
- errEl.className = "print-page__error";
810
- errEl.textContent = slide.name + ".jsx failed:\\n" + slide.error.message;
811
- page.appendChild(errEl);
812
- return;
813
- }
814
- ReactDOM.createRoot(page).render(
815
- React.createElement(slide.Component, { assetsPath: "./assets" })
816
- );
817
- });
818
- return;
819
- }
820
-
821
- var startIndex = parseInt(params.get("slide") || "0", 10);
822
- if (isNaN(startIndex) || startIndex < 0) startIndex = 0;
823
- if (startIndex >= slides.length) startIndex = slides.length - 1;
824
- var current = startIndex;
825
- var root = ReactDOM.createRoot(stage);
826
-
827
- function buildThumbs() {
828
- if (!thumbs || printMode || exportMode || thumbMode) return;
829
- thumbs.innerHTML = "";
830
- thumbButtons = [];
831
- slides.forEach(function (slide, idx) {
832
- var btn = document.createElement("button");
833
- btn.type = "button";
834
- btn.className = "deck__thumb";
835
- btn.setAttribute("aria-label", "Go to slide " + (idx + 1) + ": " + slide.name);
836
-
837
- var preview = document.createElement("div");
838
- preview.className = "deck__thumb-preview";
839
- var frame = document.createElement("iframe");
840
- frame.className = "deck__thumb-frame";
841
- frame.loading = "lazy";
842
- frame.setAttribute("aria-hidden", "true");
843
- frame.tabIndex = -1;
844
- frame.src = location.pathname + "?slide=" + idx + "&thumb=1";
845
- preview.appendChild(frame);
846
-
847
- var meta = document.createElement("div");
848
- meta.className = "deck__thumb-meta";
849
- var num = document.createElement("span");
850
- num.className = "deck__thumb-number";
851
- num.textContent = String(idx + 1).padStart(2, "0");
852
- var name = document.createElement("span");
853
- name.className = "deck__thumb-name";
854
- name.textContent = slide.name;
855
- meta.appendChild(num);
856
- meta.appendChild(name);
857
-
858
- btn.appendChild(preview);
859
- btn.appendChild(meta);
860
- btn.addEventListener("click", function () {
861
- setCurrent(idx);
862
- stage.focus && stage.focus();
863
- });
864
- thumbs.appendChild(btn);
865
- thumbButtons[idx] = btn;
866
- });
867
- }
868
-
869
- function updateThumbs() {
870
- thumbButtons.forEach(function (btn, idx) {
871
- if (!btn) return;
872
- var active = idx === current;
873
- btn.classList.toggle("is-active", active);
874
- btn.setAttribute("aria-current", active ? "true" : "false");
875
- if (active && thumbs) {
876
- var top = btn.offsetTop;
877
- var bottom = top + btn.offsetHeight;
878
- if (top < thumbs.scrollTop) {
879
- thumbs.scrollTop = Math.max(0, top - 12);
880
- } else if (bottom > thumbs.scrollTop + thumbs.clientHeight) {
881
- thumbs.scrollTop = bottom - thumbs.clientHeight + 12;
882
- }
883
- }
884
- });
885
- }
886
-
887
- function render() {
888
- var slide = slides[current];
889
- if (!slide) return;
890
- nav.textContent =
891
- (current + 1) + " / " + slides.length + " · " + slide.name;
892
- if (slide.error) {
893
- root.render(
894
- React.createElement(
895
- "div",
896
- { className: "deck__error" },
897
- slide.name + ".jsx failed:\\n" + slide.error.message
898
- )
899
- );
900
- updateThumbs();
901
- return;
902
- }
903
- root.render(
904
- React.createElement(slide.Component, { assetsPath: "./assets" })
905
- );
906
- updateThumbs();
907
- }
908
-
909
- function setCurrent(index) {
910
- current = Math.max(0, Math.min(index, slides.length - 1));
911
- render();
912
- }
913
-
914
- function fit() {
915
- if (exportMode) {
916
- stage.style.transform = "none";
917
- return;
918
- }
919
- var railW = rail && getComputedStyle(rail).display !== "none" ? rail.offsetWidth : 0;
920
- var sx = Math.max(1, window.innerWidth - railW) / 1280;
921
- var sy = window.innerHeight / 720;
922
- var s = Math.min(sx, sy) * (thumbMode ? 1 : 0.95);
923
- stage.style.transform = "scale(" + s + ")";
924
- }
925
-
926
- window.addEventListener("resize", fit);
927
- buildThumbs();
928
- fit();
929
- render();
930
-
931
- document.addEventListener("keydown", function (e) {
932
- var k = e.key;
933
- if (k === "ArrowRight" || k === "PageDown" || k === " ") {
934
- setCurrent(current + 1);
935
- e.preventDefault();
936
- } else if (k === "ArrowLeft" || k === "PageUp") {
937
- setCurrent(current - 1);
938
- e.preventDefault();
939
- } else if (k === "Home") {
940
- setCurrent(0);
941
- } else if (k === "End") {
942
- setCurrent(slides.length - 1);
943
- } else if (k === "f" || k === "F") {
944
- if (document.fullscreenElement) document.exitFullscreen();
945
- else document.documentElement.requestFullscreen();
946
- }
947
- });
948
- })();
949
- </script>
950
- </body>
951
- </html>
952
- `;
289
+ // The preview page. Kept as a real .html file (syntax-highlighted, diffable)
290
+ // rather than an 800-line template string; the __PLACEHOLDER__ slots are
291
+ // filled by buildDeck below. Function replacer: a string replacement would
292
+ // expand `$&`-style patterns in the substituted value.
293
+ const HTML_TEMPLATE = fs
294
+ .readFileSync(path.join(__dirname, "preview.html"), "utf8")
295
+ .replace("__FAVICON_LINK__", () => FAVICON_LINK);
953
296
 
954
297
  export async function buildDeck(deckDir, opts = {}) {
955
298
  const skill = resolveSkillForDeck(deckDir);
@@ -1017,6 +360,7 @@ export async function buildDeck(deckDir, opts = {}) {
1017
360
  // loadChartPreview) — resolved after lint, which already proved the bundle
1018
361
  // is materialized.
1019
362
  const chartPreview = await loadChartPreview(resolveBundleDirForDeck(deckDir));
363
+ const connectorPreview = await loadConnectorPreview(resolveBundleDirForDeck(deckDir));
1020
364
 
1021
365
  copyDesignSystem(deckDir, skill);
1022
366
 
@@ -1043,6 +387,9 @@ export async function buildDeck(deckDir, opts = {}) {
1043
387
  .map((script) => ` <script>\n${safeInline(script.source)}\n </script>`)
1044
388
  .join("\n")
1045
389
  : "",
390
+ )
391
+ .replace("__CONNECTOR_PREVIEW_SCRIPT__", () =>
392
+ connectorPreview ? ` <script>\n${safeInline(connectorPreview.source)}\n </script>` : "",
1046
393
  );
1047
394
 
1048
395
  const out = path.join(deckDir, "presentation.html");
@@ -1176,8 +523,8 @@ function parseArgs(argv) {
1176
523
  args.fix = true;
1177
524
  } else if (a === "-h" || a === "--help") {
1178
525
  process.stdout.write(
1179
- "Usage: slidemind build <deck> [--open] [--fix]\n" +
1180
- " node src/builders/build.mjs --deck <deck> [--open] [--fix]\n" +
526
+ "Usage: node src/builders/build.mjs --deck <deck> [--open] [--fix]\n" +
527
+ " (the CLI entry point is `slidemind view-draft <deck> [--fix]`)\n" +
1181
528
  " --fix apply the deterministic lint autofixes to slides/*.jsx before building\n" +
1182
529
  " (pin inline-svg px size from its viewBox; letter-spacing em→px where the\n" +
1183
530
  " block's px font-size makes the conversion exact)\n",