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.
Files changed (186) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/AGENTS.md +591 -15
  4. package/README.md +202 -7
  5. package/bin/slidemind.mjs +50 -2
  6. package/package.json +2 -2
  7. package/skills/create-slides/SKILL.md +484 -886
  8. package/skills/search/SKILL.md +46 -23
  9. package/src/bridge/build-and-push.mjs +548 -73
  10. package/src/bridge/bundle.mjs +100 -31
  11. package/src/bridge/capture.mjs +14 -17
  12. package/src/bridge/client/brands.mjs +157 -18
  13. package/src/bridge/client/engine-routing.mjs +48 -13
  14. package/src/bridge/client/engine-search.mjs +24 -1
  15. package/src/bridge/client/engine-slides.mjs +1 -1
  16. package/src/bridge/client/render.mjs +27 -1
  17. package/src/bridge/client/transport.mjs +499 -23
  18. package/src/bridge/client.mjs +10 -0
  19. package/src/bridge/config.mjs +67 -6
  20. package/src/bridge/engine-download.mjs +0 -10
  21. package/src/bridge/graph-auth.mjs +55 -3
  22. package/src/bridge/image-intent-envelope.mjs +239 -0
  23. package/src/bridge/package-preview.mjs +67 -0
  24. package/src/bridge/sharepoint.mjs +914 -0
  25. package/src/builders/asset-fetch.mjs +203 -0
  26. package/src/builders/block-dividers.mjs +83 -0
  27. package/src/builders/build.mjs +171 -87
  28. package/src/builders/card-height.mjs +294 -0
  29. package/src/builders/chart-hole.mjs +183 -0
  30. package/src/builders/chart-jsx.mjs +410 -0
  31. package/src/builders/chart-labels.mjs +326 -0
  32. package/src/builders/concept-geometry.mjs +370 -0
  33. package/src/builders/concept-intent.mjs +200 -0
  34. package/src/builders/concept-layout.mjs +79 -0
  35. package/src/builders/concept-lines.mjs +509 -0
  36. package/src/builders/concept-lists.mjs +270 -0
  37. package/src/builders/concept-marks.mjs +84 -0
  38. package/src/builders/concept-relationships.mjs +12 -0
  39. package/src/builders/concept-spec.mjs +645 -0
  40. package/src/builders/concept-text.mjs +140 -0
  41. package/src/builders/concept-waves.mjs +57 -0
  42. package/src/builders/copy-fit.mjs +139 -0
  43. package/src/builders/design-tokens.mjs +347 -0
  44. package/src/builders/draft-check.mjs +552 -340
  45. package/src/builders/draft-lines.mjs +369 -0
  46. package/src/builders/edit-route.mjs +29 -0
  47. package/src/builders/figure-sketch.mjs +375 -0
  48. package/src/builders/fold-sets.mjs +639 -0
  49. package/src/builders/gate-server.mjs +9 -4
  50. package/src/builders/gate-session.js +273 -12
  51. package/src/builders/gate-shell.js +1494 -0
  52. package/src/builders/image-intent-check.mjs +308 -0
  53. package/src/builders/image-use-reconcile.mjs +107 -0
  54. package/src/builders/kit-helpers.mjs +121 -0
  55. package/src/builders/layout-names.mjs +32 -0
  56. package/src/builders/legacy-reference-uses.mjs +179 -0
  57. package/src/builders/line-fit.mjs +224 -0
  58. package/src/builders/line-native.mjs +895 -0
  59. package/src/builders/line-relations.mjs +230 -0
  60. package/src/builders/lint-fix.mjs +117 -9
  61. package/src/builders/lint.mjs +25 -20
  62. package/src/builders/list-jsx.mjs +88 -0
  63. package/src/builders/placement.mjs +133 -0
  64. package/src/builders/plan-checks.mjs +135 -0
  65. package/src/builders/plan-comments.mjs +19 -0
  66. package/src/builders/plan-handlers.mjs +95 -0
  67. package/src/builders/plan-markdown-edit.mjs +3 -3
  68. package/src/builders/plan-overlay.js +1530 -1608
  69. package/src/builders/plan.mjs +689 -186
  70. package/src/builders/preset-definitions.json +1 -0
  71. package/src/builders/preset-eval.mjs +244 -0
  72. package/src/builders/preview.html +51 -120
  73. package/src/builders/red-thread-backbone.mjs +135 -36
  74. package/src/builders/repair.mjs +205 -0
  75. package/src/builders/repeat-groups.mjs +540 -0
  76. package/src/builders/resolve-skill.mjs +5 -4
  77. package/src/builders/review-overlay.js +296 -293
  78. package/src/builders/review.mjs +305 -84
  79. package/src/builders/rule-shift.mjs +96 -0
  80. package/src/builders/seat.mjs +354 -0
  81. package/src/builders/settle-driver.mjs +236 -0
  82. package/src/builders/settle.mjs +1448 -0
  83. package/src/builders/slide-prep.mjs +67 -0
  84. package/src/builders/spec-assets.mjs +236 -0
  85. package/src/builders/spec-match.mjs +56 -0
  86. package/src/builders/stack.mjs +413 -0
  87. package/src/builders/storyboard-markdown.mjs +70 -27
  88. package/src/builders/storyboard-schema.mjs +54 -114
  89. package/src/builders/text-blocks.mjs +212 -0
  90. package/src/builders/text-card-fit.mjs +552 -0
  91. package/src/builders/text-stack.mjs +276 -0
  92. package/src/builders/transcribe.mjs +1069 -0
  93. package/src/cli/assets.mjs +164 -0
  94. package/src/cli/common.mjs +173 -12
  95. package/src/cli/concept-spec-support.mjs +76 -0
  96. package/src/cli/concepts.mjs +1002 -103
  97. package/src/cli/deck.mjs +401 -65
  98. package/src/cli/diagnose.mjs +4 -2
  99. package/src/cli/engine-helpers.mjs +17 -10
  100. package/src/cli/engine-results.mjs +76 -3
  101. package/src/cli/help.mjs +232 -28
  102. package/src/cli/install.mjs +9 -0
  103. package/src/cli/maintenance.mjs +104 -0
  104. package/src/cli/read.mjs +2 -1
  105. package/src/cli/ready.mjs +6 -3
  106. package/src/cli/record.mjs +522 -0
  107. package/src/cli/remote.mjs +818 -0
  108. package/src/cli/revision.mjs +686 -0
  109. package/src/cli/search.mjs +30 -11
  110. package/src/cli/session.mjs +56 -13
  111. package/src/cli/setup.mjs +46 -2
  112. package/src/cli/status.mjs +142 -33
  113. package/src/cli/tasks.mjs +246 -0
  114. package/src/cli/transcribe.mjs +854 -0
  115. package/src/cli/workspace-ui.mjs +260 -0
  116. package/src/cli/workspace.mjs +212 -34
  117. package/src/install-channel.mjs +64 -7
  118. package/src/lib/adopt.mjs +346 -25
  119. package/src/lib/concept-spec-binding.mjs +96 -0
  120. package/src/lib/conversions.mjs +70 -6
  121. package/src/lib/convert-folded.mjs +107 -0
  122. package/src/lib/deck-paths.mjs +418 -0
  123. package/src/lib/durable-ids.mjs +47 -0
  124. package/src/lib/edit-operations.mjs +316 -0
  125. package/src/lib/edit-state.mjs +1244 -0
  126. package/src/lib/edit-transitions.mjs +120 -0
  127. package/src/lib/extensions.mjs +501 -0
  128. package/src/lib/image-assets.mjs +465 -0
  129. package/src/lib/image-derivatives.mjs +234 -0
  130. package/src/lib/image-intent-fingerprint.mjs +70 -0
  131. package/src/lib/image-intent-tags.mjs +176 -0
  132. package/src/lib/image-intent.mjs +802 -0
  133. package/src/lib/image-use-overrides.mjs +279 -0
  134. package/src/lib/inline-components.mjs +613 -0
  135. package/src/lib/lint-converted.mjs +68 -4
  136. package/src/lib/merge-json.mjs +163 -0
  137. package/src/lib/overwrite-guard.mjs +3 -3
  138. package/src/lib/presentation-context.mjs +531 -0
  139. package/src/lib/preservation-tags.mjs +157 -0
  140. package/src/lib/preservation-verdict.mjs +1342 -0
  141. package/src/lib/publish.mjs +1557 -0
  142. package/src/lib/rebase.mjs +945 -0
  143. package/src/lib/reconcile-ack.mjs +4 -6
  144. package/src/lib/reconcile.mjs +62 -24
  145. package/src/lib/record-client.mjs +175 -0
  146. package/src/lib/record-state.mjs +151 -0
  147. package/src/lib/remote.mjs +661 -0
  148. package/src/lib/revision.mjs +1210 -0
  149. package/src/lib/slide-guids.mjs +4 -6
  150. package/src/lib/slide-preservation.mjs +423 -0
  151. package/src/lib/slide-selection.mjs +9 -0
  152. package/src/lib/workspace-transaction.mjs +117 -0
  153. package/src/pptx/content-digest.mjs +334 -0
  154. package/src/pptx/deck-assemble.mjs +1278 -0
  155. package/src/pptx/fill/engine.mjs +2 -3
  156. package/src/pptx/fill/freeform.mjs +6 -3
  157. package/src/pptx/ooxml.mjs +20 -0
  158. package/src/pptx/package-evidence.mjs +383 -0
  159. package/src/pptx/tags.mjs +163 -11
  160. package/src/update-check.mjs +22 -4
  161. package/src/workspace/adapters.mjs +222 -0
  162. package/src/workspace/auth.mjs +256 -0
  163. package/src/workspace/autostart.mjs +204 -0
  164. package/src/workspace/daemon.mjs +360 -0
  165. package/src/workspace/discovery.mjs +180 -0
  166. package/src/workspace/edit.mjs +167 -0
  167. package/src/workspace/events.mjs +52 -0
  168. package/src/workspace/gate-client.mjs +290 -0
  169. package/src/workspace/handback.mjs +73 -0
  170. package/src/workspace/lifecycle.mjs +635 -0
  171. package/src/workspace/operations.mjs +200 -0
  172. package/src/workspace/pick-folder.mjs +152 -0
  173. package/src/workspace/registry.mjs +359 -0
  174. package/src/workspace/remote-links.mjs +397 -0
  175. package/src/workspace/remote-status.mjs +359 -0
  176. package/src/workspace/routes.mjs +1331 -0
  177. package/src/workspace/server.mjs +159 -0
  178. package/src/workspace/sync.mjs +1119 -0
  179. package/src/workspace/task-actions.mjs +215 -0
  180. package/src/workspace/tasks.mjs +507 -0
  181. package/src/workspace/ui/app.js +1886 -0
  182. package/src/workspace/ui/index.html +141 -0
  183. package/src/workspace/ui/plan-bridge.js +120 -0
  184. package/src/workspace/ui/sharepoint.svg +59 -0
  185. package/src/workspace/ui/styles.css +423 -0
  186. package/src/builders/visual-families.mjs +0 -46
