$ cat workspace-template.yaml

P

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.

Document Generation
#presentazione#presentation#slide#slides#powerpoint#pptx#pdf#deck

// Canvas Preview

canvas.flow

// Instruction

instruction.md

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

[A folder] "Seleziona Materiale Sorgente" (Drive Folder Reader) → "Leggi i Documenti disponibili" (Drive Content Reader)[B single file] "Leggi i Documenti disponibili" only (delete the folder reader)[C no source] delete BOTH readers; the request brief is the static `content` of the two nodes below  → "Selezione dei Topic Principali" (N topics × M_x slides, default M_x = 1)  → "Contenuto Presentazione" (ALL slide content, one pass)  → "#Cover" (emblema-cover)  → per (Topic #X, Slide #Y): "Topic #X - Slide #Y" (Slide Generator, emblema-* layout)       [useImages = yes: "Image - Topic #X - Slide #Y" (Image Creation) → its `image` input]  → "#Closing" (emblema-closing)  → "Make Array" (item_0 = #Cover → T1-S1 → … → TN-SM → last pin = #Closing)    → "Presentation Composer" (theme emblema) → "PPTX Converter" (theme emblema, NEVER auto-run)                                              → "Public Share" (the "execute me" target → /s/{slug}; run at the end of Phase 4 iff the user answered `publish`; auto-created if missing)                                              → "PDF Builder" (→ .pdf; end of Phase 4 iff the request asked for a PDF; auto-created if missing)

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 followUp anywhere in this template. Phase 4 ends with the verification audit at step 18 — the terminal action. Phase 4 has no user pauses: it acts deterministically on the Phase-1 gate answers.
  • After nodeExecutor the system notifies on completion — do NOT poll getNodeOutput in 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; nodeFinder shows 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.
  • applyCanvasData is used exactly once, at step 1. Later changes use expandPrototype or atomic tools (upsertNode, upsertEdge, deleteNode, deleteEdge) — instant, no applyCanvasData.

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 driveItemId takes the FOLDER id (not folderId), output driveItemIds.
  • "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 that content directly.
  • "Selezione dei Topic Principali" (Topic Selector, content → summary) — the only node that fixes the deck structure: topics AND slides per topic in one pass. Configure prompt (default "Seleziona i topic principali dei documenti e riassumi i punti chiave (topics), per ciascun topic riassumi i temi (slides) rilevanti"). Leave llmModelId EMPTY — 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. Configure prompt (rewritten at step 8); leave llmModelId EMPTY (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 to item_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 with nodeFinder, not sectionFinder); defaults template_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's image input). That internal edge IS preserved when both ids are cloned in one call; the external content edge is NOT — expandPrototype's feed re-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); expandPrototype grows any pin its own items need.
  • "Presentation Composer" (items → presentation) — Reveal.js HTML; configure title, theme (default "emblema"), transition (slide, fade, convex, concave, zoom, none). Source apps/workspace-service/app/templates/presentation_composer.py, theme assets apps/workspace-service/app/templates/themes/emblema/.
  • "PPTX Converter" (presentation → .pptx) — configure title and theme, which must match the Composer. Never auto-run. Source apps/workspace-service/app/templates/pptx_converter.py.
  • "Public Share" — the "execute me" target: template ID a3f1d8c2-6e4b-5a9d-b7c3-2f8e1a4d6b9c, category output, source apps/workspace-service/app/templates/public_share.py; takes the Composer's presentation on content, 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, category output, source node-templates/pdf-builder/; takes the Composer's presentation on its presentation input and returns pdf, named after the Composer's title, one page per slide printed as the deck shows on screen (vector text, 1920×1080). Wire ONLY presentation — its pptx input 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.

