$ cat workspace-template.yaml
Presentazione PowerPoint
// Crea una presentazione su qualsiasi argomento nel tema Emblema: dai documenti di una cartella Drive, da un singolo file (PDF, Word, la trascrizione di un video o di un audio) o, senza documenti, dalla richiesta stessa. Sceglie i topic, genera slide HTML (con o senza immagini) e le assembla in una presentazione. Su richiesta la pubblica con un link condivisibile, ne fa il PDF e il file PPTX. Builds a presentation on any subject in the Emblema theme: from the documents in a Drive folder, from one file (a PDF, a Word document, a video or audio transcript) or, with no documents, from the request itself. It picks the topics, generates HTML slides (images optional) and assembles the deck. On request it publishes a share link and makes the PDF and the PPTX file.
// Canvas Preview
// Instruction
Presentazione PowerPoint (tema Emblema)
Template ID: 52010045-5388-479c-bd12-02be60a66219 (source: fixture apps/marketplace/fixtures/workspace-templates/pptx-from-drive-emblema.workspace.json, built by scripts/pptx-from-drive-emblema/build_template.py).
Composes a deck on any subject using the Emblema theme — teal-on-white, IBM Plex, 1920×1080, output a self-contained Reveal.js presentation. The content comes from the documents in a Drive folder, from a single Drive file (PDF, Word, a video or audio transcript…), or, when the request names no source at all, from the request itself (Source C). "Selezione dei Topic Principali" picks the topics and their slides; "Contenuto Presentazione" generates ALL slide content in one pass; expandPrototype clones a prototype per (topic, slide) pair, configures, wires and lays it out in one atomic call; Make Array → Presentation Composer assembles the deck. The publish decision is collected up front at the Phase-1 gate: on opt-in Phase 4 runs "Public Share" (a /s/{slug} URL), else the deck is left in draft. The PDF ("PDF Builder") is built at the end of Phase 4 only when the request asks for one, else on request. The PPTX file is never auto-generated — the PPTX Converter runs only on a separate explicit request.
Units. A topic is a subject area; each produces one or more slides (default: one). Page = slide: the deck has exactly as many slides as Make Array has occupied pins, and total_slides = sum over all topics of M_x. When the user states a deck size P ("un PPT di 5 pagine", "5 页的 PPT", "a 12-slide deck"), P is the total INCLUDING cover and closing: P = 1 (Cover) + total_slides + 1 (Closing), so total_slides = P − 2. P is NOT a topic count and NOT a slides-per-topic count — never copy P into the topicCount answer.
Workflow chain
You MUST complete all 4 phases in order: Phase 1 → Phase 2 → Phase 3 → Phase 4. Do NOT skip any phase. Do NOT jump to Phase 4 early.
STEP 0 — before anything else in a build turn: loadInstructionSection({ sectionId: "phase-1-setup" }). Every phase's real steps, exact gate payloads and prohibitions live in its section — the index alone is NOT enough to act on. Load each phase's section before entering it.
Everything else lives in the ## sections below — load one before acting on it, starting with ## Execution Strategy. Gate payloads and the two reference tables are template variables: loadInstructionSection({ variableId: "<id>" }).
Execution Strategy
You MUST complete all 4 phases in order: Phase 1 → Phase 2 → Phase 3 → Phase 4. Do NOT skip any phase. Do NOT jump to Phase 4 early.
## Phase 1: Setup steps 1-4 (4a, 4b, 4c) · ## Phase 2: Topic Selection & Content Generation steps 5-9 · ## Phase 3: Slide Expansion steps 10-13 (11a, 12a) · ## Phase 4: Verification & Final Execution steps 14-18 (14a, 15a, 17a). Numbering is continuous 1 → 18; it never restarts. Letter-suffixed steps are mandatory sub-steps.
Global rules
- Do NOT call
followUpanywhere in this template. Phase 4 ends with theverificationaudit at step 18 — the terminal action. Phase 4 has no user pauses: it acts deterministically on the Phase-1 gate answers. - After
nodeExecutorthe system notifies on completion — do NOT pollgetNodeOutputin a loop. - On a tool error retry up to 3 times with corrected parameters; if all 3 fail ask the user — do NOT skip the step and continue with defaults.
- On "continua" after an interruption, resume where you left off;
nodeFindershows which nodes already carry the right label and connections. - NEVER execute a prototype node (
"Topic #x - Slide #1","Topic #x - Slide #1 - [With Image Input]","Image Creation - for Topic #x - Slide #1 - [With Image Input]") — it produces incorrect output. Only execute renamed clones; never delete a prototype. applyCanvasDatais used exactly once, at step 1. Later changes useexpandPrototypeor atomic tools (upsertNode,upsertEdge,deleteNode,deleteEdge) — instant, noapplyCanvasData.
Playbook: apps/workspace-service/app/templates/reference/INSTRUCTION_GUIDELINES.md.
Node Reference
Canvas labels are Italian — match them exactly with nodeFinder. After applyCanvasData the nodesPreview IDs are dead: re-resolve every label through nodeFinder, and check a nodeId is a valid UUID before upsertEdge. Port lists, defaults and widget option sets are in the canvas schema reference in <current_workspace> — only what the schema does not tell you is repeated here.
- "Seleziona Materiale Sorgente" (Drive Folder Reader) — labeled "Drive Folder Reader" on some snapshots; match either. Folder mode only; its
driveItemIdtakes the FOLDER id (notfolderId), outputdriveItemIds. - "Leggi i Documenti disponibili" (Drive Content Reader) —
driveItemId→content, which feeds BOTH "Selezione dei Topic Principali" AND "Contenuto Presentazione". Source C deletes it and sets thatcontentdirectly. - "Selezione dei Topic Principali" (Topic Selector,
content→summary) — the only node that fixes the deck structure: topics AND slides per topic in one pass. Configureprompt(default"Seleziona i topic principali dei documenti e riassumi i punti chiave (topics), per ciascun topic riassumi i temi (slides) rilevanti"). LeavellmModelIdEMPTY — empty resolves to the platform's default model on the correct provider route. NEVER invent a model name: an unresolvable name silently downgrades the LLM route and the summaries degrade into meta-text. Its exact topic and slide names MUST be reused in every later label and prompt. - "Contenuto Presentazione" (Content Generator,
content→summary) — all slide text in one pass, feeding "#Cover", "#Closing" and every topic Slide Generator. Configureprompt(rewritten at step 8); leavellmModelIdEMPTY (platform default, same rule as above). - "#Cover" / "#Closing" (Slide Generators,
content→slide_html) — labeled "#Intro" / "#Outro" on some snapshots; match either. "#Cover" (template_name: "emblema-cover",instruction: "titolo slide di apertura") is pre-connected toitem_0; "#Closing" (template_name: "emblema-closing") is NOT — you wire it at step 13. - "Topic #x - Slide #1" (prototype,
content→slide_html) — standalone Slide Generator, 1 node, not in a section (find withnodeFinder, notsectionFinder); defaultstemplate_name: "emblema-bullets",instruction: "Inserisci il contenuto del Topic #x - Slide #1". - "Topic #x - Slide #1 - [With Image Input]" (prototype,
template_name: "emblema-image") — paired with "Image Creation - for Topic #x - Slide #1 - [With Image Input]" (prompt→image→ the generator'simageinput). That internal edge IS preserved when both ids are cloned in one call; the externalcontentedge is NOT —expandPrototype'sfeedre-creates it. - "Make Array" — collector; pin order = slide order (
item_0= #Cover, one pin per slide in mapping-table order, last pin = #Closing). Occupied pins = 1 +total_slides+ 1. Ships 10 pins (item_0 … item_9);expandPrototypegrows any pin its own items need. - "Presentation Composer" (
items→presentation) — Reveal.js HTML; configuretitle,theme(default"emblema"),transition(slide,fade,convex,concave,zoom,none). Sourceapps/workspace-service/app/templates/presentation_composer.py, theme assetsapps/workspace-service/app/templates/themes/emblema/. - "PPTX Converter" (
presentation→.pptx) — configuretitleandtheme, which must match the Composer. Never auto-run. Sourceapps/workspace-service/app/templates/pptx_converter.py. - "Public Share" — the "execute me" target: template ID
a3f1d8c2-6e4b-5a9d-b7c3-2f8e1a4d6b9c, categoryoutput, sourceapps/workspace-service/app/templates/public_share.py; takes the Composer'spresentationoncontent, returns a/s/{slug}URL, triggers the upstream chain via dirty-cache. Resolved (auto-created if missing) at step 15. - "PDF Builder" — template ID
0e13b315-0a58-429a-8cee-75110496a9b7, categoryoutput, sourcenode-templates/pdf-builder/; takes the Composer'spresentationon itspresentationinput and returnspdf, named after the Composer'stitle, one page per slide printed as the deck shows on screen (vector text, 1920×1080). Wire ONLYpresentation— itspptxinput stays empty here (both wired fails the node). File-only output: it publishes nothing, so it never needs the publish answer. Ships pre-wired in the "Exports" section; step 15a re-checks it, and creates it on a canvas without one.
Emblema Layout Selection
Emblema ships 34 slide layouts. The full layout list with descriptions is in the slide node's template_name schema options in <current_workspace> — read it there; it is not repeated here. Pick per slide from the content shape: emblema-bullets everywhere wastes the brand.
Layout picking rule — apply at step 11 and through config.template_name at step 12, in priority order: first match wins. The # column IS that order.
The other 32 are all valid template_name values. No marina-* or any other family.
Images are NOT layout-gated — the slide generator injects its image instruction whenever an image input is provided, on ANY layout (node-templates/slide-generator/code.py:463). Wire image inputs by design intent: emblema-team, emblema-testimonial, emblema-logos, emblema-ui, emblema-two-col, emblema-image, emblema-magazine, emblema-two-img, emblema-hero-centered are built around a real photograph; emblema-grid-img, emblema-collage, emblema-hero-card are CSS-placeholder styles — use them only when placeholders are acceptable.
Variety target: ≥4 different layouts across total_slides topic slides; all-emblema-bullets means you missed signals (numbers, dates, comparisons, processes) — re-read the slide names and re-apply the rule. Theme consistency: every Slide Generator, the Presentation Composer and the PPTX Converter use the emblema theme and emblema-* layouts.
Sources: node-templates/slide-generator/ (code.py, input_schema.json); new layouts follow apps/workspace-service/app/templates/ADDING_PRESENTATION_THEMES.md. Sibling instruction (Marina Militare theme): docs/workspace-template-instructions/pptx-from-drive.md.
Phase 1: Setup
Gate keys: topicCount, slidesPerTopic, useImages, imageStyle, publish. Sub-steps 4a, 4b and 4c are all mandatory; 4a is not skippable — it decides WHICH questions get asked at all. By the end of step 4 all five values must be resolved: Phase 2 configures node prompts from them and Phase 4 acts on publish. One more key, pdf, is extracted at 4a only and never asked: Phase 4 builds the PDF iff the request asked for one.
applyCanvasData→ IMMEDIATELY commit the template. Call it before any other tool call.- Pick the source from the ORIGINAL request. It names, attaches (@-mention) or refers to a Drive folder or file ("dai documenti nella cartella X", "usando questo PDF", "@video.mp4") → A (folder) or B (single file):
searchDriveItems+selectDriveItem→ get that folder/file ID. On 0 results try one shorter query; if still nothing after 2 attempts ask the user to specify the item. Never retry the same query. It names no source at all ("crea una presentazione di 10 slide sulla cybersecurity") → C (no source): do NOT search Drive and do NOT ask for documents — the request itself is the material. - Configure the data source: A (folder) —
nodeFinder→ "Seleziona Materiale Sorgente",upsertNode({ nodeId: "<found ID>", input: { driveItemId: "<selected ID>" } }). B (single file) —nodeFinder→ "Seleziona Materiale Sorgente",deleteNode({ nodeId: "<found ID>" }), thennodeFinder→ "Leggi i Documenti disponibili" andupsertNodethe samedriveItemIdfield there. C (no source) —nodeFinder→ "Seleziona Materiale Sorgente" and "Leggi i Documenti disponibili",deleteNodeBOTH, then ONEbatchUpsertNodessetting the sameinput: { content: "<brief>" }on "Selezione dei Topic Principali" AND "Contenuto Presentazione". The brief is written in the request's language: the user's request verbatim, then any audience, purpose or focus it states, then the lineNessun documento sorgente: sviluppa l'argomento con conoscenze generali accurate. Non inventare dati, cifre, nomi, date o citazioni specifiche.(translated). Never invent facts the user did not give. - Phase 1 gate. Do 4a, then 4b, then 4c. Never call
requestUserDecisionbefore 4a has produced its result.
4a — extract the answers ALREADY PRESENT in the ORIGINAL message (MANDATORY). Asking a question the user already answered is a defect: it wastes a pause, invites a contradictory second answer, and gets the answer they did give dropped. Re-read the request verbatim; set every known.<key> to null, then fill from explicit triggers (it / en / zh):
topicCountandslidesPerTopic— "un PPT di 5 pagine", "a 5-page deck", "5 页的 PPT" →pageBudget = P, then the decomposition below.topicCount— "5 topic principali", "5 main topics", "5 个主题" →5.slidesPerTopic— "3 slide per topic", "3 slides each", "每个主题3页" →3.useImages— "senza immagini" / "solo testo", "no images", "不要图片" →"no", makingimageStylemoot (leavenull, never ask it); "con immagini", "with images", "加上图片" →"yes".imageStyle— "foto realistiche", "photographic images", "真实照片" →"photo"; "diagrammi" / "schemi" / "icone", "diagrams", "示意图" →"diagram". Either also setsknown.useImages = "yes".publish— "pubblicala" / "condividila", "publish it", "发布" →"publish"; "lasciala in bozza", "don't publish", "留草稿" →"draft".pdf— the PDF as a DELIVERABLE: "in PDF" / "dammi anche il PDF" / "esportala in PDF", "as a PDF" / "export to PDF" / "PDF version", "导出PDF" / "生成PDF版" →"yes". A PDF named as the SOURCE ("usando questo PDF", "dal PDF allegato", "@report.pdf") is Source B, NOT a PDF request. Never asked: when not stated,pdfstaysnull.
EXPLICIT-ONLY RULE. An answer is known ONLY when stated explicitly and unambiguously; if you must reason, estimate or infer it is NOT known — leave it null and ASK. Vague size ("circa 5 pagine", "5-7 slide", "a short deck") and conditional imagery ("qualche immagine") are NOT known. Never infer from tone, topic, audience or document type, and never infer one key from another except the ONE case above. When in doubt, fail open to ASKING: an unnecessary question costs one line; a wrong extraction silently builds the wrong deck.
Page-budget decomposition — ONLY on an explicit integer deck size P. content_slides = P − 2; if < 1 use 1 and note that the minimum deck is 3 pages. Default budgetTopics = content_slides, budgetSlidesPerTopic = 1; only when content_slides > 20, pick the SMALLEST integer S in 2..10 with content_slides % S == 0 AND content_slides / S <= 20 and use budgetSlidesPerTopic = S, budgetTopics = content_slides / S (no such S → keep the default and let step 7's >20-topic warning apply). Assert 1 + budgetTopics × budgetSlidesPerTopic + 1 == P, then set known.topicCount = budgetTopics and known.slidesPerTopic = budgetSlidesPerTopic — both resolved, so neither is asked at 4b. With no explicit P, pageBudget = null.
Conflict rule. If the message states BOTH P AND an explicit topicCount or slidesPerTopic, the explicit per-key number WINS and the other derives from content_slides = P − 2: slidesPerTopic = M → budgetTopics = max(1, round(content_slides / M)); topicCount = N → budgetSlidesPerTopic = max(1, round(content_slides / N)). Recompute pageBudget = 1 + budgetTopics × budgetSlidesPerTopic + 1; it may no longer equal P — acceptable, but you MUST say so (richieste <P> pagine; struttura richiesta → <pageBudget> pagine). If all three are stated and do not multiply out, the two structural numbers win.
Print one line before 4b naming what you extracted and what you will still ask; if you cannot print it you have not run 4a. Unit discipline: P is a PAGE total, budgetTopics a TOPIC count; passing P as topicCount ("5 topic" for "5 pagine") delivers 1 + 5 + 1 = 7 pages — a defect, not a rounding choice.
4b — ask ONLY what 4a left null. The question objects are template variables; load only the ones you still need: loadInstructionSection({ variableId: "gate.pageBudget" }) → value.questions = topicCount + slidesPerTopic (value.note explains the always-rendered free-text option) · { variableId: "gate.contentQuestions" } → useImages + imageStyle · { variableId: "gate.publishChoice" } → publish. Splice the loaded arrays into ONE questions array in canonical order (topicCount, slidesPerTopic, useImages, imageStyle, publish), keeping a question iff its known.<key> is still null and dropping imageStyle whenever known.useImages === "no". Present each kept question VERBATIM as loaded — same key, question, header, context, options; never rewrite, translate, reorder or re-encode an option list to smuggle in an extracted value. Then call requestUserDecision exactly once — at most once in Phase 1 whatever the subset size, never one call per question. Keep imageStyle when useImages is itself asked (you cannot branch mid-card, so it rides along and its answer is discarded on "no"). The full set of 5 exceeds the ≤4 soft recommendation but stays inside the ≤6 hard limit: a deliberate exception. If ZERO questions remain, do NOT call requestUserDecision at all — questions takes 1–6 entries, so [] is a schema error. Print the fully resolved set instead, treat the result as answers = [], and merge.
Merge — for a question you ASKED the gate answer wins; for one you did NOT ask, known.<key> IS the answer, read as answers.find(a => a.key === "<key>")?.selectedIds?.[0]. Cancelled (answers is []) or missing → the bracketed fallback.
topicCountChoice←known.topicCount/ gate answer,otherTextaccepted if it parses as a positive integer ≤ 50 elseauto[auto]slidesPerTopicChoice←known.slidesPerTopic/ gate answer,otherTextaccepted if it parses as a positive integer ≤ 10 elseauto[auto]useImagesChoice←known.useImages/ gate answer [no]imageStyleChoice←known.imageStyle, defaulting tophotowhen null because images are on but no style was stated / gate answer [photo]publishChoice←known.publish/ gate answer [draft]pdfChoice←known.pdf[no] — never a gate question
known.publish === "publish" counts as the user's explicit choice to publish. Never publish on a null, draft or cancelled resolution. Carry pageBudget, budgetTopics, budgetSlidesPerTopic forward unchanged: the gate never clears them, and a cancelled gate keeps them and every known.* value too — cancelling the card does not retract what the user typed. imageStyleChoice is used only when useImagesChoice === "yes"; it builds the per-slide image prompts at step 12.
4c — deck language (NEVER a question). Resolve deckLanguage from the ORIGINAL request: a language the user explicitly asked for ("in inglese", "in English", "用中文") wins; otherwise the language the request message itself is written in. Do NOT ask the user and do NOT infer from the documents — the source material's language NEVER decides the deck's. Map it to a platform code (it, en, zh, de, es, fr, ja, ko, nl, pt, ru) and apply it in ONE batchUpsertNodes call setting input: { language: "<code>" } on SIX nodes. Resolve all six ids with ONE nodeFinder call carrying ALL six labels in queries — never one call per label, and never ids remembered from before applyCanvasData (a <current_workspace> inventory built before the template applied still lists the OLD canvas; writing to those ids silently leaves the real generators on auto): "Selezione dei Topic Principali", "Contenuto Presentazione", "#Cover", "#Closing", "Topic #x - Slide #1", "Topic #x - Slide #1 - [With Image Input]". The two prototypes propagate language to every clone at step 12; the Image Creation prototype has no language input — skip it. If the request language is genuinely unresolvable, make NO write: every node keeps language: "auto" (source-material language), which is the correct fallback, and step 18's summary must then say the deck follows the documents' language.
Phase 1 complete. Now proceed to Phase 2. Do NOT skip to Phase 4.
Phase 2: Topic Selection & Content Generation
Entry precondition: all five values RESOLVED — topicCountChoice, slidesPerTopicChoice, useImagesChoice, imageStyleChoice, publishChoice, each extracted at 4a or answered at 4b. It does NOT matter how many questions were asked — five, one, or zero; what matters is that all five VALUES exist (imageStyleChoice is moot unless useImagesChoice === "yes"; publishChoice is draft when absent/cancelled). If any is missing you half-ran step 4: STOP, go back, re-run 4a, ask the remainder in ONE requestUserDecision call. Do NOT proceed with defaults — Phase 2 would run its LLM nodes twice.
- Resolve the merged answers, then configure the topic selector.
pageBudgetset AND a choice still"auto"→ take it from the budget (budgetTopics/budgetSlidesPerTopic).pageBudgetset AND a choice is a number differing from the decomposition → keep the number and recomputepageBudget = 1 + topicCountChoice × slidesPerTopicChoice + 1, so the run and the final summary quote the deck size actually produced.pageBudgetnull → leave both as merged (autogenuinely means "you decide"). PrintStruttura risolta: <topicCountChoice> topic × <slidesPerTopicChoice> slide = <pageBudget or "n/d"> pagine totali.ThennodeFinder→ "Selezione dei Topic Principali": both stillauto(only possible whenpageBudgetis null) → leave the default prompt; at least one is now a number →upsertNode({ nodeId: <found ID>, input: { prompt: "Seleziona <N> topic principali dei documenti e per ciascun topic seleziona <M> temi (slides) rilevanti" } })(Source C:"Proponi <N> topic principali per una presentazione sull'argomento richiesto e per ciascun topic seleziona <M> temi (slides) rilevanti", and with both stillautoset it anyway, with "i topic" / "i temi" — the default prompt talks about documents there are none of) with<N>=topicCountChoice(or "i topic" ifauto),<M>=slidesPerTopicChoice(or "i temi"), appending whenpageBudgetis set:" La presentazione finale deve avere esattamente <pageBudget> pagine: copertina + <N>×<M> slide di contenuto + chiusura. Non proporre più topic o più slide di così."Append any focus area named in the original message. nodeExecutor→ "Selezione dei Topic Principali". Wait for completion; with a Drive source "Leggi i Documenti disponibili" runs as its dependency (Source C has none).getNodeOutput→ storetopics = [topic1_name, …, topicN_name],slides[X] = [slide1_name, …, slideM_name]per topic X,N = number of topics,M_x = number of slides for topic X(with the default promptM_x = 1for every topic). Then:- If
topicCountChoiceis a specific integer (including one from the budget), capNto it; ifslidesPerTopicChoiceis, cap everyM_xto it. The user already chose at the gate — do NOT re-ask. If neither is set ANDpageBudgetis null, use ALL topics from the output — do NOT cap or reduce. - Budget reconciliation (MANDATORY when
pageBudgetis set) — after the caps,total_slides == pageBudget − 2MUST hold, wheretotal_slides = sum of M_x. Too HIGH → drop trailing slides from the LAST topic's last slide backwards until it matches; if that empties a topic, drop the topic. Too LOW → do NOT deliver a shorter deck: add slides round-robin across the existing topics starting at Topic #1, naming each added slide from a sub-theme already present under that topic in the selector output (never invent a topic name); if the material genuinely cannot support the count, STOP, say how many content slides the documents support and ask whether to proceed shorter — do NOT silently shrink. PrintBudget pagine: <pageBudget> richieste = 1 copertina + <total_slides> contenuto + 1 chiusura.total_slidesafter reconciliation is the planned slide count; the step-11 table, the step-14a roster and the step-18 summary must all agree with it. - Safety limits, applied AFTER the gate caps above: if
N> 20 ortotal_slides> 50, warn and ask whether to proceed or reduce — do NOT silently reduce, and ask only about proceeding vs reducing (never re-ask the topic-count / slides-per-topic questions).
- If
CRITICAL: ALL topic and slide names used later MUST come from this step-7 output. Do NOT invent topic or slide names.
nodeFinder→ "Contenuto Presentazione"; rewrite its prompt listing ALL topics and slides with the exact step-7 names:upsertNode({ nodeId: <found ID>, input: { prompt: "Prepara il contenuto della presentazione espandendo ogni topic e slide selezionati dal nodo \"Selezione dei Topic Principali\" creando il testo per ciascuna sezione.\n\nNel prompt elenca tutti i topic e le slide da espandere includendo il loro nome esteso nel seguente formato:\n\n#Topic 1: <topic1_name>\n##Slide 1: <slides[1][1]>\n##Slide 2: <slides[1][2]>\n...\n#Topic 2: <topic2_name>\n##Slide 1: <slides[2][1]>\n...\n\nPer ogni topic principale, sviluppa una sezione dettagliata con tutte le slide associate, fornendo descrizioni complete e coerenti per la presentazione finale." } })— replace ALL placeholders with actual step-7 names.nodeExecutor→ "Contenuto Presentazione", immediately after step 8 — do NOT stop in between. Wait for completion.
Phase 2 complete. Now proceed to Phase 3. Do NOT skip to Phase 4.
Phase 3: Slide Expansion
expandPrototype replaces the whole clone → configure → wire → pins → layout choreography with ONE atomic call per prototype kind: it clones the prototype once per items[] entry, applies each item's config (keyed by PROTOTYPE node id), wires the shared feed edge into every clone, wires each clone's output to collector pin item_<pin> (growing missing Make Array pins automatically), and with layout runs ungroup → organize → group → autoLayout. Atomic: on error nothing changed — fix the inputs and retry. Returns { success, instances: [{ label, pin, nodeIdMap }], sectionId?, summary }. Do NOT call updateNodeSchema and do NOT wire item_* edges yourself for the clones, and do NOT re-run batchCloneNodes, batchUpsertEdges, organizeNodes, autoLayout or sectionManager over the expansion — repeating any part corrupts the pin order.
nodeFinder→ store: "Contenuto Presentazione" →contentId; "Make Array" →makeArrayId; always"Topic #x - Slide #1"→simpleProtoId; whenuseImagesChoice === "yes"also"Topic #x - Slide #1 - [With Image Input]"→imageSlideProtoIdand"Image Creation - for Topic #x - Slide #1 - [With Image Input]"→imageCreationProtoId.-
Build the COMPLETE MAPPING TABLE before expanding — it is IMMUTABLE; steps 12-18 MUST use this exact mapping. For each topic X from #1 to #N, for each slide Y from #1 to #M_x: a sequential
clone_indexfrom 0;pin = clone_index + 1(item_0is reserved for #Cover); a layout from the picking rule in## Emblema Layout Selection(≥4 distinct layouts). Print the table, columnsclone_index | pin | topic | slide | layout.11a — partition (MANDATORY when
useImagesChoice === "yes"; skip entirely otherwise). Preserving each row'sclone_indexandpin, split intoimage_rows(the slides you decided deserve a generated image — design-intent list in## Emblema Layout Selection) andtext_rows(all the rest);text_rows.length + image_rows.lengthMUST equaltotal_slides. Print both lists with their counts and layouts. Ifimage_rowsis empty, run the No branch only. -
Expand — one
expandPrototypecall per prototype kind, every item carrying an explicitpinfrom its mapping-table row so the two calls interleave correctly in Make Array's pin order (without explicit pins the second call restarts atitem_0and overwrites the Cover). Text slides — all rows in the No branch,text_rowsin the Yes branch (skip when empty):Image slides — Yes branch only,
image_rows. Same shape withprototype: { nodeIds: ["<imageCreationProtoId>", "<imageSlideProtoId>"] }(BOTH ids in one array — that is what preserves each pair's internal Image Creation[image]→ Slide Generator[image]edge),feed.toPrototypeNodeIdandcollector.fromPrototypeNodeIdboth"<imageSlideProtoId>",layout: { groupLabel: "Topic Slides con Immagini" }, each item configuring BOTH prototype nodes:{ label: "Topic #1 - Slide #2", pin: 2, config: { "<imageSlideProtoId>": { instruction: "…", template_name: "emblema-image" }, "<imageCreationProtoId>": { prompt: "<IMAGE_PROMPT>" } } }.<IMAGE_PROMPT>comes fromimageStyleChoice, naming that row's own topic and slide:"diagram"→"Genera un'immagine diagrammatica per il topic '<topicX_name>', slide '<slides[X][Y]>'. Stile: diagramma editoriale con icone, testo etichette, schemi visivi vettoriali. Composizione chiara e leggibile, coerente con il brand Emblema (colori teal/bianco, tipografia pulita).""photo"→"Genera un'immagine fotografica per il topic '<topicX_name>', slide '<slides[X][Y]>'. Stile: fotografia editoriale realistica di persone, ambienti, atmosfere coerenti con il contenuto. NON usare icone, testo, grafica vettoriale, schemi o diagrammi. Mood evocativo, palette teal/bianco quando possibile, coerente con il brand Emblema."
Both calls:
instructionMUST carry the exact topic and slide names (never the bare"Topic #1 - Slide #2");template_nameMUST be one of the 32 emblema content layouts (neveremblema-cover/emblema-closing, nevermarina-*); every Image Creation clone MUST get a non-empty per-slidepromptin this same call. Keep each returnedinstances[i].nodeIdMap(prototype id → clone id) — steps 12a, 14 and 14a read clone ids from it.12a — label the image pairs (Yes branch only, immediately after the image call). For a single-node prototype
item.labelrenames the clone directly; for the two-node image prototype it names a per-item wrapper section that thelayoutstep then dissolves, so the two node labels MUST be set explicitly or every image slide stays named"Topic #x - Slide #1 - [With Image Input]"and the step-14a roster cannot match it. ONEbatchUpsertNodescall over the imageinstances, two ops per pair:{ nodeId: instances[g].nodeIdMap["<imageSlideProtoId>"], label: "Topic #X - Slide #Y" }and{ nodeId: instances[g].nodeIdMap["<imageCreationProtoId>"], label: "Image - Topic #X - Slide #Y" }. - Wire #Closing — the one pin
expandPrototypedid not grow, because #Closing is an existing node, not a clone.nodeFinder→ "#Closing" (or "#Outro").upsertEdge({ sourceNodeId: "<#Closing ID>", sourcePort: "slide_html", targetNodeId: "<makeArrayId>", targetPort: "item_<total_slides + 1>" })— it declares that pin if Make Array lacks it, so no schema call is needed. Fallback, only if C7 finds fewer pins (an older build that doesn't declare wired pins): ONEupdateNodeSchema({ nodeId: "<makeArrayId>", inputSchema: { type: "object", properties: { … }, required: [] } })carrying oneitem_<i>: { type: "string", order: <i>, title: "Item <i+1>", portType: "Any" }for everyi = 0 … total_slides + 1— it REPLACES the schema and prunes edges to any pin you omit, so list them all and never fewer than 10.
Phase 3 complete. Now proceed to Phase 4.
Phase 4: Verification & Final Execution
Ordering (CRITICAL): 14 → 14a → 15 → 15a → 16 → 17 → 17a → 18. No user pauses — 14a is the blocking Make Array membership audit, 15 resolves "Public Share", 15a resolves "PDF Builder", 16 acts on the Phase-1 publishChoice, 17 executes iff publishChoice === "publish", 17a executes iff pdfChoice === "yes", 18 is the verification audit (terminal — never call followUp). Do NOT call verification before nodeExecutor (steps 17 / 17a, when they run). Do NOT skip this phase: missing edges cause empty slides. The delivered deck has exactly as many slides as Make Array has OCCUPIED PINS — not the nodes you created, not the rows in your mapping table, not the topics you announced. Every slide count you state MUST be the occupied-pin count measured at 14a.
-
nodeFinder→ "Make Array" and ALL Slide Generators (labels matching "Topic #", plus "#Cover" / "#Closing", accepting "#Intro" / "#Outro").total_slidesis the planned count fixed at step 7. RUN every check and FIX every failure here; at step 18 you REPORT each as averificationitem.
14a. Make Array membership audit (BLOCKING — nothing downstream runs until it passes). In a real failure the agent announced "10 slide totali" and success while Make Array held only item_0 and item_11, and the PPTX had 2 slides. Do NOT trust your mapping table, your expandPrototype result, or your memory — read the canvas. Cover/closing match by ROLE (first pin = cover, last = closing), so never report a pin MISSING over a #Cover/#Intro label variant. Complete ALL sub-steps:
a. Expected roster = the step-11 table plus #Cover and #Closing: item_0 → #Cover; item_<clone_index + 1> → "Topic #X - Slide #Y" per row; item_<total_slides + 1> → #Closing; expected occupied pins = total_slides + 2.
b. ACTUAL membership from the canvas: nodeFinder → "Make Array", enumerating incoming edges as (targetPort, sourceNodeId, source label). If it exposes none, read the source side: nodeFinder every "Topic #" / #Cover / #Closing node and record its outputs target port. outputs: [] is NOT connected, whatever your plan says.
c. Print the roster table — mandatory, | pin | expected node | actual source | status |, one row per pin, status OK or MISSING. If you cannot print it you have not run the audit — go back to (b).
d. Three checks: (1) Coverage — every pin item_0 … item_<total_slides + 1> has exactly one incoming edge. (2) Distinctness & no gaps — no two slide nodes share a pin, no slide node is on two pins, and the occupied indices form the contiguous range 0 … total_slides + 1 (item_0 + item_11 on an 11-slide plan is 2 slides, not 11 — gaps are the signature of this failure). (3) Count — occupied_pins == total_slides + 2; store actual_slide_count = occupied_pins: this, not total_slides + 2, is what the user receives.
e. All three pass → state Composizione verificata: <actual_slide_count> slide collegate su <total_slides + 2> pianificate. and go to 15.
f. Any failure → REPAIR, once. MISSING pin → upsertEdge({ sourceNodeId: <that row's clone ID>, sourcePort: "slide_html", targetNodeId: "<makeArrayId>", targetPort: "item_<n>" }). DUPLICATE → deleteEdge the wrong one, upsertEdge the right one. Do NOT re-run expandPrototype — it would clone a second set of slides. If a row has no clone at all, say so explicitly: a step-12 failure, not a wiring failure. Then re-run (b)-(d) exactly once.
g. Still failing → STOP. Do NOT loop. Do NOT proceed to step 17 and do NOT execute anything. Go to step 18 and report honestly: one error item per empty pin naming the pin and the slide that should occupy it, plus one reading "Composizione incompleta: <actual_slide_count> slide collegate su <total_slides + 2> pianificate — la presentazione conterrà <actual_slide_count> slide." Reporting the planned count as if delivered is a failure of this step, not a rounding of it. actual_slide_count is the ONLY slide number you may quote from here on.
- Resolve the Public Share node (deterministic, no pause).
nodeFinder({ query: "Public Share" }): found → store itsnodeIdas<publicShareId>. Not found ANDpublishChoice === "publish"→ auto-create withupsertNode({ templateId: "a3f1d8c2-6e4b-5a9d-b7c3-2f8e1a4d6b9c" }), store the returnednodeId, thenupsertEdge({ sourceNodeId: "<Presentation Composer ID>", sourcePort: "presentation", targetNodeId: "<publicShareId>", targetPort: "content" }). Not found ANDpublishChoice !== "publish"→ nothing to do; the deck stays in draft. Go to 15a either way. 15a. Resolve the PDF Builder (deterministic, no pause; only whenpdfChoice === "yes", else go to 16).nodeFinder({ query: "PDF Builder" }): found → store itsnodeIdas<pdfBuilderId>; it must have exactly ONE incoming edge, Presentation Composerpresentation→ itspresentation, and nothing onpptx→upsertEdgethe missing one,deleteEdgeany other. Not found →upsertNode({ templateId: "0e13b315-0a58-429a-8cee-75110496a9b7", label: "PDF Builder" }), store the returnednodeId, thenupsertEdge({ sourceNodeId: "<Presentation Composer ID>", sourcePort: "presentation", targetNodeId: "<pdfBuilderId>", targetPort: "presentation" }). Go to 16. - Act on the Phase-1 publish decision (no user pause). Give a one-sentence summary in the user's language that the presentation is built and ready (and that its PDF follows, when
pdfChoice === "yes").publishChoice === "publish"AND<publicShareId>set → step 17; otherwise (draft/ cancelled / no node) → skip 17, go to 17a, noting in the summary that the presentation is not published. Not a pause —publishChoicewas collected at the Phase-1 gate andpdfChoiceextracted at 4a; steps 17 and 17a are the only conditional executions. - Execute "Public Share" (only when the user opted in).
nodeExecutor({ nodeId: "<publicShareId>" }), only under 16's condition. The system auto-runs all upstream nodes (#Cover + all topic slides + #Closing → Make Array → Presentation Composer → Public Share). Wait; the result card shows the/s/{slug}URL. The PPTX Converter is NOT auto-run — execute it later only on explicit user request. 17a. Execute "PDF Builder" (only when the request asked for a PDF).pdfChoice === "yes"AND<pdfBuilderId>set →nodeExecutor({ nodeId: "<pdfBuilderId>" }); it runs the slides and the Composer first when step 17 did not. Wait; the result card offers the.pdf. It fails withRENDER_SERVICE_UNAVAILABLE/RENDER_FAILED→ the deck (and the link, if published) is unaffected: say so, run it once more, and report at step 18 if it still fails. Never change a slide for it. - Final audit.
verification→ run the audit and present the checklist before summarizing (info✓ for passes,warning/errorfor real issues): Composition (from 14a) —actual_slide_count == total_slides + 2occupied pins, contiguous, no gaps or duplicates;info✓ ONLY when 14a passed, else theerroritems from 14a(g). One item per check C1–C9 of the step-14 table (C9 only whenuseImagesChoice === "yes"), reporting its state after the step-14 fixes. Execution status — step 17 ran → ✓ "Public Share eseguito; URL di condivisione/s/{slug}generato"; step 17a ran → ✓ "PDF pronto: .pdf" (or anerrornaming the[LIBRARY]code when it still failed); neither ran →info"Presentazione lasciata in bozza — nessun nodo eseguito (esegui 'Public Share' per il link o 'PPTX Converter' per il file; il PDF su richiesta)". Summary:"Verifica finale della presentazione Emblema: <N> controlli, <E> errori, <W> warning."in the user's language. On anyerrorthe user picks what to fix — wait for that selection, then re-run the relevant Phase 4 steps. If all pass, add a one-line wrap-up: N topics, M_x slides per topic, total slides =actual_slide_count, the occupied-pin count measured at 14a, NOT the plannedtotal_slides + 2, theme = Emblema. WhenpageBudgetwas set, state requested vs delivered:"Richieste <pageBudget> pagine — consegnate <actual_slide_count>."If those differ, that IS the headline — lead with it, do not describe the run as successful. If published: mention the/s/{slug}URL and that the PPTX can be generated on request ("esegui PPTX Converter"). If draft: say so explicitly and how to publish ("esegui Public Share") or download ("esegui PPTX Converter"). Either way: name the.pdfwhen step 17a built it, else say the PDF is available on request ("chiedimi il PDF").verificationis the terminal action — do NOT callfollowUpafter it.
Phase 4 complete — the step-18 verification audit is the terminal action of this template. Do NOT call followUp after it.
Common User Requests → Configuration Tips
For user-request → configuration mappings load the table: loadInstructionSection({ variableId: "reference.configTips" }) returns 14 { userSays, configure } rows — known-answer bypass, page budgets, image / style / publish phrasings, vague requests, topic and slides-per-topic counts, focus areas, content style, named slide types → template_name, image-heavy layouts, on-request PPTX, PDF export (also after the build). Match the user's phrasing to a userSays row and apply that row's configure text literally — do not guess a mapping from memory. (Its rows cite v1.8.0 step numbers; map them onto this version's steps by name, not by number.)
Error Handling
On any execution, wiring or count error load the table: loadInstructionSection({ variableId: "reference.errorHandling" }) returns 17 { error, cause, fix } rows — MANIFEST_NOT_FOUND, connection refused, empty slides, announced ≠ delivered slide count, delivered ≠ requested page count, no topics in output, missing "Contenuto Presentazione" content, slide_html → undefined, Make Array pin/schema desync, un-renamed clones, uniform emblema-bullets, theme drift, marina-* layouts, a plain or failed PPTX, and three PDF Builder rows (render failure, both sources wired, no source). Match the row whose error matches your symptom and apply its fix; do NOT invent a fix before loading the table. (Its rows cite v1.8.0 step numbers — "step 16a" is this version's 14a, "Phase 3B step 12/14b" is step 12 / step 13, "Phase 3A Step B" is step 12, "Phase 4 step 19" is step 17.)
// Dependencies
1from input import DriveFolderReader2from input import DriveContentReader3from process import TextSummarizer4from process import SlideGenerator5from process import ImageCreation6from process import MakeArray7from output import PresentationComposer8from output import PPTXConverter9from output import PublicShare10from output import PDFBuilder// Variables
1{2 "gate.pageBudget": {3 "note": "A free-text input is always rendered as the last option, so the user can type a specific number (e.g. \"7\") without any extra flag.",4 "questions": [5 {6 "key": "topicCount",7 "header": "Topic",8 "context": "Ogni topic produce uno o più slide. Il totale slide finale = Cover + (topics × slides per topic) + Closing.",9 "options": [10 {11 "id": "3",12 "label": "3 topic"13 },14 {15 "id": "5",16 "label": "5 topic"17 },18 {19 "id": "10",20 "label": "10 topic"21 },22 {23 "id": "auto",24 "label": "Automatico",25 "description": "Lascia decidere all'agente in base alla rilevanza e all'estensione dei documenti"26 }27 ],28 "question": "Quanti topic principali vuoi nella presentazione?"29 },30 {31 "key": "slidesPerTopic",32 "header": "Slide/topic",33 "context": "1 slide per topic = deck conciso. 2-3 slide per topic = deck dettagliato. Il numero finale di slide-content = topic × slide-per-topic.",34 "options": [35 {36 "id": "1",37 "label": "1 slide per topic"38 },39 {40 "id": "2",41 "label": "2 slide per topic"42 },43 {44 "id": "3",45 "label": "3 slide per topic"46 },47 {48 "id": "auto",49 "label": "Automatico",50 "description": "Lascia decidere all'agente per ogni topic in base alla complessità del contenuto"51 }52 ],53 "question": "Quante slide per ogni topic?"54 }55 ]56 },57 "gate.publishChoice": {58 "questions": [59 {60 "key": "publish",61 "header": "Pubblica",62 "context": "Se sì, una volta costruita e assemblata la presentazione l'agente esegue il nodo 'Public Share' che genera un URL pubblico /s/<slug> condivisibile. Il file PowerPoint (PPTX Converter) NON viene generato automaticamente — resta disponibile su richiesta separata. Anche il PDF della presentazione si ottiene su richiesta.",63 "options": [64 {65 "id": "publish",66 "label": "Sì, pubblica automaticamente quando pronta"67 },68 {69 "id": "draft",70 "label": "No, lascia la presentazione in bozza"71 }72 ],73 "question": "Quando la presentazione è pronta, vuoi pubblicarla automaticamente (link condivisibile via Public Share)?"74 }75 ]76 },77 "reference.configTips": [78 {79 "userSays": "Any request that already answers a gate question (size, images, style, publish, PDF, focus)",80 "configure": "**Known-answer bypass.** Phase 1 step 4a extracts every explicitly-stated answer into `known.<key>`; 4b asks ONLY the keys still `null`, and skips the `requestUserDecision` call entirely when nothing is left. Trigger phrases per key: the 3a-i extraction table."81 },82 {83 "userSays": "\"un PPT di N pagine\" / \"N 页的 PPT\" / \"presentazione da N slide\" / \"an N-page deck\"",84 "configure": "**Page budget.** 3a-i: `pageBudget = N`, `content_slides = N − 2`, default `budgetTopics = N − 2`, `budgetSlidesPerTopic = 1` → resolves BOTH `known.topicCount` and `known.slidesPerTopic`, so **neither is asked at 3a-ii**; step 4a carries them, step 6 forces `total_slides = N − 2`. Delivered deck = exactly N pages. **Never treat N as a topic count — that yields N+2 pages.**"85 },86 {87 "userSays": "\"senza immagini\" / \"solo testo\" / \"不要图片\" · \"con immagini fotografiche\" / \"真实照片\" · \"pubblicala\" / \"share it publicly\" / \"发布\" · \"lasciala in bozza\" / \"keep it a draft\"",88 "configure": "4a sets, respectively: `known.useImages = \"no\"` (and `imageStyle` becomes moot — dropped from the gate too); `known.useImages = \"yes\"` + `known.imageStyle = \"photo\"` (an explicit style implies images); `known.publish = \"publish\"` — this IS the user's explicit publish choice, so Phase 4 step 17 runs \"Public Share\" with no second prompt; `known.publish = \"draft\"`. Any key 3a-i resolves is NOT asked at 3a-ii."89 },90 {91 "userSays": "\"circa N pagine\" / \"N-M pagine\" / \"un deck breve\" / \"qualche immagine\"",92 "configure": "Approximate, ranged or conditional — NOT known (explicit-only rule). Leave the key `null` and ASK at 3a-ii. Do NOT invent a value from a fuzzy request."93 },94 {95 "userSays": "(no count specified)",96 "configure": "Default mode: 1 slide per topic; leave the Topic Selector prompt at its default. Total content slides = N (number of topics)."97 },98 {99 "userSays": "\"X topic, una slide ciascuno\"",100 "configure": "Default mode. Topic Selector: optionally restrict to N=X via the prompt (`\"Seleziona X topic principali...\"`). Total content slides = X."101 },102 {103 "userSays": "\"X topic con Y slide ciascuno\"",104 "configure": "Topic Selector prompt → `\"Seleziona X topic principali e per ciascun topic seleziona Y temi (slides) rilevanti\"`. Total content slides = X × Y."105 },106 {107 "userSays": "\"Y slide per topic\" / \"Più slide per topic\" / \"Meno slide\"",108 "configure": "Topic Selector: set, raise or lower the slides count in the prompt (`\"per ciascun topic seleziona Y temi (slides)\"`); the default fallback is 1 slide per topic."109 },110 {111 "userSays": "\"Focus on specific topics\"",112 "configure": "Topic Selector prompt → `\"Focus on [topic area] only\"`."113 },114 {115 "userSays": "\"Change content style\"",116 "configure": "Update the \"Contenuto Presentazione\" prompt with the formatting preferences."117 },118 {119 "userSays": "The user names a slide type (\"add a timeline\", \"use KPI cards\" / \"metric dashboard\", \"before vs after\", \"add the team\", \"customer quote\", \"pricing slide\", \"show the roadmap\", \"code example\", \"definition slide\", \"big question\", \"feature grid\", \"logo cloud\", \"section divider\")",120 "configure": "Assign the matching `template_name` to that slide in Phase 3 (per-item `config` in `expandPrototype`), patching post-hoc with `upsertNode` if needed: timeline → `emblema-timeline`; KPIs → `emblema-kpis` (3-up) or `emblema-stat` (single hero number); comparison → `emblema-compare` (binary) or `emblema-matrix` (multi-option); team → `emblema-team`; quote → `emblema-testimonial`; pricing → `emblema-pricing`; roadmap → `emblema-roadmap` (Now/Next/Later) or `emblema-timeline`; code → `emblema-code`; definition → `emblema-definition`; question → `emblema-question`; feature grid → `emblema-features` (3×2 for 6 features); logo cloud → `emblema-logos`; section divider → an extra `(topic, slide)` mapping-table row with `emblema-section`."121 },122 {123 "userSays": "\"Add images\" / \"more visual deck\" / \"con immagini\"",124 "configure": "Pick image-heavy layouts in Phase 3 (per-item `config` in `expandPrototype`) from the slide's content shape: `emblema-image`, `emblema-magazine`, `emblema-two-img`, `emblema-grid-img`, `emblema-hero-centered`, `emblema-hero-card`, `emblema-collage`. Unlike Marina, Emblema supports images natively — no theme extension needed. Only the layouts marked **yes** in the Img column actually receive a generated image."125 },126 {127 "userSays": "\"Genera il PPTX\" / \"download the PowerPoint\"",128 "configure": "Execute the PPTX Converter explicitly — it is never auto-run — not part of Phase 4; run it only on a separate explicit user request."129 },130 {131 "userSays": "\"Esportala in PDF\" / \"dammi il PDF\" / \"download as PDF\" / \"导出PDF\"",132 "configure": "In the build request itself it is `known.pdf = \"yes\"` (step 4a), and steps 15a + 17a build it. Asked after the build (an edit turn): run step 15a — resolve \"PDF Builder\", or auto-create it with `upsertNode({ templateId: \"0e13b315-0a58-429a-8cee-75110496a9b7\", label: \"PDF Builder\" })`, and wire ONLY Presentation Composer `presentation` → its `presentation` — then `nodeExecutor` it. No gate, no other phase. Never wire its `pptx` input here, and never run the PPTX Converter to make a PDF."133 }134 ],135 "gate.contentQuestions": {136 "questions": [137 {138 "key": "useImages",139 "header": "Immagini",140 "context": "Se sì, le slide con layout compatibile ricevono un nodo di generazione immagine collegato. Se no, il deck è solo testo e grafica vettoriale Emblema.",141 "options": [142 {143 "id": "yes",144 "label": "Sì, genera immagini per le slide"145 },146 {147 "id": "no",148 "label": "No, solo testo e grafica vettoriale"149 }150 ],151 "question": "Vuoi includere immagini generate automaticamente nelle slide?"152 },153 {154 "key": "imageStyle",155 "header": "Stile img",156 "context": "Usata solo se la risposta precedente è \"sì\": guida tutti i prompt di generazione immagine. Diagrammatico = visualizzazioni con icone, testo etichette e schemi vettoriali. Fotografico = immagini realistiche di persone, ambienti e atmosfere, senza grafica vettoriale.",157 "options": [158 {159 "id": "photo",160 "label": "Fotografico",161 "description": "Persone, ambienti, mood realistici. Niente icone, testo o grafica vettoriale. Ottimo per contenuti editoriali e narrativi."162 },163 {164 "id": "diagram",165 "label": "Diagrammatico",166 "description": "Diagrammi, icone, testo etichette, schemi vettoriali. Ottimo per contenuti tecnici, dati, processi."167 }168 ],169 "question": "Se generi immagini, che stile vuoi per le slide?"170 }171 ]172 },173 "reference.errorHandling": [174 {175 "fix": "the user must upload them to a KnowledgeBase first.",176 "cause": "Documents not processed",177 "error": "**MANIFEST_NOT_FOUND**"178 },179 {180 "fix": "check `EMBLEMA_API_BASE_URL`.",181 "cause": "www-emblema not reachable",182 "error": "**Connection refused**"183 },184 {185 "fix": "Verify the \"Contenuto Presentazione\" `[summary]` → each Slide Generator `[content]` edge (created by `expandPrototype`'s `feed` at Phase 3 — check C2), and that the \"Contenuto Presentazione\" prompt lists all topics and slides with their full names.",186 "cause": "Cloned Slide Generators do NOT inherit external edges.",187 "error": "**Empty slides**"188 },189 {190 "fix": "Step 16a exists to catch exactly this: if you reached the summary without printing 16a's roster table, you skipped it. Never state a slide count you have not measured from occupied pins.",191 "cause": "Make Array had empty pins — typically only `item_0` (#Cover) and the last pin (#Closing) occupied, because Step D's `batchUpsertEdges` was never issued, was issued with an empty/partial edges array, or targeted clone IDs never created in Step A.",192 "error": "**Announced slide count ≠ delivered slide count** (e.g. \"10 slide totali\" reported, 2 slides in the deck)"193 },194 {195 "fix": "3a-i must set `pageBudget` / `budgetTopics` / `budgetSlidesPerTopic` and print the budget line; step 4a must resolve `auto` → budget; step 6 must reconcile `total_slides` to `pageBudget − 2`. Related: the unit error — answering the gate with the raw page number as a topic count (\"5 pagine\" → \"5 topic\") yields `1 + 5 + 1 = 7` pages. `P` is a page total; `budgetTopics = P − 2` is the topic count.",196 "cause": "The page budget was not resolved at 3a-i, or was resolved then lost when both gate answers came back `auto`.",197 "error": "**Delivered page count ≠ requested page count** (asked for a 5-page PPT, got 14 / 10 / 2)"198 },199 {200 "fix": "Provide more source material or relax the prompt.",201 "cause": "\"Selezione dei Topic Principali\" returned nothing, or fewer slides per topic than expected — the document may lack content.",202 "error": "**No topics or slides in output**"203 },204 {205 "fix": "Verify the prompt matches the step-6 output exactly.",206 "cause": "Its prompt was not updated with all topics and slides, or the names are wrong → incomplete or mismatched slides.",207 "error": "**Missing \"Contenuto Presentazione\" content**"208 },209 {210 "fix": "Verify the Make Array `nodeId` is a valid UUID from `nodeFinder` and the `item_X` pin number is correct; always validate nodeIds before `upsertEdge`.",211 "cause": "Invalid `targetNodeId` or `targetPort`.",212 "error": "**Connected slide_html → undefined**"213 },214 {215 "fix": "Run the `updateNodeSchema` fallback at Phase 3 step 13 (`item_0 … item_<total_slides + 1>`); include **every** pin (it replaces rather than merges and prunes edges to any pin you omit) and never go below 10.",216 "cause": "An older build whose `upsertEdge` / `batchUpsertEdges` add the edge without declaring the pin — the node's `inputSchema` (which drives the visible pin count) was not synced.",217 "error": "**Make Array shows fewer pins than the edges wired into it** (>10 connections, 10 pins drawn)"218 },219 {220 "fix": "Call `upsertNode` (or Step B's `batchUpsertNodes`) with `label` for each clone.",221 "cause": "Cloning does NOT rename.",222 "error": "**Nodes still named `\"Topic #x - Slide #1\"` after the rename step**"223 },224 {225 "fix": "Patch each clone with `upsertNode` using the Layout picking rule; if even half the deck is on `emblema-bullets` you missed signals — re-read the slide names and apply the rule top-to-bottom.",226 "cause": "Step B's per-slide layout picking was skipped (checks C3/C4).",227 "error": "**All slides are `emblema-bullets` uniformly**"228 },229 {230 "fix": "`upsertNode({ nodeId: <found ID>, input: { theme: \"emblema\" } })`. Both must match (check C8).",231 "cause": "Presentation Composer or PPTX Converter `theme` is not `\"emblema\"` (e.g. `\"white\"` / `\"marina\"`)",232 "error": "**Theme drifted from emblema**"233 },234 {235 "fix": "`upsertNode` each `marina-*` to its Emblema equivalent: `marina-metrics` → `emblema-kpis`, `marina-timeline` → `emblema-timeline`, `marina-comparison` → `emblema-matrix` or `emblema-table`, `marina-content` → `emblema-bullets` or `emblema-two-col`.",236 "cause": "Marina layouts do NOT render with the Emblema theme — they expect Marina-specific CSS classes (`.card-grid`, `.timeline > .event`, `data-header-title`) Emblema does not provide.",237 "error": "**Wrong layout family (`marina-*` instead of `emblema-*`)**"238 },239 {240 "fix": "Plain look: tell the user the server's document-render needs the new image (an operator rebuild), and that a node created before 1.4.0 needs updating; nothing to change on the canvas. \"PPTX export failed\": re-run the PPTX Converter once; if it fails again, report the message to the user and leave the HTML deck / Public Share as the deliverable. Do NOT edit the converter's `theme` to work around either.",241 "cause": "Since PPTX Converter 1.4.0 document-render lays the deck out in Chromium, so the .pptx keeps the Emblema look and its pictures. A plain .pptx means the converter used its built-in fallback: a document-render image without `/v1/render/html-to-pptx`, or a deck the route refuses (too large, too many slides). \"PPTX export failed … run the node again\" means document-render is down, timed out or failed (1.4.1+ fails instead of falling back).",242 "error": "**PPTX looks plain** (white slides, generic colours, no pictures) or **\"PPTX export failed\"**"243 },244 {245 "fix": "The deck and the share link are unaffected. Run \"PDF Builder\" once more; still failing → report the code at step 18 and leave everything else as is. Never change a slide for it.",246 "cause": "document-render is down or too old for `/v1/render/html-to-pdf` (`status: 404`), or it could not print the deck (not a Reveal deck, no slides).",247 "error": "**PDF Builder fails with `RENDER_SERVICE_UNAVAILABLE` / `RENDER_FAILED`**"248 },249 {250 "fix": "`deleteEdge` the edge into `pptx`; keep Presentation Composer `presentation` → its `presentation` (step 15a), then run it again.",251 "cause": "Both its `presentation` and `pptx` inputs are wired.",252 "error": "**PDF Builder: \"Connect one source, not both\"**"253 },254 {255 "fix": "`upsertEdge` Presentation Composer `presentation` → PDF Builder `presentation` (step 15a), then run it again.",256 "cause": "Nothing is wired into its `presentation` input.",257 "error": "**PDF Builder: \"Required input not provided\"**"258 }259 ]260}$ git log --oneline
2.3.1: built on PPTX Converter 1.4.1: the .pptx keeps the deck's look (document-render lays it out in Chromium), the converter falls back to its built-in renderer only when the route cannot apply and fails clearly when document-render is down; the troubleshooting row for a plain or failed PPTX says so. 2.3.0: the deck is also delivered as a PDF. "PDF Builder" (1.1.0) prints the Composer's HTML deck; it ships in "Exports" and Phase 4 runs it when the request asks for a PDF, else on request. Built on Slide Generator 1.12.0, Presentation Composer 1.5.1 and PPTX Converter 1.4.0; first version built from repo sources. 2.2.0: a request that names no Drive folder or file builds the deck from the request itself (Source C); renamed "Presentazione PowerPoint". 2.1.0: the deck language follows the request (step 4c).
2.3.0: the deck is also delivered as a PDF. "PDF Builder" (1.1.0) prints the Composer's HTML deck; it ships in "Exports" and Phase 4 runs it when the request asks for a PDF, else on request. Built on Slide Generator 1.12.0, Presentation Composer 1.5.1 and PPTX Converter 1.4.0; first version built from repo sources. 2.2.0: a request that names no Drive folder or file builds the deck from the request itself (Source C); renamed "Presentazione PowerPoint". 2.1.0: the deck language follows the request (step 4c).
Phase-1 sub-step 4c: deckLanguage resolution + language input on content nodes (language-persistence). Requires Slide Generator >=1.10.0, Text Summarizer >=1.6.0.
2.0.4 (metadata-only): truthful marketplace description — PPTX export on request, publishing opt-in with draft default, images optional, Drive-folder input, topic selection — plus search tags. No canvas/instruction changes. Fixes model fit-judgment at the template-decision bump (gt10: deepseek rejected this template because the old description implied HTML-only output, forced publishing, and images).
v2.0.3: strip baked llmModel defaults from 6 LLM nodes (2 summarizers + 4 slide prototypes). The legacy name fell through to the LiteLLM-by-name route where reasoning cannot be disabled, leaking thinking transcripts into summaries (guard-test-8 root cause). Empty resolves to the platform default on the direct route.
v2.0.2: llmModelId must stay EMPTY (platform default resolves to the correct provider route); inventing a model name downgraded the LLM route and leaked reasoning transcripts into summaries (hollow-deck root cause, guard-test eval).
v2.0.1 — Node Reference field name corrected: llmModel → llmModelId (agents followed the doc into a validation error and self-corrected, wasting 2 steps/session).
v2.0.0 — instruction rewritten for the section-loading engine: phases as loadable sections with STEP 0 mandate, Phase 3 uses the atomic expandPrototype tool, gate payloads and reference tables moved to instruction variables. Requires engine with loadInstructionSection + expandPrototype.
v1.7.1 — label-tolerant composition audit: 16a and step-16 lookups accept #Cover/#Closing and #Intro/#Outro as role synonyms.
v1.7.0 — user-input composition (known-answer bypass incl. deterministic page-budget resolution) + blocking Make Array membership audit (step 16a) with honest-reporting rules.
Make Array pin-count fix: instructions now direct the agent to call `updateNodeSchema` after wiring more than 10 inputs into a Make Array node — added Phase 3 step 14b, a Phase 4 "inputSchema pin count" verification check, an Error Handling entry, and (Marina) Incremental-Edit re-sync notes — so the node renders the right number of pins instead of staying at its default 10. Also picks up the pending repo edit that drops the obsolete `allowOther` flag from the Phase-1 `requestUserDecision` examples (free-text is always rendered as the last option). No canvasData / nodeCodeData / schema changes.