@@ -0,0 +1,639 @@
1
+ // A declared set, written as one component. The reader names the organisms a
2
+ // concept repeats (`spec.repeats`, concept-repeats.ts) and repeat-groups.mjs
3
+ // builds each from one template — one size, one inner layout. The transcriber
4
+ // still writes every member out in full; this pass folds a BUILT set into
5
+ //
6
+ // const Repeat1 = ({ x, y, id, textId, text }) => (<>
7
+ // <B id={id} x={x} y={y} w={248} h={112} fillVar="var(--bg-2)" shape="rounded" />
8
+ // <T id={textId} x={x + 72} y={y + 16} w={160} role="body">{text}</T>
9
+ // </>);
10
+ // <Repeat1 x={0} y={0} id="c1" textId="t1" text={<>Plan</>} />
11
+ //
12
+ // so the design agent changes the set's look once and a member's words at its
13
+ // call site (workflow/authoring.md → Repeated organisms are components).
14
+ //
15
+ // Exact or not at all. A set folds only when
16
+ // - every member draws the same parts (same helpers, same order),
17
+ // - each part sits at one offset from its member's origin in every member,
18
+ // or at an offset its call site passes (a taller text lowers its stack;
19
+ // parts that move together share one prop),
20
+ // - a drawn figure is the first member's, moved to the member's place
21
+ // (or, drawn differently, its member's own at the call site),
22
+ // - instantiating the template gives back each member's own lines, and
23
+ // - no element painted between a member's first and last part overlaps one
24
+ // of its parts (an instance paints its parts together, so that element
25
+ // would change sides).
26
+ // Anything else stays as drawn, with the set's comment. Lines stay outside the
27
+ // component as static directives; ids stay the spec's, so settle, connectors,
28
+ // assets and the preservation reader (src/lib/inline-components.mjs) keep
29
+ // addressing every part.
30
+ //
31
+ // Pure. `SLIDEMIND_ABLATE=foldSets` turns it off for A/B runs.
32
+ import { correspond, repeatGroups } from "./repeat-groups.mjs";
33
+
34
+ const ablated = () => (process.env.SLIDEMIND_ABLATE ?? "").split(",").includes("foldSets");
35
+ const INDENT = " ";
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // One emitted line: `<Tag a="v" b={…} flag>children</Tag>` or `<Tag … />`.
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /** End (exclusive) of the braced value opening at `i`. */
42
+ function braceEnd(s, i) {
43
+ let depth = 0;
44
+ for (let k = i; k < s.length; k++) {
45
+ const c = s[k];
46
+ if (c === '"' || c === "'" || c === "`") {
47
+ for (k++; k < s.length && s[k] !== c; k++) if (s[k] === "\\") k++;
48
+ continue;
49
+ }
50
+ if (c === "{") depth++;
51
+ else if (c === "}" && --depth === 0) return k + 1;
52
+ }
53
+ return -1;
54
+ }
55
+
56
+ /** A bare line directive (not a group of them, whose children sit in its frame). */
57
+ const BARE_LINE = /^\s*<div (?:data-connector|data-sm-line)\b(?!-group)/;
58
+
59
+ /**
60
+ * `{ tag, attrs: [{ name, raw }], children, selfClose }`, or null for anything
61
+ * else. A bare line directive is no part: its ends are static by contract
62
+ * (lint-connectors.ts), so it stays outside the component.
63
+ */
64
+ export function tokenizeLine(line) {
65
+ const s = line.trim();
66
+ const head = /^<([A-Za-z][\w]*)(?=[\s/>])/.exec(s);
67
+ if (!head || BARE_LINE.test(s)) return null;
68
+ const tag = head[1];
69
+ const attrs = [];
70
+ let k = head[0].length;
71
+ for (;;) {
72
+ while (s[k] === " ") k++;
73
+ if (s.startsWith("/>", k)) {
74
+ return k + 2 === s.length ? { tag, attrs, children: null, selfClose: true } : null;
75
+ }
76
+ if (s[k] === ">") {
77
+ k++;
78
+ break;
79
+ }
80
+ const name = /^[A-Za-z_][\w-]*/.exec(s.slice(k))?.[0];
81
+ if (!name) return null;
82
+ k += name.length;
83
+ if (s[k] !== "=") {
84
+ attrs.push({ name, raw: null });
85
+ continue;
86
+ }
87
+ k++;
88
+ let end;
89
+ if (s[k] === '"') end = s.indexOf('"', k + 1) + 1;
90
+ else if (s[k] === "{") end = braceEnd(s, k);
91
+ else return null;
92
+ if (end <= k) return null;
93
+ attrs.push({ name, raw: s.slice(k, end) });
94
+ k = end;
95
+ }
96
+ const close = `</${tag}>`;
97
+ if (!s.endsWith(close)) return null;
98
+ return { tag, attrs, children: s.slice(k, s.length - close.length), selfClose: false };
99
+ }
100
+
101
+ function renderLine(tag, attrs, children, selfClose) {
102
+ const head = [tag, ...attrs.map(({ name, raw }) => (raw === null ? name : `${name}=${raw}`))].join(" ");
103
+ return selfClose ? `<${head} />` : `<${head}>${children}</${tag}>`;
104
+ }
105
+
106
+ /** `<X …></X>` and `<X … />` draw the same element. */
107
+ const sameElement = (a, b) => a.replace(/><\/[A-Za-z]\w*>$/, " />") === b.replace(/><\/[A-Za-z]\w*>$/, " />");
108
+
109
+ const numberOf = (raw) => (raw && /^\{-?\d+(?:\.\d+)?(?:e[+-]?\d+)?\}$/.test(raw) ? Number(raw.slice(1, -1)) : null);
110
+ const offsetExpr = (axis, d) => (d === 0 ? axis : d > 0 ? `${axis} + ${d}` : `${axis} - ${-d}`);
111
+ const camel = (s) => String(s).replace(/[^A-Za-z0-9]+(.)?/g, (_, c) => (c ? c.toUpperCase() : "")).replace(/^[A-Z]/, (c) => c.toLowerCase());
112
+ const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
113
+
114
+ /**
115
+ * Every member's attribute names in one order that keeps each member's own:
116
+ * a name only some members carry (`nowrap` on one text) goes right after the
117
+ * name it follows there, so the template gives that member back as written.
118
+ */
119
+ function attrOrder(lists) {
120
+ const out = [];
121
+ for (const attrs of lists) {
122
+ attrs.forEach((a, i) => {
123
+ if (!out.includes(a.name)) out.splice(i ? out.indexOf(attrs[i - 1].name) + 1 : 0, 0, a.name);
124
+ });
125
+ }
126
+ return out;
127
+ }
128
+
129
+ /** A difference without float noise (`75.7`, not `75.69999999999999`). */
130
+ const tidy = (n) => Number(n.toFixed(4));
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // A drawn figure's geometry: `<Figure circles={…} outline={…} mirror={…}
134
+ // holes={…}>`, object literals as figure-sketch.mjs figureJsxAttrs writes them.
135
+ // In a template every coordinate is written from the member's x/y
136
+ // (`{x: x + 182.5,y: y + 35}`); the inliner computes it back to the literal.
137
+ // ---------------------------------------------------------------------------
138
+
139
+ const GEOMETRY = new Set(["circles", "outline", "mirror", "holes"]);
140
+ const lit = (v) => JSON.stringify(v).replace(/"([A-Za-z_][A-Za-z0-9_]*)":/g, "$1: ");
141
+
142
+ /** An attribute's object literal, or undefined when it is not one. */
143
+ function literalOf(raw) {
144
+ if (!raw?.startsWith("{") || !raw.endsWith("}")) return undefined;
145
+ try {
146
+ return JSON.parse(raw.slice(1, -1).replace(/([{,[]\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:/g, '$1"$2":'));
147
+ } catch {
148
+ return undefined;
149
+ }
150
+ }
151
+
152
+ /** Every leaf of a geometry attribute in order: `{ path, value, axis }` (axis null: a radius, an angle, a flag). */
153
+ function geometryLeaves(attr, root) {
154
+ const out = [];
155
+ const axisOf = (path) => {
156
+ const last = path[path.length - 1];
157
+ if (attr === "outline" || attr === "holes") return last === "x" || last === "y" ? last : null;
158
+ if (attr === "circles") return last === 0 ? "x" : last === 1 ? "y" : null;
159
+ if (attr === "mirror" && path[0] === "through") return last === 0 ? "x" : last === 1 ? "y" : null;
160
+ if (attr === "mirror" && path[0] === "at") return root.axis === "vertical" ? "x" : root.axis === "horizontal" ? "y" : null;
161
+ return null;
162
+ };
163
+ const walk = (v, path) => {
164
+ if (v && typeof v === "object") for (const [key, child] of Object.entries(v)) walk(child, [...path, Array.isArray(v) ? Number(key) : key]);
165
+ else out.push({ path, value: v, axis: typeof v === "number" ? axisOf(path) : null });
166
+ };
167
+ walk(root, []);
168
+ return out;
169
+ }
170
+
171
+ /** A figure part's place: its outline's first point. */
172
+ function figurePlace(t) {
173
+ const p = literalOf(t.attrs.find((a) => a.name === "outline")?.raw)?.[0];
174
+ return { x: Number.isFinite(p?.x) ? p.x : null, y: Number.isFinite(p?.y) ? p.y : null };
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // Geometry for the paint-order guard
179
+ // ---------------------------------------------------------------------------
180
+
181
+ function rectIndex(spec, measured) {
182
+ const out = new Map();
183
+ for (const e of spec.elements ?? []) out.set(e.id, { x: e.x, y: e.y, w: e.w, h: e.h });
184
+ for (const c of spec.connectors ?? []) {
185
+ const pts = (c.points ?? c.path?.points ?? []).map((p) => (Array.isArray(p) ? { x: p[0], y: p[1] } : p)).filter((p) => Number.isFinite(p?.x));
186
+ if (c.id && pts.length) {
187
+ const x = Math.min(...pts.map((p) => p.x));
188
+ const y = Math.min(...pts.map((p) => p.y));
189
+ out.set(c.id, { x, y, w: Math.max(...pts.map((p) => p.x)) - x, h: Math.max(...pts.map((p) => p.y)) - y });
190
+ }
191
+ }
192
+ for (const [id, m] of measured ?? []) if (Number.isFinite(m?.x)) out.set(id, { x: m.x, y: m.y, w: m.w, h: m.h });
193
+ return out;
194
+ }
195
+
196
+ const overlaps = (a, b, pad) =>
197
+ a.x - pad < b.x + b.w && b.x - pad < a.x + a.w && a.y - pad < b.y + b.h && b.y - pad < a.y + a.h;
198
+
199
+ /** The point ends of a line directive's own attributes, as a box. */
200
+ function directiveBox(line) {
201
+ const pts = [...line.matchAll(/data-(?:from|to)="(-?[\d.]+),(-?[\d.]+)"/g)].map((m) => ({ x: Number(m[1]), y: Number(m[2]) }));
202
+ if (pts.length < 2) return null;
203
+ const x = Math.min(...pts.map((p) => p.x));
204
+ const y = Math.min(...pts.map((p) => p.y));
205
+ return { x, y, w: Math.max(...pts.map((p) => p.x)) - x, h: Math.max(...pts.map((p) => p.y)) - y };
206
+ }
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // One set, from a plan: `{ id, comment, members: [{ lines, ids }], prefixes,
210
+ // names? }` — per member the line of each template part and the id it owns
211
+ // (part 0 first), per part the prefix its props are named by, and (a refold)
212
+ // the prop names the set already had.
213
+ // ---------------------------------------------------------------------------
214
+
215
+ /** A part's box: measured, else the spec's, else its own literal x/y/w/h. */
216
+ function boxOf(id, line, rects) {
217
+ const known = rects.get(id);
218
+ if (known) return known;
219
+ const t = tokenizeLine(line);
220
+ const n = (name) => numberOf(t?.attrs.find((a) => a.name === name)?.raw);
221
+ const [x, y, w, h] = ["x", "y", "w", "h"].map(n);
222
+ return [x, y, w, h].every((v) => v !== null) ? { x, y, w, h } : null;
223
+ }
224
+
225
+ /**
226
+ * The plan with its parts in paint order — the template paints them in one run,
227
+ * so it must paint them in the order the slide did — or a reason when members
228
+ * disagree about that order.
229
+ */
230
+ function inPaintOrder(plan) {
231
+ const first = plan.members[0].lines;
232
+ const order = first.map((_, j) => j).sort((a, b) => first[a] - first[b]);
233
+ for (const m of plan.members) {
234
+ const lines = order.map((j) => m.lines[j]);
235
+ if (lines.some((v, i) => i > 0 && v < lines[i - 1])) return { why: "its members paint their parts in different orders" };
236
+ }
237
+ const moved = new Map(order.map((j, k) => [j, k]));
238
+ const names = plan.names && new Map([...plan.names].map(([key, v]) => [key.replace(/^\d+/, (j) => String(moved.get(Number(j)))), v]));
239
+ return {
240
+ ...plan,
241
+ names,
242
+ prefixes: order.map((j) => plan.prefixes[j]),
243
+ members: plan.members.map((m) => ({ ...m, lines: order.map((j) => m.lines[j]), ids: order.map((j) => m.ids[j]) })),
244
+ };
245
+ }
246
+
247
+ function foldPlan(lines, given, n, rects) {
248
+ if (!given.members.length || given.members.some((m) => m.lines.length !== given.members[0].lines.length)) return { why: "its members draw different parts" };
249
+ const plan = inPaintOrder(given);
250
+ if (plan.why) return plan;
251
+ const members = plan.members;
252
+ const count = members[0].lines.length;
253
+ if (!count) return { why: "its members draw different parts" };
254
+ const tokens = members.map((m) => m.lines.map((i) => tokenizeLine(lines[i])));
255
+ if (tokens.some((list) => list.some((t) => !t))) return { why: "a part is not a helper line" };
256
+ for (let j = 0; j < count; j++) {
257
+ if (new Set(tokens.map((list) => list[j].tag)).size > 1) return { why: "its members draw different parts" };
258
+ }
259
+ // Where each part is drawn: its x/y, a figure its outline's first point.
260
+ const places = tokens.map((list) =>
261
+ list.map((t) => {
262
+ if (t.tag === "Figure") return figurePlace(t);
263
+ const at = (name) => (t.attrs.some((a) => a.name === name) ? numberOf(t.attrs.find((a) => a.name === name).raw) ?? NaN : null);
264
+ return { x: at("x"), y: at("y") };
265
+ }),
266
+ );
267
+ // A member's origin is where its first part is drawn.
268
+ const origins = places.map((list) => list[0]);
269
+ if (origins.some((o) => !Number.isFinite(o.x) || !Number.isFinite(o.y))) return { why: "its first part has no literal place" };
270
+
271
+ const usedNames = new Set(["x", "y"]);
272
+ const unique = (name) => {
273
+ let out = name;
274
+ for (let k = 2; usedNames.has(out); k++) out = `${name}${k}`;
275
+ usedNames.add(out);
276
+ return out;
277
+ };
278
+ const named = (j, attr, fallback) => unique(plan.names?.get(`${j}:${attr}`) ?? fallback);
279
+ const props = []; // [{ name, part, attr | "children" | offset axis, values? }]
280
+
281
+ // Each part's place from its member's origin, per axis: one offset every
282
+ // member shares, or — where a member's content set it apart (a taller text
283
+ // lowers its stack) — an offset prop at the call site, one per group of
284
+ // parts that move together.
285
+ const offsets = places[0].map(() => ({})); // [j][axis] = { prop, values, c }
286
+ for (const axis of ["x", "y"]) {
287
+ const leaders = new Map(); // delta key -> leader part
288
+ for (let j = 0; j < count; j++) {
289
+ const v = places.map((list) => list[j][axis]);
290
+ if (v.every((n) => n === null)) continue;
291
+ if (v.some((n) => !Number.isFinite(n))) return { why: `a part has no literal ${axis}` };
292
+ const d = v.map((n, k) => n - origins[k][axis]);
293
+ const shared = [tidy(d[0]), d[0]].find((c) => v.every((n, k) => origins[k][axis] + c === n));
294
+ if (shared !== undefined) {
295
+ offsets[j][axis] = { prop: null, c: shared };
296
+ continue;
297
+ }
298
+ const key = d.map((n) => tidy(n - d[0])).join(",");
299
+ if (!leaders.has(key)) {
300
+ const values = d.map(tidy);
301
+ const prop = named(j, `@${axis}`, `${plan.prefixes[j] || "part"}${axis.toUpperCase()}`);
302
+ props.push({ name: prop, part: j, offset: axis, values });
303
+ leaders.set(key, { values, prop });
304
+ }
305
+ const { values, prop } = leaders.get(key);
306
+ const c = tidy(d[0] - values[0]);
307
+ if (v.some((n, k) => origins[k][axis] + values[k] + c !== n)) return { why: "its members' inner layouts differ" };
308
+ offsets[j][axis] = { prop, values, c };
309
+ }
310
+ }
311
+ /** The expression for a coordinate `c` past part j's place on `axis`, and its value for member k. */
312
+ const coord = (j, axis, c) => {
313
+ const o = offsets[j][axis];
314
+ if (!o.prop) return { text: offsetExpr(axis, c), at: (k) => origins[k][axis] + c };
315
+ const tail = c === 0 ? "" : c > 0 ? ` + ${c}` : ` - ${-c}`;
316
+ return { text: `${axis} + ${o.prop}${tail}`, at: (k) => (c === 0 ? origins[k][axis] + o.values[k] : origins[k][axis] + o.values[k] + c) };
317
+ };
318
+
319
+ /** A figure attribute as the first member's, moved to each member's place (template text); null when it is not that. */
320
+ const movedFigure = (j, name, values) => {
321
+ const roots = values.map((a) => literalOf(a?.raw));
322
+ if (roots.some((r, k) => r === undefined || values[k].raw !== `{${lit(r)}}`)) return null;
323
+ const leaves = roots.map((r) => geometryLeaves(name, r));
324
+ if (leaves.some((l) => l.length !== leaves[0].length)) return null;
325
+ const root = structuredClone(roots[0]);
326
+ for (const [i, leaf] of leaves[0].entries()) {
327
+ if (leaves.some((l) => JSON.stringify(l[i].path) !== JSON.stringify(leaf.path))) return null;
328
+ if (!leaf.axis) {
329
+ if (leaves.some((l) => l[i].value !== leaf.value)) return null;
330
+ continue;
331
+ }
332
+ const o = offsets[j][leaf.axis];
333
+ if (!o) return null;
334
+ const e = coord(j, leaf.axis, tidy(leaf.value - origins[0][leaf.axis] - (o.prop ? o.values[0] : 0)));
335
+ if (leaves.some((l, k) => e.at(k) !== l[i].value)) return null;
336
+ let at = root;
337
+ for (const step of leaf.path.slice(0, -1)) at = at[step];
338
+ at[leaf.path.at(-1)] = `⟦${e.text}⟧`;
339
+ }
340
+ return `{${lit(root).replace(/"⟦([^⟧]*)⟧"/g, "$1")}}`;
341
+ };
342
+
343
+ // Template: per part, per attribute — literal when every member agrees.
344
+ const template = [];
345
+ for (let j = 0; j < count; j++) {
346
+ const prefix = plan.prefixes[j];
347
+ const names = attrOrder(tokens.map((list) => list[j].attrs));
348
+ const attrs = [];
349
+ for (const name of names) {
350
+ const values = tokens.map((list) => list[j].attrs.find((a) => a.name === name));
351
+ if ((name === "x" || name === "y") && tokens[0][j].tag !== "Figure") {
352
+ attrs.push({ name, raw: `{${coord(j, name, offsets[j][name].c).text}}` });
353
+ continue;
354
+ }
355
+ // A figure every member repeats at its own place is the first one's,
356
+ // written from the member's x/y; one drawn differently is its member's.
357
+ const drawn = tokens[0][j].tag === "Figure" && GEOMETRY.has(name) ? movedFigure(j, name, values) : null;
358
+ if (drawn) {
359
+ attrs.push({ name, raw: drawn });
360
+ continue;
361
+ }
362
+ const same = name !== "id" && values.every((a) => a && a.raw === values[0].raw);
363
+ if (same) {
364
+ attrs.push({ name, raw: values[0].raw });
365
+ continue;
366
+ }
367
+ const prop = named(j, name, name === "id" ? (prefix ? `${prefix}Id` : "id") : prefix ? `${prefix}${cap(camel(name))}` : camel(name));
368
+ props.push({ name: prop, part: j, attr: name });
369
+ attrs.push({ name, raw: `{${prop}}` });
370
+ }
371
+ const contents = tokens.map((list) => list[j].children ?? "");
372
+ let children = contents[0];
373
+ // A seated helper (`<I …/>` on its disc) is templated too, so its size
374
+ // and look are the set's; otherwise differing words ride in a slot. A
375
+ // chart rides whole in its member's slot: its data and styles are its own
376
+ // and stay literals at the call site (lint: `<Chart>` props must be
377
+ // literals — x113 QBR 1's eight KPI cards lifted `data`/`labels`/
378
+ // `seriesStyle` into props and every draft was refused).
379
+ const inner = contents.map((c) => (c.startsWith("<") ? tokenizeLine(c) : null));
380
+ if (contents.some((c) => c !== contents[0]) && inner.every((t) => t?.selfClose && t.tag === inner[0].tag && t.tag !== "Chart")) {
381
+ const idRaw = inner[0].attrs.find((a) => a.name === "id")?.raw;
382
+ const innerPrefix = plan.prefixFor?.(/^"(.*)"$/.exec(idRaw ?? "")?.[1]) ?? `${prefix || "inner"}Inner`;
383
+ const innerNames = attrOrder(inner.map((t) => t.attrs));
384
+ const innerAttrs = [];
385
+ for (const name of innerNames) {
386
+ const values = inner.map((t) => t.attrs.find((a) => a.name === name));
387
+ if (name !== "id" && values.every((a) => a && a.raw === values[0].raw)) {
388
+ innerAttrs.push({ name, raw: values[0].raw });
389
+ continue;
390
+ }
391
+ const prop = named(j, `>${name}`, name === "id" ? `${innerPrefix}Id` : `${innerPrefix}${cap(camel(name))}`);
392
+ props.push({ name: prop, part: j, attr: name, inner: true });
393
+ innerAttrs.push({ name, raw: `{${prop}}` });
394
+ }
395
+ children = renderLine(inner[0].tag, innerAttrs, null, true);
396
+ } else if (contents.some((c) => c !== contents[0])) {
397
+ const text = tokens[0][j].tag === "T" || tokens[0][j].tag === "L";
398
+ const prop = named(j, "children", prefix ? `${prefix}${text ? "Text" : "Content"}` : text ? "text" : "content");
399
+ props.push({ name: prop, part: j, attr: "children" });
400
+ children = `{${prop}}`;
401
+ }
402
+ const selfClose = tokens.every((list) => list[j].selfClose) && !children;
403
+ template.push(renderLine(tokens[0][j].tag, attrs, children, selfClose));
404
+ }
405
+
406
+ // An offset prop only a drawing used that became its member's own is unused.
407
+ for (let i = props.length - 1; i >= 0; i--) {
408
+ if (props[i].offset && !template.some((t) => new RegExp(`\\b${props[i].name}\\b`).test(t))) props.splice(i, 1);
409
+ }
410
+
411
+ // Self-check: the template, filled with each member's own values, gives
412
+ // back that member's lines.
413
+ for (const [k, list] of tokens.entries()) {
414
+ for (let j = 0; j < count; j++) {
415
+ const own = list[j];
416
+ const attrs = [];
417
+ for (const a of tokenizeLine(template[j]).attrs) {
418
+ if ((a.name === "x" || a.name === "y") && own.tag !== "Figure") {
419
+ attrs.push({ name: a.name, raw: `{${coord(j, a.name, offsets[j][a.name].c).at(k)}}` });
420
+ continue;
421
+ }
422
+ // A figure's geometry was checked point by point against every member.
423
+ if (own.tag === "Figure" && GEOMETRY.has(a.name)) {
424
+ const value = own.attrs.find((o) => o.name === a.name);
425
+ if (value) attrs.push(value);
426
+ continue;
427
+ }
428
+ if (!props.some((p) => p.part === j && !p.inner && p.attr === a.name)) attrs.push(a);
429
+ else {
430
+ const value = own.attrs.find((o) => o.name === a.name);
431
+ if (value) attrs.push(value);
432
+ }
433
+ }
434
+ let children = own.children ?? "";
435
+ if (props.some((p) => p.part === j && p.inner)) {
436
+ const ownInner = tokenizeLine(children);
437
+ const innerAttrs = [];
438
+ for (const a of tokenizeLine(tokenizeLine(template[j]).children).attrs) {
439
+ if (!props.some((p) => p.part === j && p.inner && p.attr === a.name)) innerAttrs.push(a);
440
+ else {
441
+ const value = ownInner?.attrs.find((o) => o.name === a.name);
442
+ if (value) innerAttrs.push(value);
443
+ }
444
+ }
445
+ children = renderLine(ownInner?.tag ?? "", innerAttrs, null, true);
446
+ }
447
+ const rebuilt = renderLine(own.tag, attrs, children, own.selfClose);
448
+ if (!sameElement(rebuilt, lines[members[k].lines[j]].trim())) return { why: "the template does not give back a member" };
449
+ }
450
+ }
451
+
452
+ // Paint order: an instance paints its parts together, where its first one
453
+ // was. A part painted AFTER some element now paints before it, so the two
454
+ // must not overlap; parts painted before it keep their side.
455
+ for (const m of members) {
456
+ const lo = Math.min(...m.lines);
457
+ const hi = Math.max(...m.lines);
458
+ const own = new Set(m.lines);
459
+ for (let i = lo + 1; i < hi; i++) {
460
+ // Another member's part counts too: it moves to its own call site.
461
+ if (own.has(i) || !/^\s*</.test(lines[i])) continue;
462
+ const later = m.lines.flatMap((at, j) => (at > i ? [boxOf(m.ids[j], lines[at], rects)] : []));
463
+ const ids = [...lines[i].matchAll(/\bid="([^"]+)"/g)].map((x) => x[1]);
464
+ // A free line's ends are literals on its own line: the source's truth,
465
+ // even after a move no render has measured yet.
466
+ const box = (BARE_LINE.test(lines[i]) ? directiveBox(lines[i]) : null) ?? ids.map((id) => boxOf(id, lines[i], rects)).find(Boolean);
467
+ if (!box || later.some((b) => !b)) return { why: "an element painted between its parts has no known place" };
468
+ const pad = /\bshadow\b/.test(lines[i]) || m.lines.some((x) => x > i && /\bshadow\b/.test(lines[x])) ? 16 : 2;
469
+ if (later.some((b) => overlaps(b, box, pad))) return { why: "an element painted between its parts overlaps it" };
470
+ }
471
+ }
472
+
473
+ // The call sites, in the order members are declared.
474
+ const name = `Repeat${n}`;
475
+ const calls = tokens.map((list, k) => {
476
+ const attrs = [`x={${origins[k].x}}`, `y={${origins[k].y}}`];
477
+ for (const p of props) {
478
+ if (p.offset) {
479
+ attrs.push(`${p.name}={${p.values[k]}}`);
480
+ continue;
481
+ }
482
+ const own = p.inner ? tokenizeLine(list[p.part].children) : list[p.part];
483
+ if (p.attr === "children" && !p.inner) {
484
+ // Plain words travel as a string; markup, entities and quotes as a fragment.
485
+ if (own.children) attrs.push(/[<>{}&"\n]/.test(own.children) ? `${p.name}={<>${own.children}</>}` : `${p.name}="${own.children}"`);
486
+ continue;
487
+ }
488
+ const value = own.attrs.find((a) => a.name === p.attr);
489
+ if (!value) continue;
490
+ attrs.push(value.raw === null ? p.name : `${p.name}=${value.raw}`);
491
+ }
492
+ return `${INDENT}<${name} ${attrs.join(" ")} />`;
493
+ });
494
+ const params = ["x", "y", ...props.map((p) => p.name)].join(", ");
495
+ const declaration = [plan.comment, ` const ${name} = ({ ${params} }) => (<>`, ...template.map((t) => ` ${t}`), " </>);"];
496
+ return { members, calls, declaration };
497
+ }
498
+
499
+ /** Index every element line by the ids it carries. */
500
+ function linesById(lines) {
501
+ const lineOf = new Map();
502
+ lines.forEach((line, i) => {
503
+ if (!line.startsWith(`${INDENT}<`)) return;
504
+ for (const m of line.matchAll(/\bid="([^"]+)"/g)) if (!lineOf.has(m[1])) lineOf.set(m[1], i);
505
+ });
506
+ return lineOf;
507
+ }
508
+
509
+ /** Fold the plans that hold; everything else stays as written. */
510
+ function applyPlans(src, plans, rects, dropComment) {
511
+ const lines = String(src).split("\n");
512
+ const kept = [];
513
+ const folds = [];
514
+ // A line belongs to ONE component. A set drawn inside another set's members
515
+ // (the rows of each card, when the cards themselves fold) would claim lines
516
+ // the outer fold already collapsed into its call sites — and lose them (x101
517
+ // PM 2: twelve row texts gone). It stays flat, with its comment.
518
+ const claimed = new Set();
519
+ for (const plan of plans) {
520
+ if (plan.why) {
521
+ kept.push({ id: plan.id, why: plan.why });
522
+ continue;
523
+ }
524
+ if (plan.members.some((m) => m.lines.some((i) => claimed.has(i)))) {
525
+ kept.push({ id: plan.id, why: "its parts are drawn inside another set's component" });
526
+ continue;
527
+ }
528
+ const result = foldPlan(lines, plan, folds.length + 1, rects);
529
+ if (result.why) kept.push({ id: plan.id, why: result.why });
530
+ else {
531
+ folds.push({ plan, ...result });
532
+ for (const m of result.members) for (const i of m.lines) claimed.add(i);
533
+ }
534
+ }
535
+ if (!folds.length) return { src, folded: [], kept };
536
+ // Each member's parts collapse into its call site, at its first part.
537
+ const replaced = new Map();
538
+ const dropped = new Set();
539
+ for (const fold of folds) {
540
+ fold.members.forEach((m, k) => {
541
+ const first = Math.min(...m.lines);
542
+ replaced.set(first, fold.calls[k]);
543
+ for (const i of m.lines) if (i !== first) dropped.add(i);
544
+ });
545
+ }
546
+ const body = [];
547
+ lines.forEach((line, i) => {
548
+ if (dropped.has(i) || dropComment(line, folds)) return;
549
+ body.push(replaced.get(i) ?? line);
550
+ });
551
+ const at = body.findIndex((line) => line === " return (");
552
+ if (at < 0) return { src, folded: [], kept: [...kept, ...folds.map((f) => ({ id: f.plan.id, why: "the slide has no return to declare it before" }))] };
553
+ body.splice(at, 0, ...folds.flatMap((f) => f.declaration));
554
+ return { src: body.join("\n"), folded: folds.map((f) => f.plan.id), kept };
555
+ }
556
+
557
+ /**
558
+ * Fold every built set of a transcribed slide into a local component.
559
+ * @returns {{ src: string, folded: string[], kept: Array<{ id: string, why: string }> }}
560
+ */
561
+ export function foldSets(src, spec, measured = null) {
562
+ const groups = repeatGroups(spec ?? {}).filter((g) => g.built);
563
+ if (ablated() || !groups.length) return { src, folded: [], kept: [] };
564
+ const lineOf = linesById(String(src).split("\n"));
565
+ const elements = spec.elements ?? [];
566
+ const plans = groups.map((group) => {
567
+ const kids = correspond(elements, group.members);
568
+ const members = group.members.map((m, i) => {
569
+ const lines = [];
570
+ const owners = [];
571
+ for (const e of [...(m.virtual ? [] : [m]), ...(kids?.[i] ?? [])]) {
572
+ const at = lineOf.get(e.id);
573
+ if (at === undefined || lines.includes(at)) continue;
574
+ lines.push(at);
575
+ owners.push(e);
576
+ }
577
+ return { lines, ids: owners.map((e) => e.id), owners };
578
+ });
579
+ const prefixes = members[0].owners.map((e, j) => (j === 0 && !group.members[0].virtual ? "" : camel(e.role ?? e.kind ?? `part${j}`)));
580
+ const prefixFor = (id) => {
581
+ const e = elements.find((x) => x.id === id);
582
+ return e ? camel(e.role ?? e.kind ?? "inner") : null;
583
+ };
584
+ const comment = ` // ${group.id}: ${group.members.map((m) => m.id).join(", ")} are one organism (${group.arrangement}${group.hub ? ` around ${group.hub.id}` : ""}) — its look is drawn once here; each member is a call site below.`;
585
+ return { id: group.id, comment, members, prefixes, prefixFor };
586
+ });
587
+ const ids = groups.map((g) => g.id);
588
+ const setComment = new RegExp(`^\\s*\\{/\\* (${ids.map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")}):`);
589
+ return applyPlans(src, plans, rectIndex(spec, measured), (line, folds) =>
590
+ setComment.test(line) && folds.some((f) => line.includes(`{/* ${f.plan.id}:`)),
591
+ );
592
+ }
593
+
594
+ /**
595
+ * Fold a flat source the way `folded` was folded: its `Repeat<n>` components
596
+ * name the sets, their call sites the members and part ids, their templates
597
+ * the prop names. For a source that was inlined to be rewritten line by line
598
+ * (the bounded repair) and must go back to its components where that is still
599
+ * exact. `flat` is assumed to carry no declarations of its own.
600
+ */
601
+ export function refoldLike(folded, flat, measured = null) {
602
+ if (ablated()) return { src: flat, folded: [], kept: [] };
603
+ const srcLines = String(folded).split("\n");
604
+ const lineOf = linesById(String(flat).split("\n"));
605
+ const plans = [];
606
+ for (let i = 0; i < srcLines.length; i++) {
607
+ const head = /^ {2}const (Repeat\d+) = \(\{ [^}]* \}\) => \(<>$/.exec(srcLines[i]);
608
+ if (!head) continue;
609
+ const comment = /^ {2}\/\/ /.test(srcLines[i - 1] ?? "") ? srcLines[i - 1] : ` // ${head[1]}`;
610
+ const template = [];
611
+ const raws = [];
612
+ for (i++; i < srcLines.length && srcLines[i] !== " </>);"; i++) {
613
+ template.push(tokenizeLine(srcLines[i]));
614
+ raws.push(srcLines[i]);
615
+ }
616
+ if (template.some((t) => !t)) continue;
617
+ const prop = (raw) => /^\{([A-Za-z_$][\w$]*)\}$/.exec(raw ?? "")?.[1] ?? null;
618
+ const idProps = template.map((t) => prop(t.attrs.find((a) => a.name === "id")?.raw));
619
+ const names = new Map();
620
+ template.forEach((t, j) => {
621
+ for (const a of t.attrs) if (prop(a.raw) && a.name !== "x" && a.name !== "y") names.set(`${j}:${a.name}`, prop(a.raw));
622
+ if (prop(t.children)) names.set(`${j}:children`, prop(t.children));
623
+ // An offset prop (`y={y + titleY}`, a figure's `{x: x + figX + 4}`).
624
+ for (const m of raws[j].matchAll(/\b([xy]) \+ ([A-Za-z_$][\w$]*)/g)) if (!names.has(`${j}:@${m[1]}`)) names.set(`${j}:@${m[1]}`, m[2]);
625
+ const seated = t.children?.startsWith("<") ? tokenizeLine(t.children) : null;
626
+ for (const a of seated?.attrs ?? []) if (prop(a.raw)) names.set(`${j}:>${a.name}`, prop(a.raw));
627
+ });
628
+ const prefixes = idProps.map((p) => (p && p !== "id" ? p.replace(/Id$/, "") : ""));
629
+ const calls = srcLines.map(tokenizeLine).filter((t) => t?.tag === head[1]);
630
+ const members = calls.map((call) => {
631
+ const ids = idProps.map((p) => /^"([^"]+)"$/.exec(call.attrs.find((a) => a.name === p)?.raw ?? "")?.[1] ?? null);
632
+ return { ids, lines: ids.map((id) => (id === null ? undefined : lineOf.get(id))) };
633
+ });
634
+ const missing = idProps.includes(null) || members.some((m) => m.lines.includes(undefined));
635
+ plans.push({ id: head[1], comment, names, prefixes, members, ...(missing ? { why: "a part is gone from the source" } : {}) });
636
+ }
637
+ if (!plans.length) return { src: flat, folded: [], kept: [] };
638
+ return applyPlans(flat, plans, rectIndex({}, measured), () => false);
639
+ }
@@ -17,6 +17,7 @@ import { fileURLToPath } from "node:url";
17
17
 
18
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
19
  const GATE_SESSION_PATH = path.join(__dirname, "gate-session.js");
20
+ const GATE_SHELL_PATH = path.join(__dirname, "gate-shell.js");
20
21
 
21
22
  // Tab-reuse timing contract, shared by both gates and injected into their
22
23
  // overlays as CFG.pollMs / CFG.pollTtlMs:
@@ -204,14 +205,18 @@ export function stripModuleSyntax(src) {
204
205
  .replace(/^export\s+/gm, "");
205
206
  }
206
207
 
207
- // gate-session.js as a browser IIFE exposing window.__SLIDEMIND_GATE__. Both
208
- // gate pages inline this BEFORE their overlay script — the overlays' whole
209
- // ledger/round machinery is that one shared factory.
208
+ // gate-session.js + gate-shell.js as one browser script exposing
209
+ // window.__SLIDEMIND_GATE__ (the ledger/round machinery) and
210
+ // window.__SLIDEMIND_SHELL__ (the shared chrome). Both gate pages inline this
211
+ // BEFORE their overlay script — the overlays read both at boot.
210
212
  export function bundleGateSession() {
211
213
  return (
212
214
  "(function () {\n" +
213
215
  stripModuleSyntax(fs.readFileSync(GATE_SESSION_PATH, "utf8")) +
214
- "\nwindow.__SLIDEMIND_GATE__ = { createGateSession: createGateSession };\n})();\n"
216
+ "\nwindow.__SLIDEMIND_GATE__ = { createGateSession: createGateSession };\n})();\n" +
217
+ "(function () {\n" +
218
+ stripModuleSyntax(fs.readFileSync(GATE_SHELL_PATH, "utf8")) +
219
+ "\nwindow.__SLIDEMIND_SHELL__ = { createGateShell: createGateShell, ICONS: ICONS, el: el };\n})();\n"
215
220
  );
216
221
  }
217
222