slidemind 0.3.20 → 0.4.1
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 +592 -16
- package/LICENSE +4 -0
- package/README.md +203 -8
- package/THIRD-PARTY-NOTICES.md +201 -0
- package/bin/slidemind.mjs +50 -2
- package/package.json +5 -3
- 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/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
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { deriveEnvLabel } from "../config.mjs";
|
|
25
25
|
import { getDesignBundleForBrand, getDesignBundleForBrandAnyBackend } from "./brands.mjs";
|
|
26
|
-
import { isSessionInvalidError, sessionsInScope, bridgeFetch, backendCapabilitiesResult, backendSupportsEngineRoutes } from "./transport.mjs";
|
|
26
|
+
import { isSessionInvalidError, sessionsInScope, bridgeFetch, backendCapabilitiesResult, backendSupportsEngineRoutes, capabilityUnknownError } from "./transport.mjs";
|
|
27
27
|
|
|
28
28
|
export function searchRoute(searchId, suffix = "") {
|
|
29
29
|
return `/api/cli/searches/${encodeURIComponent(String(searchId))}${suffix}`;
|
|
@@ -86,11 +86,12 @@ export async function resolveSearchBackend(searchId) {
|
|
|
86
86
|
if (!isOpaqueSearchId(searchId)) return null;
|
|
87
87
|
const failures = [];
|
|
88
88
|
for (const s of sessionsInScope()) {
|
|
89
|
-
// A
|
|
90
|
-
//
|
|
89
|
+
// A probe that got no answer (a revoked session 401s it; a slow relay
|
|
90
|
+
// times it out) says nothing about whether THIS environment holds the
|
|
91
|
+
// record — keep the failure, so "search expired" is never said for it.
|
|
91
92
|
const probe = await backendCapabilitiesResult(s.backendURL);
|
|
92
|
-
if (probe.
|
|
93
|
-
failures.push(probe
|
|
93
|
+
if (probe.status === "unknown") {
|
|
94
|
+
failures.push(capabilityUnknownError(s.backendURL, probe));
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
96
97
|
if (probe.caps?.engineRoutes !== true) continue;
|
|
@@ -112,6 +113,10 @@ export async function resolveSearchBackend(searchId) {
|
|
|
112
113
|
(e) => isSessionInvalidError(e) || e?.code === "login_required",
|
|
113
114
|
);
|
|
114
115
|
if (blocking) throw blocking;
|
|
116
|
+
// An environment whose capabilities could not be read may well hold the
|
|
117
|
+
// record: that is a retryable failure, not an expiry.
|
|
118
|
+
const unknown = failures.find((e) => e?.code === "capability_unknown");
|
|
119
|
+
if (unknown) throw unknown;
|
|
115
120
|
return null;
|
|
116
121
|
}
|
|
117
122
|
|
|
@@ -126,17 +131,37 @@ export async function resolveSearchBackend(searchId) {
|
|
|
126
131
|
// the design does the caller fall back to the pane relay (the design lives on
|
|
127
132
|
// an older add-in, and searching a different corpus would fold ITS slides into
|
|
128
133
|
// this deck). Returns null for that fallback and when nothing paired serves the
|
|
129
|
-
// routes at all
|
|
134
|
+
// routes at all — and only when that is KNOWN: an environment whose capability
|
|
135
|
+
// probe got no answer throws `capability_unknown` (retryable, see
|
|
136
|
+
// transport.backendCapabilities) rather than being counted as an old add-in.
|
|
137
|
+
// The pane relay is the path for an add-in without the routes, never for a
|
|
138
|
+
// request that timed out (x103).
|
|
139
|
+
//
|
|
140
|
+
// That unanswered probe is thrown only when the answer DEPENDS on it. A
|
|
141
|
+
// candidate that serves the deck's design wins outright whatever the silent
|
|
142
|
+
// environment would have said, so a stopped `local` must not block a deck
|
|
143
|
+
// bound to `dev` (2026-10-11). Where it could have changed the pick (no
|
|
144
|
+
// candidate at all, no candidate serving the design, a sole candidate that
|
|
145
|
+
// might have had a rival), guessing would search another corpus, so it throws.
|
|
130
146
|
export async function resolveEngineBackend({ backendURL = null, brandId = null } = {}) {
|
|
131
147
|
if (backendURL) {
|
|
132
148
|
return (await backendSupportsEngineRoutes(backendURL)) ? backendURL : null;
|
|
133
149
|
}
|
|
134
150
|
const sessions = sessionsInScope();
|
|
135
151
|
const candidates = [];
|
|
152
|
+
const unanswered = [];
|
|
136
153
|
for (const s of sessions) {
|
|
137
|
-
|
|
154
|
+
try {
|
|
155
|
+
if (await backendSupportsEngineRoutes(s.backendURL)) candidates.push(s.backendURL);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
unanswered.push({ backendURL: s.backendURL, err });
|
|
158
|
+
}
|
|
138
159
|
}
|
|
139
|
-
|
|
160
|
+
const unlessUnanswered = (answer) => {
|
|
161
|
+
if (unanswered.length) throw unanswered[0].err;
|
|
162
|
+
return answer;
|
|
163
|
+
};
|
|
164
|
+
if (candidates.length === 0) return unlessUnanswered(null);
|
|
140
165
|
// Exactly one CONNECTED environment: there is nothing else the deck could
|
|
141
166
|
// belong to. Note the test is on the sessions, not on the candidates — during
|
|
142
167
|
// a rollout only one of several environments serves the routes, and taking it
|
|
@@ -145,7 +170,7 @@ export async function resolveEngineBackend({ backendURL = null, brandId = null }
|
|
|
145
170
|
if (brandId) {
|
|
146
171
|
// The memo only stands while that environment is still a candidate.
|
|
147
172
|
const memo = engineBackendForBrand.get(brandId);
|
|
148
|
-
if (memo) return candidates.includes(memo) ? memo : null;
|
|
173
|
+
if (memo) return candidates.includes(memo) ? memo : unlessUnanswered(null);
|
|
149
174
|
// Candidates first: an environment that serves both the design AND the
|
|
150
175
|
// engine routes wins outright, even when an older environment also has the
|
|
151
176
|
// (content-identical) design.
|
|
@@ -158,6 +183,8 @@ export async function resolveEngineBackend({ backendURL = null, brandId = null }
|
|
|
158
183
|
// This candidate doesn't serve the design — try the next.
|
|
159
184
|
}
|
|
160
185
|
}
|
|
186
|
+
// The silent environment may be the candidate that serves it.
|
|
187
|
+
unlessUnanswered();
|
|
161
188
|
try {
|
|
162
189
|
const { backendURL: be } = await getDesignBundleForBrandAnyBackend(brandId);
|
|
163
190
|
engineBackendForBrand.set(brandId, be);
|
|
@@ -170,7 +197,7 @@ export async function resolveEngineBackend({ backendURL = null, brandId = null }
|
|
|
170
197
|
// rather than guessing an environment.
|
|
171
198
|
}
|
|
172
199
|
}
|
|
173
|
-
if (candidates.length === 1) return candidates[0];
|
|
200
|
+
if (candidates.length === 1) return unlessUnanswered(candidates[0]);
|
|
174
201
|
const err = new Error(
|
|
175
202
|
`Several connected environments run engine search (${candidates
|
|
176
203
|
.map((u) => deriveEnvLabel(u))
|
|
@@ -186,16 +213,24 @@ export async function resolveEngineBackend({ backendURL = null, brandId = null }
|
|
|
186
213
|
// "backend authoring ready" half of the readiness picture (`doctor`, `ready`),
|
|
187
214
|
// reported as ENV LABELS so no URL can reach a printed line. Pass
|
|
188
215
|
// connectionStatus()'s `backends` to skip environments that are already known
|
|
189
|
-
// unreachable; without it every session in scope is probed.
|
|
216
|
+
// unreachable; without it every session in scope is probed. An environment
|
|
217
|
+
// whose probe got no answer is listed under `unknown`, not under "no routes":
|
|
218
|
+
// a readiness report must not call a slow relay an old add-in.
|
|
190
219
|
export async function engineReachability({ backends = null } = {}) {
|
|
191
220
|
const urls = Array.isArray(backends)
|
|
192
221
|
? backends.filter((b) => b?.ok && b.backendURL).map((b) => b.backendURL)
|
|
193
222
|
: sessionsInScope().map((s) => s.backendURL);
|
|
194
223
|
const envs = [];
|
|
224
|
+
const unknown = [];
|
|
195
225
|
for (const url of urls) {
|
|
196
|
-
|
|
226
|
+
try {
|
|
227
|
+
if (await backendSupportsEngineRoutes(url)) envs.push(deriveEnvLabel(url));
|
|
228
|
+
} catch (err) {
|
|
229
|
+
if (err?.code !== "capability_unknown") throw err;
|
|
230
|
+
unknown.push(deriveEnvLabel(url));
|
|
231
|
+
}
|
|
197
232
|
}
|
|
198
|
-
return { available: envs.length > 0, envs };
|
|
233
|
+
return { available: envs.length > 0, envs, unknown };
|
|
199
234
|
}
|
|
200
235
|
|
|
201
236
|
// Resolve + verify the backend for a direct engine call. Throws a typed
|
|
@@ -4,7 +4,7 @@ import { deriveEnvLabel } from "../config.mjs";
|
|
|
4
4
|
import { assertSearchId, downloadTimeoutMs, resultRoute, directEngineBackend } from "./engine-routing.mjs";
|
|
5
5
|
import { pickAnyPresentationEntry } from "./pane-ops.mjs";
|
|
6
6
|
import { resolveBackendForPres, engineOpWithPaneWait } from "./presentations.mjs";
|
|
7
|
-
import { engineTimeoutMs, bridgeFetch, bridgeStreamToFile, envInt, delegatedBodyFields, graphHeaders } from "./transport.mjs";
|
|
7
|
+
import { engineTimeoutMs, bridgeFetch, bridgeStreamToFile, envInt, delegatedBodyFields, graphHeaders, backendCapabilities } from "./transport.mjs";
|
|
8
8
|
|
|
9
9
|
// Brand-engine search through the local bridge. New add-ins run this
|
|
10
10
|
// server-side with the relayed user token; older add-ins may fall back to a
|
|
@@ -308,3 +308,26 @@ export async function engineDownloadToFile({
|
|
|
308
308
|
return shape(res);
|
|
309
309
|
});
|
|
310
310
|
}
|
|
311
|
+
|
|
312
|
+
// The collage reader behind `transcribe`'s icon fetch: each item is a motif plus
|
|
313
|
+
// the numbered contact sheet (`gridDataUri`) its search returned; the answer is
|
|
314
|
+
// one 1-based selection, or null for "nothing here shows it", per item. Null
|
|
315
|
+
// when the backend does not offer it (no `assetPicks` capability) — the caller
|
|
316
|
+
// then decides by file name alone.
|
|
317
|
+
export async function pickAssets({ items, backendURL }) {
|
|
318
|
+
const caps = await backendCapabilities(backendURL);
|
|
319
|
+
if (caps?.assetPicks !== true) return null;
|
|
320
|
+
if (!items.length) return [];
|
|
321
|
+
const res = await bridgeFetch(
|
|
322
|
+
"/api/cli/asset-picks",
|
|
323
|
+
{
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: { "content-type": "application/json" },
|
|
326
|
+
body: JSON.stringify({ items }),
|
|
327
|
+
timeoutMs: engineTimeoutMs(),
|
|
328
|
+
},
|
|
329
|
+
backendURL,
|
|
330
|
+
);
|
|
331
|
+
const data = (await res.json()) ?? {};
|
|
332
|
+
return Array.isArray(data.picks) ? data.picks : null;
|
|
333
|
+
}
|
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
// for older backends that don't serve /api/cli/capabilities yet.
|
|
10
10
|
|
|
11
11
|
import { resolveBackendForPres } from "./presentations.mjs";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
bridgeFetch,
|
|
14
|
+
envInt,
|
|
15
|
+
sleep,
|
|
16
|
+
backendSupportsRenderJobs,
|
|
17
|
+
assertCliVersionSupported,
|
|
18
|
+
assertImageIntentSupported,
|
|
19
|
+
} from "./transport.mjs";
|
|
13
20
|
|
|
14
21
|
// Rebuild a job failure into the exact error shape bridgeFetch throws for the
|
|
15
22
|
// sync route, so callers (etag self-heal, error texts) can't tell the
|
|
@@ -197,6 +204,7 @@ export async function renderPptx({
|
|
|
197
204
|
sourceGuids = null,
|
|
198
205
|
conversions = null,
|
|
199
206
|
reconcile = null,
|
|
207
|
+
imageIntent = null,
|
|
200
208
|
backendURL = null,
|
|
201
209
|
onProgress = null,
|
|
202
210
|
}) {
|
|
@@ -217,6 +225,10 @@ export async function renderPptx({
|
|
|
217
225
|
// speaks this CLI's contract fails deep and confusingly; fail here instead
|
|
218
226
|
// with the actionable `slidemind update`.
|
|
219
227
|
await assertCliVersionSupported(be);
|
|
228
|
+
// And, when this deck carries approved image obligations, the contract that
|
|
229
|
+
// enforces them. Before the upload, so an old add-in costs a capability probe
|
|
230
|
+
// rather than a render that silently drops the user's picture.
|
|
231
|
+
if (imageIntent) await assertImageIntentSupported(be);
|
|
220
232
|
const basePath = renderBasePath({ presId, brandId });
|
|
221
233
|
const label = brandId ? (deckName ?? brandId) : presId;
|
|
222
234
|
const body = { slides: slides ?? {} };
|
|
@@ -251,6 +263,14 @@ export async function renderPptx({
|
|
|
251
263
|
if (reconcile && Object.keys(reconcile).length > 0) {
|
|
252
264
|
body.reconcile = reconcile;
|
|
253
265
|
}
|
|
266
|
+
// The deck's approved image obligations (src/bridge/image-intent-envelope.mjs).
|
|
267
|
+
// Its asset descriptors carry `workspacePath`, not bytes: those bytes are
|
|
268
|
+
// already in `deckAssets` above under that exact key, so the envelope points
|
|
269
|
+
// at them instead of shipping every screenshot twice. It also travels when the
|
|
270
|
+
// JSX forgot to bind a use — that is the whole point of validating against
|
|
271
|
+
// expectations rather than against the JSX. Additive: a deck with no image
|
|
272
|
+
// uses sends the same body it always did.
|
|
273
|
+
if (imageIntent) body.imageIntent = imageIntent;
|
|
254
274
|
// The brand surface has no presId to label the render with — pass the deck
|
|
255
275
|
// slug so job records/temp dirs stay readable (server-side sanitized).
|
|
256
276
|
if (brandId && deckName) body.deckName = deckName;
|
|
@@ -278,6 +298,7 @@ export async function translateDeck({
|
|
|
278
298
|
captured,
|
|
279
299
|
nativeSlides = null,
|
|
280
300
|
etag = null,
|
|
301
|
+
imageIntent = null,
|
|
281
302
|
backendURL = null,
|
|
282
303
|
}) {
|
|
283
304
|
if (!presId && !brandId) {
|
|
@@ -290,6 +311,7 @@ export async function translateDeck({
|
|
|
290
311
|
be = await resolveBackendForPres(presId);
|
|
291
312
|
}
|
|
292
313
|
await assertCliVersionSupported(be);
|
|
314
|
+
if (imageIntent) await assertImageIntentSupported(be);
|
|
293
315
|
const body = { captured };
|
|
294
316
|
// Reused native one-slide .pptx (base64), keyed by play-order filename — the
|
|
295
317
|
// server splices them in alongside the locally-captured JSX slides (contract
|
|
@@ -298,6 +320,10 @@ export async function translateDeck({
|
|
|
298
320
|
body.nativeSlides = nativeSlides;
|
|
299
321
|
}
|
|
300
322
|
if (etag) body.etag = etag;
|
|
323
|
+
// Same envelope as the render route: expected image obligations, descriptors
|
|
324
|
+
// pointing into the captured deck's own asset bytes. Both routes have to
|
|
325
|
+
// enforce the same thing, so both carry the same expectations.
|
|
326
|
+
if (imageIntent) body.imageIntent = imageIntent;
|
|
301
327
|
if (brandId && deckName) body.deckName = deckName;
|
|
302
328
|
const res = await bridgeFetch(
|
|
303
329
|
`${renderBasePath({ presId, brandId })}/translate`,
|