#LayoutPick when the slide is…
1emblema-definitiona defined term/concept
2emblema-questionone rhetorical / framing question
3emblema-statementone bold claim, pull quote, mission statement
4emblema-sectiona divider for a new major part — sparingly: after the cover, or per topic group when ≥6 topics
5emblema-codea code listing / API call / snippet
6emblema-timelinechronological — milestones, history, phases, dates
7emblema-roadmapa roadmap (now/next/later, quarters)
8emblema-processa methodology with numbered steps
9emblema-stata single hero metric
10emblema-kpisa dashboard of 3-9 paired KPIs
11emblema-comparebefore vs after / transformation / impact
12emblema-charta chart description / data visualization
13emblema-tablea financial / metrics table with totals
14emblema-matrixa feature/option comparison matrix
15emblema-speca technical spec sheet (key:value pairs)
16emblema-statusa project status with RAG indicators
17emblema-teama team intro (people, names, roles)
18emblema-testimoniala customer quote / endorsement
19emblema-logosa logo cloud / "trusted by" / partners
20emblema-uia product UI showcase / app screenshot
21emblema-features≥5-6 enumerated product features
22emblema-pricingpricing tiers
23emblema-two-colexplanation left, one supporting visual right
24photo-driven, imagery providedone dramatic image → emblema-image; magazine story with one photo → emblema-magazine; two paired images → emblema-two-img; centered hero → emblema-hero-centered; text + visual panel → emblema-two-col
24bphoto-rich, placeholder-onlyemblema-grid-img (2×2 grid), emblema-hero-card (hero + floating card), emblema-collage (3-image collage) — ONLY when a CSS placeholder is acceptable
25else — ordinary content, 2-4 bullet takeawaysemblema-bullets, the safe default workhorse
—emblema-agendaONLY when the user explicitly asks for an agenda slide (NOT per topic)
—emblema-cover / emblema-closingRESERVED for #Cover / #Closing — never a topic slide

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.

  1. applyCanvasData → IMMEDIATELY commit the template. Call it before any other tool call.
  2. 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.
  3. 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>" }), then nodeFinder → "Leggi i Documenti disponibili" and upsertNode the same driveItemId field there. C (no source) — nodeFinder → "Seleziona Materiale Sorgente" and "Leggi i Documenti disponibili", deleteNode BOTH, then ONE batchUpsertNodes setting the same input: { 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 line Nessun 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.
  4. Phase 1 gate. Do 4a, then 4b, then 4c. Never call requestUserDecision before 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):

  • topicCount and slidesPerTopic — "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", making imageStyle moot (leave null, never ask it); "con immagini", "with images", "加上图片" → "yes".
  • imageStyle — "foto realistiche", "photographic images", "真实照片" → "photo"; "diagrammi" / "schemi" / "icone", "diagrams", "示意图" → "diagram". Either also sets known.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, pdf stays null.

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, otherText accepted if it parses as a positive integer ≤ 50 else auto [auto]
  • slidesPerTopicChoice ← known.slidesPerTopic / gate answer, otherText accepted if it parses as a positive integer ≤ 10 else auto [auto]
  • useImagesChoice ← known.useImages / gate answer [no]
  • imageStyleChoice ← known.imageStyle, defaulting to photo when 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.

  1. Resolve the merged answers, then configure the topic selector. pageBudget set AND a choice still "auto" → take it from the budget (budgetTopics / budgetSlidesPerTopic). pageBudget set AND a choice is a number differing from the decomposition → keep the number and recompute pageBudget = 1 + topicCountChoice × slidesPerTopicChoice + 1, so the run and the final summary quote the deck size actually produced. pageBudget null → leave both as merged (auto genuinely means "you decide"). Print Struttura risolta: <topicCountChoice> topic × <slidesPerTopicChoice> slide = <pageBudget or "n/d"> pagine totali. Then nodeFinder → "Selezione dei Topic Principali": both still auto (only possible when pageBudget is 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 still auto set it anyway, with "i topic" / "i temi" — the default prompt talks about documents there are none of) with <N> = topicCountChoice (or "i topic" if auto), <M> = slidesPerTopicChoice (or "i temi"), appending when pageBudget is 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.
  2. nodeExecutor → "Selezione dei Topic Principali". Wait for completion; with a Drive source "Leggi i Documenti disponibili" runs as its dependency (Source C has none).
  3. getNodeOutput → store topics = [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 prompt M_x = 1 for every topic). Then:
    • If topicCountChoice is a specific integer (including one from the budget), cap N to it; if slidesPerTopicChoice is, cap every M_x to it. The user already chose at the gate — do NOT re-ask. If neither is set AND pageBudget is null, use ALL topics from the output — do NOT cap or reduce.
    • Budget reconciliation (MANDATORY when pageBudget is set) — after the caps, total_slides == pageBudget − 2 MUST hold, where total_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. Print Budget pagine: <pageBudget> richieste = 1 copertina + <total_slides> contenuto + 1 chiusura. total_slides after 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 or total_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).

