slidemind 0.3.19 → 0.4.0
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +591 -15
- package/README.md +202 -7
- package/bin/slidemind.mjs +50 -2
- package/package.json +2 -2
- package/skills/create-slides/SKILL.md +484 -886
- package/skills/search/SKILL.md +46 -23
- package/src/bridge/build-and-push.mjs +548 -73
- package/src/bridge/bundle.mjs +100 -31
- package/src/bridge/capture.mjs +14 -17
- package/src/bridge/client/brands.mjs +157 -18
- package/src/bridge/client/engine-routing.mjs +48 -13
- package/src/bridge/client/engine-search.mjs +24 -1
- package/src/bridge/client/engine-slides.mjs +1 -1
- package/src/bridge/client/render.mjs +27 -1
- package/src/bridge/client/transport.mjs +499 -23
- package/src/bridge/client.mjs +10 -0
- package/src/bridge/config.mjs +67 -6
- package/src/bridge/engine-download.mjs +0 -10
- package/src/bridge/graph-auth.mjs +55 -3
- package/src/bridge/image-intent-envelope.mjs +239 -0
- package/src/bridge/package-preview.mjs +67 -0
- package/src/bridge/sharepoint.mjs +914 -0
- package/src/builders/asset-fetch.mjs +203 -0
- package/src/builders/block-dividers.mjs +83 -0
- package/src/builders/build.mjs +171 -87
- package/src/builders/card-height.mjs +294 -0
- package/src/builders/chart-hole.mjs +183 -0
- package/src/builders/chart-jsx.mjs +410 -0
- package/src/builders/chart-labels.mjs +326 -0
- package/src/builders/concept-geometry.mjs +370 -0
- package/src/builders/concept-intent.mjs +200 -0
- package/src/builders/concept-layout.mjs +79 -0
- package/src/builders/concept-lines.mjs +509 -0
- package/src/builders/concept-lists.mjs +270 -0
- package/src/builders/concept-marks.mjs +84 -0
- package/src/builders/concept-relationships.mjs +12 -0
- package/src/builders/concept-spec.mjs +645 -0
- package/src/builders/concept-text.mjs +140 -0
- package/src/builders/concept-waves.mjs +57 -0
- package/src/builders/copy-fit.mjs +139 -0
- package/src/builders/design-tokens.mjs +347 -0
- package/src/builders/draft-check.mjs +552 -340
- package/src/builders/draft-lines.mjs +369 -0
- package/src/builders/edit-route.mjs +29 -0
- package/src/builders/figure-sketch.mjs +375 -0
- package/src/builders/fold-sets.mjs +639 -0
- package/src/builders/gate-server.mjs +9 -4
- package/src/builders/gate-session.js +273 -12
- package/src/builders/gate-shell.js +1494 -0
- package/src/builders/image-intent-check.mjs +308 -0
- package/src/builders/image-use-reconcile.mjs +107 -0
- package/src/builders/kit-helpers.mjs +121 -0
- package/src/builders/layout-names.mjs +32 -0
- package/src/builders/legacy-reference-uses.mjs +179 -0
- package/src/builders/line-fit.mjs +224 -0
- package/src/builders/line-native.mjs +895 -0
- package/src/builders/line-relations.mjs +230 -0
- package/src/builders/lint-fix.mjs +117 -9
- package/src/builders/lint.mjs +25 -20
- package/src/builders/list-jsx.mjs +88 -0
- package/src/builders/placement.mjs +133 -0
- package/src/builders/plan-checks.mjs +135 -0
- package/src/builders/plan-comments.mjs +19 -0
- package/src/builders/plan-handlers.mjs +95 -0
- package/src/builders/plan-markdown-edit.mjs +3 -3
- package/src/builders/plan-overlay.js +1530 -1608
- package/src/builders/plan.mjs +689 -186
- package/src/builders/preset-definitions.json +1 -0
- package/src/builders/preset-eval.mjs +244 -0
- package/src/builders/preview.html +51 -120
- package/src/builders/red-thread-backbone.mjs +135 -36
- package/src/builders/repair.mjs +205 -0
- package/src/builders/repeat-groups.mjs +540 -0
- package/src/builders/resolve-skill.mjs +5 -4
- package/src/builders/review-overlay.js +296 -293
- package/src/builders/review.mjs +305 -84
- package/src/builders/rule-shift.mjs +96 -0
- package/src/builders/seat.mjs +354 -0
- package/src/builders/settle-driver.mjs +236 -0
- package/src/builders/settle.mjs +1448 -0
- package/src/builders/slide-prep.mjs +67 -0
- package/src/builders/spec-assets.mjs +236 -0
- package/src/builders/spec-match.mjs +56 -0
- package/src/builders/stack.mjs +413 -0
- package/src/builders/storyboard-markdown.mjs +70 -27
- package/src/builders/storyboard-schema.mjs +54 -114
- package/src/builders/text-blocks.mjs +212 -0
- package/src/builders/text-card-fit.mjs +552 -0
- package/src/builders/text-stack.mjs +276 -0
- package/src/builders/transcribe.mjs +1069 -0
- package/src/cli/assets.mjs +164 -0
- package/src/cli/common.mjs +173 -12
- package/src/cli/concept-spec-support.mjs +76 -0
- package/src/cli/concepts.mjs +1002 -103
- package/src/cli/deck.mjs +401 -65
- package/src/cli/diagnose.mjs +4 -2
- package/src/cli/engine-helpers.mjs +17 -10
- package/src/cli/engine-results.mjs +76 -3
- package/src/cli/help.mjs +232 -28
- package/src/cli/install.mjs +9 -0
- package/src/cli/maintenance.mjs +104 -0
- package/src/cli/read.mjs +2 -1
- package/src/cli/ready.mjs +6 -3
- package/src/cli/record.mjs +522 -0
- package/src/cli/remote.mjs +818 -0
- package/src/cli/revision.mjs +686 -0
- package/src/cli/search.mjs +30 -11
- package/src/cli/session.mjs +56 -13
- package/src/cli/setup.mjs +46 -2
- package/src/cli/status.mjs +142 -33
- package/src/cli/tasks.mjs +246 -0
- package/src/cli/transcribe.mjs +854 -0
- package/src/cli/workspace-ui.mjs +260 -0
- package/src/cli/workspace.mjs +212 -34
- package/src/install-channel.mjs +64 -7
- package/src/lib/adopt.mjs +346 -25
- package/src/lib/concept-spec-binding.mjs +96 -0
- package/src/lib/conversions.mjs +70 -6
- package/src/lib/convert-folded.mjs +107 -0
- package/src/lib/deck-paths.mjs +418 -0
- package/src/lib/durable-ids.mjs +47 -0
- package/src/lib/edit-operations.mjs +316 -0
- package/src/lib/edit-state.mjs +1244 -0
- package/src/lib/edit-transitions.mjs +120 -0
- package/src/lib/extensions.mjs +501 -0
- package/src/lib/image-assets.mjs +465 -0
- package/src/lib/image-derivatives.mjs +234 -0
- package/src/lib/image-intent-fingerprint.mjs +70 -0
- package/src/lib/image-intent-tags.mjs +176 -0
- package/src/lib/image-intent.mjs +802 -0
- package/src/lib/image-use-overrides.mjs +279 -0
- package/src/lib/inline-components.mjs +613 -0
- package/src/lib/lint-converted.mjs +68 -4
- package/src/lib/merge-json.mjs +163 -0
- package/src/lib/overwrite-guard.mjs +3 -3
- package/src/lib/presentation-context.mjs +531 -0
- package/src/lib/preservation-tags.mjs +157 -0
- package/src/lib/preservation-verdict.mjs +1342 -0
- package/src/lib/publish.mjs +1557 -0
- package/src/lib/rebase.mjs +945 -0
- package/src/lib/reconcile-ack.mjs +4 -6
- package/src/lib/reconcile.mjs +62 -24
- package/src/lib/record-client.mjs +175 -0
- package/src/lib/record-state.mjs +151 -0
- package/src/lib/remote.mjs +661 -0
- package/src/lib/revision.mjs +1210 -0
- package/src/lib/slide-guids.mjs +4 -6
- package/src/lib/slide-preservation.mjs +423 -0
- package/src/lib/slide-selection.mjs +9 -0
- package/src/lib/workspace-transaction.mjs +117 -0
- package/src/pptx/content-digest.mjs +334 -0
- package/src/pptx/deck-assemble.mjs +1278 -0
- package/src/pptx/fill/engine.mjs +2 -3
- package/src/pptx/fill/freeform.mjs +6 -3
- package/src/pptx/ooxml.mjs +20 -0
- package/src/pptx/package-evidence.mjs +383 -0
- package/src/pptx/tags.mjs +163 -11
- package/src/update-check.mjs +22 -4
- package/src/workspace/adapters.mjs +222 -0
- package/src/workspace/auth.mjs +256 -0
- package/src/workspace/autostart.mjs +204 -0
- package/src/workspace/daemon.mjs +360 -0
- package/src/workspace/discovery.mjs +180 -0
- package/src/workspace/edit.mjs +167 -0
- package/src/workspace/events.mjs +52 -0
- package/src/workspace/gate-client.mjs +290 -0
- package/src/workspace/handback.mjs +73 -0
- package/src/workspace/lifecycle.mjs +635 -0
- package/src/workspace/operations.mjs +200 -0
- package/src/workspace/pick-folder.mjs +152 -0
- package/src/workspace/registry.mjs +359 -0
- package/src/workspace/remote-links.mjs +397 -0
- package/src/workspace/remote-status.mjs +359 -0
- package/src/workspace/routes.mjs +1331 -0
- package/src/workspace/server.mjs +159 -0
- package/src/workspace/sync.mjs +1119 -0
- package/src/workspace/task-actions.mjs +215 -0
- package/src/workspace/tasks.mjs +507 -0
- package/src/workspace/ui/app.js +1886 -0
- package/src/workspace/ui/index.html +141 -0
- package/src/workspace/ui/plan-bridge.js +120 -0
- package/src/workspace/ui/sharepoint.svg +59 -0
- package/src/workspace/ui/styles.css +423 -0
- package/src/builders/visual-families.mjs +0 -46
package/src/builders/build.mjs
CHANGED
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
// slidemind view-draft <deck> (the CLI entry point; it builds, then captures)
|
|
7
7
|
// node src/builders/build.mjs --deck <deck> [--open] [--fix]
|
|
8
8
|
//
|
|
9
|
-
// Convention
|
|
10
|
-
//
|
|
9
|
+
// Convention (the deck's WORKSPACE — `<deck>/.slidemind/`, or the deck dir
|
|
10
|
+
// itself for a deck that predates the layout; see src/lib/deck-paths.mjs):
|
|
11
|
+
// <workspace>/
|
|
11
12
|
// slides/
|
|
12
13
|
// 01-cover.jsx
|
|
13
14
|
// 02-agenda.jsx
|
|
14
15
|
// ...
|
|
16
|
+
// preview/presentation.html what this module writes
|
|
17
|
+
// preview/fonts/ the bundle's brand faces, next to the page
|
|
15
18
|
//
|
|
16
19
|
// Each slide is a thin JSX component that composes a layout from
|
|
17
20
|
// skills/create-slides/layouts/. Slide files do NOT import layouts; the
|
|
@@ -21,9 +24,11 @@
|
|
|
21
24
|
// The build:
|
|
22
25
|
// 1. Inlines every layout .jsx as a <script type="text/jsx-template"> block.
|
|
23
26
|
// 2. Inlines every slide .jsx the same way, in filename order.
|
|
24
|
-
// 3.
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
+
// 3. Inlines corporate-design.css + layouts.css FROM THE BUNDLE CACHE and
|
|
28
|
+
// points every brand asset (chrome-*, master-*, logos) at the bundle's
|
|
29
|
+
// assets dir by absolute file: URL — the design is referenced, never
|
|
30
|
+
// copied into the deck (see the deck-workspace change).
|
|
31
|
+
// 4. Emits preview/presentation.html with React + Babel-standalone from CDN and a
|
|
27
32
|
// bootstrap that transpiles on load and renders a keyboard-navigable
|
|
28
33
|
// deck.
|
|
29
34
|
|
|
@@ -31,6 +36,7 @@ import { spawn } from "node:child_process";
|
|
|
31
36
|
import fs from "node:fs";
|
|
32
37
|
import os from "node:os";
|
|
33
38
|
import path from "node:path";
|
|
39
|
+
import crypto from "node:crypto";
|
|
34
40
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
35
41
|
|
|
36
42
|
import { resolveBundleDirForDeck } from "../bridge/bundle.mjs";
|
|
@@ -41,6 +47,8 @@ import { lintConvertedOrThrow } from "../lib/lint-converted.mjs";
|
|
|
41
47
|
import { resolveSkillForDeck } from "./resolve-skill.mjs";
|
|
42
48
|
import { convertedHostLayouts, loadConversions } from "../lib/conversions.mjs";
|
|
43
49
|
import { slideOrderWarning, sortSlideFilenames } from "../lib/slide-order.mjs";
|
|
50
|
+
import { selectSlideFiles } from "../lib/slide-selection.mjs";
|
|
51
|
+
import { bundleOwnedFiles, deckSlug, previewPath } from "../lib/deck-paths.mjs";
|
|
44
52
|
|
|
45
53
|
const __filename = fileURLToPath(import.meta.url);
|
|
46
54
|
const __dirname = path.dirname(__filename);
|
|
@@ -68,19 +76,59 @@ export function stripImports(src) {
|
|
|
68
76
|
);
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
|
|
79
|
+
// Brand assets (logos, decor, chrome-*/master-* layout backgrounds) live in the
|
|
80
|
+
// materialized bundle cache and are NOT copied into the deck. Every reference
|
|
81
|
+
// to them in a layout, a slide or the CSS is rewritten to an absolute file: URL
|
|
82
|
+
// into that cache, so the page works opened straight off disk. The review
|
|
83
|
+
// server rewrites this same prefix to a route it serves (see review.mjs) —
|
|
84
|
+
// a browser on an http page may not load file: subresources.
|
|
85
|
+
export function brandAssetsUrl(skill) {
|
|
86
|
+
const dir = skill?.brand?.assetsDir;
|
|
87
|
+
return dir ? pathToFileURL(dir).href.replace(/\/$/, "") : "./assets";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function rewriteAssetsPath(src, brandUrl) {
|
|
72
91
|
// The layout defaults point at "../assets" (correct for in-repo authoring
|
|
73
|
-
// under skills/create-slides/layouts/)
|
|
74
|
-
//
|
|
92
|
+
// under skills/create-slides/layouts/) — that layer IS the brand's, so it
|
|
93
|
+
// resolves to the bundle.
|
|
94
|
+
return src.replace(/assetsPath = "\.\.\/assets"/g, `assetsPath = "${brandUrl}"`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A slide that names a layout background by hand ("./assets/chrome-Black.png")
|
|
98
|
+
// meant the brand's copy; deck-owned media ("./assets/photo.jpg") stays local.
|
|
99
|
+
// `deckOwned` holds the files the DECK owns under those names — a user's own
|
|
100
|
+
// `assets/master-data.png` is its own image, not the bundle's, and keeps
|
|
101
|
+
// resolving locally. Layout sources are the brand's, so they pass none.
|
|
102
|
+
function rewriteBrandAssetRefs(src, brandUrl, deckOwned = null) {
|
|
75
103
|
return src.replace(
|
|
76
|
-
/
|
|
77
|
-
|
|
104
|
+
/(["'`(])\.?\/?assets\/((?:chrome|master)-[^"'`)]*)/g,
|
|
105
|
+
(m, open, name) => (deckOwned?.has(`assets/${name}`) ? m : `${open}${brandUrl}/${name}`),
|
|
78
106
|
);
|
|
79
107
|
}
|
|
80
108
|
|
|
81
|
-
|
|
109
|
+
// Files in the deck's assets/ that are NOT verified copies of the bundle's own —
|
|
110
|
+
// the deck owns them whatever they are called.
|
|
111
|
+
function deckOwnedAssets(deckDir) {
|
|
112
|
+
const bundleOwned = bundleOwnedFiles(deckDir, resolveBundleDirForDeck(deckDir));
|
|
113
|
+
const owned = new Set();
|
|
114
|
+
let names = [];
|
|
115
|
+
try {
|
|
116
|
+
names = fs.readdirSync(path.join(deckDir, "assets"));
|
|
117
|
+
} catch {
|
|
118
|
+
return owned;
|
|
119
|
+
}
|
|
120
|
+
for (const name of names) {
|
|
121
|
+
if (!bundleOwned.has(`assets/${name}`)) owned.add(`assets/${name}`);
|
|
122
|
+
}
|
|
123
|
+
return owned;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function inlineLayout(file, brandUrl) {
|
|
82
127
|
const stem = path.basename(file, ".jsx");
|
|
83
|
-
const raw =
|
|
128
|
+
const raw = rewriteBrandAssetRefs(
|
|
129
|
+
rewriteAssetsPath(fs.readFileSync(file, "utf8"), brandUrl),
|
|
130
|
+
brandUrl,
|
|
131
|
+
);
|
|
84
132
|
const binding = raw.match(/export\s+default\s+function\s+([A-Za-z_$][\w$]*)\s*\(/)?.[1] || layoutBindingNameForStem(stem);
|
|
85
133
|
const src = safeInline(raw);
|
|
86
134
|
return (
|
|
@@ -105,59 +153,42 @@ function layoutBindingNameForStem(stem) {
|
|
|
105
153
|
)).join("");
|
|
106
154
|
}
|
|
107
155
|
|
|
108
|
-
function inlineSlide(file, index) {
|
|
156
|
+
function inlineSlide(file, index, brandUrl, deckOwned) {
|
|
109
157
|
const stem = path.basename(file, ".jsx");
|
|
110
|
-
const src = safeInline(
|
|
158
|
+
const src = safeInline(
|
|
159
|
+
rewriteBrandAssetRefs(stripImports(fs.readFileSync(file, "utf8")), brandUrl, deckOwned),
|
|
160
|
+
);
|
|
111
161
|
return (
|
|
112
162
|
`<script type="text/jsx-template" data-kind="slide" ` +
|
|
113
163
|
`data-index="${index}" data-name="${stem}">\n${src}\n</script>`
|
|
114
164
|
);
|
|
115
165
|
}
|
|
116
166
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (fs.statSync(p).isFile()) {
|
|
135
|
-
fs.copyFileSync(p, path.join(outAssets, entry));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
fs.copyFileSync(skill.cssFile, path.join(deckDir, "corporate-design.css"));
|
|
140
|
-
|
|
141
|
-
// layouts.css references url("../assets/...") relative to the in-repo
|
|
142
|
-
// layouts dir. The build copies it flat into the deck next to assets/,
|
|
143
|
-
// so rewrite the URLs to "./assets/...".
|
|
144
|
-
//
|
|
145
|
-
// Layering: brand layouts.css first (lower in the cascade), then the
|
|
146
|
-
// active skill's layouts.css. Skill rules sharing a selector win.
|
|
147
|
-
const cssSources =
|
|
148
|
-
skill.brand.layoutsCssFile === path.join(skill.layoutsDir, "layouts.css")
|
|
167
|
+
// The design system, INLINED from the bundle cache instead of copied into the
|
|
168
|
+
// deck: two stylesheets (corporate-design.css, then layouts.css so its rules
|
|
169
|
+
// win a selector tie) with every url("../assets/…") pointed at the bundle.
|
|
170
|
+
//
|
|
171
|
+
// Layering is unchanged — brand layouts.css first, the active skill's on top —
|
|
172
|
+
// only the destination is: nothing lands in the deck, so a deck no longer
|
|
173
|
+
// carries (or uploads, or hashes into an approval) a copy of the design.
|
|
174
|
+
function designSystemCss(skill, brandUrl) {
|
|
175
|
+
const chunks = [];
|
|
176
|
+
const rewrite = (css) =>
|
|
177
|
+
css
|
|
178
|
+
.replace(/url\("\.\.\/assets\//g, `url("${brandUrl}/`)
|
|
179
|
+
.replace(/url\('\.\.\/assets\//g, `url('${brandUrl}/`)
|
|
180
|
+
.replace(/url\(\.\.\/assets\//g, `url(${brandUrl}/`);
|
|
181
|
+
const cssSources = [
|
|
182
|
+
skill.cssFile,
|
|
183
|
+
...(skill.brand.layoutsCssFile === path.join(skill.layoutsDir, "layouts.css")
|
|
149
184
|
? [path.join(skill.layoutsDir, "layouts.css")]
|
|
150
|
-
: [skill.brand.layoutsCssFile, path.join(skill.layoutsDir, "layouts.css")]
|
|
151
|
-
|
|
185
|
+
: [skill.brand.layoutsCssFile, path.join(skill.layoutsDir, "layouts.css")]),
|
|
186
|
+
];
|
|
152
187
|
for (const cssPath of cssSources) {
|
|
153
|
-
if (!fs.existsSync(cssPath)) continue;
|
|
154
|
-
|
|
155
|
-
css = css.replace(/url\("\.\.\/assets\//g, 'url("./assets/');
|
|
156
|
-
css = css.replace(/url\('\.\.\/assets\//g, "url('./assets/");
|
|
157
|
-
css = css.replace(/url\(\.\.\/assets\//g, "url(./assets/");
|
|
158
|
-
cssChunks.push(css);
|
|
188
|
+
if (!cssPath || !fs.existsSync(cssPath)) continue;
|
|
189
|
+
chunks.push(rewrite(fs.readFileSync(cssPath, "utf8")));
|
|
159
190
|
}
|
|
160
|
-
|
|
191
|
+
return chunks.join("\n\n");
|
|
161
192
|
}
|
|
162
193
|
|
|
163
194
|
// First concrete (non-generic) family of a CSS var's font stack, or null.
|
|
@@ -204,7 +235,7 @@ function brandFontFaces(deckDir, skill) {
|
|
|
204
235
|
|
|
205
236
|
let css = "";
|
|
206
237
|
try {
|
|
207
|
-
css = fs.readFileSync(
|
|
238
|
+
css = fs.readFileSync(skill.cssFile, "utf8");
|
|
208
239
|
} catch {
|
|
209
240
|
/* alias resolution degrades to canonical families only */
|
|
210
241
|
}
|
|
@@ -213,7 +244,7 @@ function brandFontFaces(deckDir, skill) {
|
|
|
213
244
|
mono: firstConcreteFamily(css, "font-mono"),
|
|
214
245
|
};
|
|
215
246
|
|
|
216
|
-
const outFonts =
|
|
247
|
+
const outFonts = previewPath(deckDir, "fonts");
|
|
217
248
|
const rules = [];
|
|
218
249
|
for (const font of fonts) {
|
|
219
250
|
if (!font?.family || !font?.filename) continue;
|
|
@@ -239,9 +270,17 @@ function brandFontFaces(deckDir, skill) {
|
|
|
239
270
|
const format = filename.toLowerCase().endsWith(".otf")
|
|
240
271
|
? "opentype"
|
|
241
272
|
: "truetype";
|
|
273
|
+
// Two sources, because this one page is opened from four places and no single
|
|
274
|
+
// relative URL resolves in all of them: page-relative (`./fonts/`) is right for
|
|
275
|
+
// a file:// open and for the workspace's own /decks/:id/preview/ route, while
|
|
276
|
+
// `./preview/fonts/` is right for the review server (page at /, statics from the
|
|
277
|
+
// workspace root) and the embedded /decks/:id/review gate. A `src:` list is
|
|
278
|
+
// exactly the fallback CSS provides for this — the browser takes the first that
|
|
279
|
+
// loads, so brand type no longer silently degrades to a system font.
|
|
242
280
|
const rule = (family) =>
|
|
243
281
|
`@font-face { font-family: ${JSON.stringify(family)}; ` +
|
|
244
|
-
`src: url(${JSON.stringify(`./fonts/${filename}`)}) format("${format}")
|
|
282
|
+
`src: url(${JSON.stringify(`./fonts/${filename}`)}) format("${format}"), ` +
|
|
283
|
+
`url(${JSON.stringify(`./preview/fonts/${filename}`)}) format("${format}"); ` +
|
|
245
284
|
`font-weight: ${weight}; font-style: ${style}; font-display: block; }`;
|
|
246
285
|
rules.push(rule(font.family));
|
|
247
286
|
const alias = aliasByRole[font.role];
|
|
@@ -267,34 +306,36 @@ function loadOverrides(deckDir) {
|
|
|
267
306
|
return data;
|
|
268
307
|
}
|
|
269
308
|
|
|
270
|
-
//
|
|
271
|
-
// the
|
|
309
|
+
// SVG assets the preview can inline — the deck's own downloads plus the
|
|
310
|
+
// bundle's brand glyphs (the deck no longer carries a copy of those). An <img src="x.svg"> can't inherit `color`, so a
|
|
272
311
|
// CSS-tinted icon looks wrong in the HTML preview yet right in the .pptx render
|
|
273
312
|
// (which substitutes currentColor) — the source-mismatch that sends agents off
|
|
274
313
|
// to inspect SVGs. Inlining the SVG at runtime makes the preview honour `color`
|
|
275
314
|
// exactly as the render does. Keyed three ways so any of the src spellings an
|
|
276
315
|
// author uses ("./assets/x.svg", "assets/x.svg", "x.svg") resolves.
|
|
277
|
-
function collectSvgAssets(deckDir) {
|
|
278
|
-
const assetsDir = path.join(deckDir, "assets");
|
|
316
|
+
function collectSvgAssets(deckDir, skill, brandUrl) {
|
|
279
317
|
const map = {};
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
entries = fs.readdirSync(assetsDir);
|
|
283
|
-
} catch {
|
|
284
|
-
return map;
|
|
285
|
-
}
|
|
286
|
-
for (const name of entries) {
|
|
287
|
-
if (!name.toLowerCase().endsWith(".svg")) continue;
|
|
288
|
-
let markup;
|
|
318
|
+
const read = (dir, keys) => {
|
|
319
|
+
let entries;
|
|
289
320
|
try {
|
|
290
|
-
|
|
321
|
+
entries = fs.readdirSync(dir);
|
|
291
322
|
} catch {
|
|
292
|
-
|
|
323
|
+
return;
|
|
293
324
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
325
|
+
for (const name of entries) {
|
|
326
|
+
if (!name.toLowerCase().endsWith(".svg")) continue;
|
|
327
|
+
let markup;
|
|
328
|
+
try {
|
|
329
|
+
markup = fs.readFileSync(path.join(dir, name), "utf8");
|
|
330
|
+
} catch {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
for (const key of keys(name)) map[key] = markup;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
// Brand first, deck second: a deck file of the same name overrides it.
|
|
337
|
+
read(skill?.brand?.assetsDir, (n) => [`${brandUrl}/${n}`, n]);
|
|
338
|
+
read(path.join(deckDir, "assets"), (n) => [`./assets/${n}`, `assets/${n}`, n]);
|
|
298
339
|
return map;
|
|
299
340
|
}
|
|
300
341
|
|
|
@@ -306,6 +347,23 @@ const HTML_TEMPLATE = fs
|
|
|
306
347
|
.readFileSync(path.join(__dirname, "preview.html"), "utf8")
|
|
307
348
|
.replace("__FAVICON_LINK__", () => FAVICON_LINK);
|
|
308
349
|
|
|
350
|
+
/** Next to presentation.html: the content hash it was built from. */
|
|
351
|
+
export const PREVIEW_SOURCE_FILE = "presentation.source.json";
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* The content hash presentation.html shows, when its stamp is the one written
|
|
355
|
+
* with it (a rebuild without a stamp, or a hand-copied preview, answers null).
|
|
356
|
+
*/
|
|
357
|
+
export function previewBuiltFrom(deckDir) {
|
|
358
|
+
try {
|
|
359
|
+
const stamp = JSON.parse(fs.readFileSync(previewPath(deckDir, PREVIEW_SOURCE_FILE), "utf8"));
|
|
360
|
+
const htmlAt = fs.statSync(previewPath(deckDir, "presentation.html")).mtimeMs;
|
|
361
|
+
return typeof stamp?.hash === "string" && Date.parse(stamp.builtAt) >= htmlAt - 1000 ? stamp.hash : null;
|
|
362
|
+
} catch {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
309
367
|
export async function buildDeck(deckDir, opts = {}) {
|
|
310
368
|
const skill = resolveSkillForDeck(deckDir);
|
|
311
369
|
const slidesDir = path.join(deckDir, "slides");
|
|
@@ -317,14 +375,14 @@ export async function buildDeck(deckDir, opts = {}) {
|
|
|
317
375
|
// inserts internally, but `slidemind build` should not create a lossy
|
|
318
376
|
// presentation.html artifact for them.
|
|
319
377
|
throw new Error(
|
|
320
|
-
`Deck ${
|
|
378
|
+
`Deck ${deckSlug(deckDir)} uses skill '${skill.skillName}' (mode ` +
|
|
321
379
|
`fill-template); it has no HTML preview. Run 'slidemind build-pptx ` +
|
|
322
|
-
`${
|
|
380
|
+
`${deckSlug(deckDir)}' to produce the editable .pptx.`,
|
|
323
381
|
);
|
|
324
382
|
}
|
|
325
|
-
const slideNames = sortSlideFilenames(
|
|
383
|
+
const slideNames = selectSlideFiles(sortSlideFilenames(
|
|
326
384
|
fs.readdirSync(slidesDir).filter((f) => f.endsWith(".jsx")),
|
|
327
|
-
);
|
|
385
|
+
), opts.only);
|
|
328
386
|
const slideFiles = slideNames.map((f) => path.join(slidesDir, f));
|
|
329
387
|
if (slideFiles.length === 0) {
|
|
330
388
|
throw new Error(`No .jsx files in ${slidesDir}`);
|
|
@@ -394,19 +452,21 @@ export async function buildDeck(deckDir, opts = {}) {
|
|
|
394
452
|
const chartPreview = await loadChartPreview(resolveBundleDirForDeck(deckDir));
|
|
395
453
|
const connectorPreview = await loadConnectorPreview(resolveBundleDirForDeck(deckDir));
|
|
396
454
|
|
|
397
|
-
|
|
455
|
+
const brandUrl = brandAssetsUrl(skill);
|
|
398
456
|
|
|
399
457
|
const layoutsBlock = layoutFiles
|
|
400
|
-
.map(inlineLayout)
|
|
458
|
+
.map((file) => inlineLayout(file, brandUrl))
|
|
401
459
|
.concat(convertedHostStubs(deckDir, conversions))
|
|
402
460
|
.join("\n");
|
|
461
|
+
const deckOwned = deckOwnedAssets(deckDir);
|
|
403
462
|
const slidesBlock = slideFiles
|
|
404
|
-
.map((file, i) => inlineSlide(file, i))
|
|
463
|
+
.map((file, i) => inlineSlide(file, i, brandUrl, deckOwned))
|
|
405
464
|
.join("\n");
|
|
406
465
|
|
|
407
|
-
const svgAssets = collectSvgAssets(deckDir);
|
|
466
|
+
const svgAssets = collectSvgAssets(deckDir, skill, brandUrl);
|
|
408
467
|
|
|
409
|
-
const html = HTML_TEMPLATE.replace("__TITLE__",
|
|
468
|
+
const html = HTML_TEMPLATE.replace("__TITLE__", deckSlug(deckDir))
|
|
469
|
+
.replace("__DESIGN_CSS__", () => designSystemCss(skill, brandUrl))
|
|
410
470
|
.replace("__BRAND_FONT_FACES__", brandFontFaces(deckDir, skill))
|
|
411
471
|
.replace("__LAYOUTS__", layoutsBlock)
|
|
412
472
|
.replace("__SLIDES__", slidesBlock)
|
|
@@ -427,8 +487,22 @@ export async function buildDeck(deckDir, opts = {}) {
|
|
|
427
487
|
connectorPreview ? ` <script>\n${safeInline(connectorPreview.source)}\n </script>` : "",
|
|
428
488
|
);
|
|
429
489
|
|
|
430
|
-
|
|
490
|
+
// Parallel lanes must not replace the full-deck review page, or each other's
|
|
491
|
+
// partial preview. Keep the same directory so relative asset URLs still work.
|
|
492
|
+
const suffix = opts.only == null ? "" : `-${crypto.createHash("sha256").update(JSON.stringify(slideNames)).digest("hex").slice(0, 16)}`;
|
|
493
|
+
const out = previewPath(deckDir, `presentation${suffix}.html`);
|
|
494
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
431
495
|
fs.writeFileSync(out, html);
|
|
496
|
+
// What this preview was built FROM: the review gate binds an approval to it, so
|
|
497
|
+
// an approval never covers slides changed, renamed or deleted after the build.
|
|
498
|
+
if (!suffix) {
|
|
499
|
+
try {
|
|
500
|
+
const { hashDeckContent } = await import("./review.mjs");
|
|
501
|
+
fs.writeFileSync(previewPath(deckDir, PREVIEW_SOURCE_FILE), JSON.stringify({ hash: `sha256:${hashDeckContent(deckDir)}`, builtAt: new Date().toISOString() }) + "\n");
|
|
502
|
+
} catch {
|
|
503
|
+
// no stamp: freshness falls back to file times
|
|
504
|
+
}
|
|
505
|
+
}
|
|
432
506
|
return { out, slideCount: slideFiles.length };
|
|
433
507
|
}
|
|
434
508
|
|
|
@@ -588,6 +662,16 @@ async function main() {
|
|
|
588
662
|
`Autofixed ${fixed.fixes} lint issue(s) in ${fixed.files} slide file(s) — re-read any file before patching it.\n`,
|
|
589
663
|
);
|
|
590
664
|
}
|
|
665
|
+
// Source someone else owns is withheld, never rewritten — and a withheld
|
|
666
|
+
// mutation is always SAID. `slidemind view-draft --fix` prints these from
|
|
667
|
+
// src/cli/deck.mjs; this dev script was the one path where the same guard
|
|
668
|
+
// ran silently. Duplicated on purpose: sharing the printer would mean
|
|
669
|
+
// reshaping a seam four production commands now depend on.
|
|
670
|
+
for (const w of fixed.withheld ?? []) {
|
|
671
|
+
process.stderr.write(
|
|
672
|
+
`[fix:withheld] ${w.file} — autofix (${w.fixes.join(", ")}) NOT applied: ${w.reason}.\n`,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
591
675
|
}
|
|
592
676
|
const { out, slideCount } = await buildDeck(deckDir);
|
|
593
677
|
const rel = path.relative(process.cwd(), out) || out;
|