CRITICAL: ALL topic and slide names used later MUST come from this step-7 output. Do NOT invent topic or slide names.

  1. 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.
  2. 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.

  1. nodeFinder → store: "Contenuto Presentazione" → contentId; "Make Array" → makeArrayId; always "Topic #x - Slide #1" → simpleProtoId; when useImagesChoice === "yes" also "Topic #x - Slide #1 - [With Image Input]" → imageSlideProtoId and "Image Creation - for Topic #x - Slide #1 - [With Image Input]" → imageCreationProtoId.
  2. 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_index from 0; pin = clone_index + 1 (item_0 is reserved for #Cover); a layout from the picking rule in ## Emblema Layout Selection (≥4 distinct layouts). Print the table, columns clone_index | pin | topic | slide | layout.

    11a — partition (MANDATORY when useImagesChoice === "yes"; skip entirely otherwise). Preserving each row's clone_index and pin, split into image_rows (the slides you decided deserve a generated image — design-intent list in ## Emblema Layout Selection) and text_rows (all the rest); text_rows.length + image_rows.length MUST equal total_slides. Print both lists with their counts and layouts. If image_rows is empty, run the No branch only.

  3. Expand — one expandPrototype call per prototype kind, every item carrying an explicit pin from its mapping-table row so the two calls interleave correctly in Make Array's pin order (without explicit pins the second call restarts at item_0 and overwrites the Cover). Text slides — all rows in the No branch, text_rows in the Yes branch (skip when empty):

    expandPrototype({  prototype: { nodeIds: ["<simpleProtoId>"] },  items: [    { label: "Topic #1 - Slide #1", pin: 1, config: { "<simpleProtoId>": { instruction: "Inserisci il contenuto del Topic #1 '<topic1_name>' - Slide #1 '<slides[1][1]>'", template_name: "emblema-statement" } } },    // …one entry per row, in clone_index order  ],  feed:      { fromNodeId: "<contentId>", fromHandle: "summary", toPrototypeNodeId: "<simpleProtoId>", toHandle: "content" },  collector: { nodeId: "<makeArrayId>", fromPrototypeNodeId: "<simpleProtoId>", fromHandle: "slide_html" },  layout:    { groupLabel: "Topic Slides" }})

    Image slides — Yes branch only, image_rows. Same shape with prototype: { nodeIds: ["<imageCreationProtoId>", "<imageSlideProtoId>"] } (BOTH ids in one array — that is what preserves each pair's internal Image Creation [image] → Slide Generator [image] edge), feed.toPrototypeNodeId and collector.fromPrototypeNodeId both "<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 from imageStyleChoice, 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: instruction MUST carry the exact topic and slide names (never the bare "Topic #1 - Slide #2"); template_name MUST be one of the 32 emblema content layouts (never emblema-cover / emblema-closing, never marina-*); every Image Creation clone MUST get a non-empty per-slide prompt in this same call. Keep each returned instances[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.label renames the clone directly; for the two-node image prototype it names a per-item wrapper section that the layout step 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. ONE batchUpsertNodes call over the image instances, 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" }.

  4. Wire #Closing — the one pin expandPrototype did 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): ONE updateNodeSchema({ nodeId: "<makeArrayId>", inputSchema: { type: "object", properties: { … }, required: [] } }) carrying one item_<i>: { type: "string", order: <i>, title: "Item <i+1>", portType: "Any" } for every i = 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.

  1. nodeFinder → "Make Array" and ALL Slide Generators (labels matching "Topic #", plus "#Cover" / "#Closing", accepting "#Intro" / "#Outro"). total_slides is the planned count fixed at step 7. RUN every check and FIX every failure here; at step 18 you REPORT each as a verification item.

    #Check → fix
    C1#Cover slide_html → item_0; if a topic slide overwrote it, reconnect #Cover and shift topic slides to start at item_1
    C2EACH "Topic #X - Slide #Y" has a content input (inputs: [] = EMPTY slide) → upsertEdge <contentId> summary → clone content
    C3EACH clone's template_name starts emblema-, never emblema-cover / emblema-closing → upsertNode; all-emblema-bullets means step 11 skipped layout picking, redo it
    C4≥4 distinct emblema layouts when total_slides >= 4 → re-apply the picking rule, patch with upsertNode
    C5EACH clone → item_<pin>, topic slides starting at item_1; outputs: [] = missing Make Array edge → upsertEdge it
    C6#Closing slide_html → Make Array's LAST pin, else no closing slide → fix immediately with upsertEdge
    C7Make Array's inputSchema lists ≥ total_slides + 2 item_* properties — it must display the pins, not merely have edges → run step 13's updateNodeSchema fallback
    C8Presentation Composer theme == "emblema" AND PPTX Converter theme == "emblema" → upsertNode the drifted node ("white" / "marina") back to "emblema"
    C9only when useImagesChoice === "yes" — EACH image-row clone has BOTH a content and an image input from its paired "Image - Topic #X - Slide #Y", and EACH Image Creation clone a non-empty per-slide prompt → missing edge: upsertEdge Image Creation clone image → Slide Gen clone image; empty prompt: upsertNode with the step-12 <IMAGE_PROMPT>

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.

  1. Resolve the Public Share node (deterministic, no pause). nodeFinder({ query: "Public Share" }): found → store its nodeId as <publicShareId>. Not found AND publishChoice === "publish" → auto-create with upsertNode({ templateId: "a3f1d8c2-6e4b-5a9d-b7c3-2f8e1a4d6b9c" }), store the returned nodeId, then upsertEdge({ sourceNodeId: "<Presentation Composer ID>", sourcePort: "presentation", targetNodeId: "<publicShareId>", targetPort: "content" }). Not found AND publishChoice !== "publish" → nothing to do; the deck stays in draft. Go to 15a either way. 15a. Resolve the PDF Builder (deterministic, no pause; only when pdfChoice === "yes", else go to 16). nodeFinder({ query: "PDF Builder" }): found → store its nodeId as <pdfBuilderId>; it must have exactly ONE incoming edge, Presentation Composer presentation → its presentation, and nothing on pptx → upsertEdge the missing one, deleteEdge any other. Not found → upsertNode({ templateId: "0e13b315-0a58-429a-8cee-75110496a9b7", label: "PDF Builder" }), store the returned nodeId, then upsertEdge({ sourceNodeId: "<Presentation Composer ID>", sourcePort: "presentation", targetNodeId: "<pdfBuilderId>", targetPort: "presentation" }). Go to 16.
  2. 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 — publishChoice was collected at the Phase-1 gate and pdfChoice extracted at 4a; steps 17 and 17a are the only conditional executions.
  3. 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 with RENDER_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.
  4. Final audit. verification → run the audit and present the checklist before summarizing (info ✓ for passes, warning/error for real issues): Composition (from 14a) — actual_slide_count == total_slides + 2 occupied pins, contiguous, no gaps or duplicates; info ✓ ONLY when 14a passed, else the error items from 14a(g). One item per check C1–C9 of the step-14 table (C9 only when useImagesChoice === "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 an error naming 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 any error the 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 planned total_slides + 2, theme = Emblema. When pageBudget was 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 .pdf when step 17a built it, else say the PDF is available on request ("chiedimi il PDF"). verification is the terminal action — do NOT call followUp after 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

requirements.py
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

variables.json
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

v2.3.1
HEAD
2026-10-26

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).

v2.3.02026-10-25

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).

v2.1.02026-08-16

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.

v2.0.42026-08-15

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.32026-08-14

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.22026-08-13

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.12026-08-12

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.02026-08-12

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.12026-08-05

v1.7.1 — label-tolerant composition audit: 16a and step-16 lookups accept #Cover/#Closing and #Intro/#Outro as role synonyms.

v1.7.02026-08-05

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.

v1.6.12026-05-19

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.