pi-design-deck 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -114,6 +114,10 @@ function applyPreviewHtml(preview, previewHtml) {
114
114
  if (dataset.fonts) {
115
115
  preview.dataset.fonts = dataset.fonts;
116
116
  }
117
+ // Copy inline styles from source preview div
118
+ if (first.style.cssText) {
119
+ preview.style.cssText = first.style.cssText;
120
+ }
117
121
  preview.innerHTML = first.innerHTML;
118
122
  return;
119
123
  }
@@ -142,12 +146,12 @@ function optionHint(count) {
142
146
  return `Choose one - press ${parts.join(" ")} or click`;
143
147
  }
144
148
 
145
- function createOptionCard(option, slideId, generated) {
149
+ function createOptionCard(option, slideId, generatedBy) {
146
150
  const card = createElement("div", "option");
147
151
  card.setAttribute("role", "radio");
148
152
  card.setAttribute("aria-checked", "false");
149
153
  card.tabIndex = 0;
150
- if (generated) {
154
+ if (generatedBy !== false) {
151
155
  card.classList.add("option-generated");
152
156
  }
153
157
  card.dataset.value = option.label;
@@ -164,8 +168,10 @@ function createOptionCard(option, slideId, generated) {
164
168
  const label = createElement("span", "option-label", option.label);
165
169
  header.appendChild(label);
166
170
 
167
- if (generated) {
168
- header.appendChild(createElement("span", "badge-generated", "Generated"));
171
+ if (generatedBy !== false) {
172
+ const modelShort = generatedBy ? generatedBy.split("/").pop() : null;
173
+ const badgeText = modelShort ? `Generated by ${modelShort}` : "Generated";
174
+ header.appendChild(createElement("span", "badge-generated", badgeText));
169
175
  } else if (option.recommended) {
170
176
  header.appendChild(createElement("span", "rec-badge", "Recommended"));
171
177
  }
@@ -184,11 +190,47 @@ function createOptionCard(option, slideId, generated) {
184
190
  el.tabIndex = -1;
185
191
  });
186
192
 
193
+ // Footer contains aside (optional) + notes input
194
+ const footer = createElement("div", "option-footer");
195
+
187
196
  if (option.aside) {
188
197
  const aside = createElement("div", "option-aside");
189
- aside.innerHTML = escapeHtml(option.aside).replace(/\n/g, "<br>");
190
- card.appendChild(aside);
198
+ // Handle both actual newlines and literal \n sequences
199
+ aside.innerHTML = escapeHtml(option.aside).replace(/\\n/g, "<br>").replace(/\n/g, "<br>");
200
+ footer.appendChild(aside);
201
+ }
202
+
203
+ // Notes input for user instructions
204
+ const notesContainer = createElement("div", "option-notes");
205
+ const notesLabel = createElement("label", "option-notes-label", "Your notes (optional)");
206
+ const notesInput = createElement("textarea", "option-notes-input");
207
+ notesInput.placeholder = "Add notes...";
208
+ notesInput.rows = 1;
209
+ notesInput.dataset.slideId = slideId;
210
+ notesInput.dataset.optionLabel = option.label;
211
+
212
+ // Restore saved notes if this option was previously selected with notes
213
+ if (optionNotes[slideId]?.label === option.label && optionNotes[slideId]?.notes) {
214
+ notesInput.value = optionNotes[slideId].notes;
191
215
  }
216
+
217
+ notesInput.addEventListener("input", (e) => {
218
+ const value = e.target.value.trim();
219
+ if (value) {
220
+ optionNotes[slideId] = { label: option.label, notes: value };
221
+ } else if (optionNotes[slideId]?.label === option.label) {
222
+ delete optionNotes[slideId];
223
+ }
224
+ saveSelectionsToStorage();
225
+ });
226
+
227
+ // Prevent click from bubbling to card (which would select it)
228
+ notesInput.addEventListener("click", (e) => e.stopPropagation());
229
+
230
+ notesContainer.appendChild(notesLabel);
231
+ notesContainer.appendChild(notesInput);
232
+ footer.appendChild(notesContainer);
233
+ card.appendChild(footer);
192
234
 
193
235
  if (option.description) {
194
236
  card.setAttribute("title", option.description);
@@ -205,10 +247,33 @@ function createGenerateBar(slideId) {
205
247
  const bar = createElement("div", "gen-bar");
206
248
 
207
249
  const row = createElement("div", "gen-row");
250
+
251
+ // Generate button with separate count selector
252
+ const genGroup = createElement("div", "gen-group");
208
253
  const button = createElement("button", "btn-gen-more");
209
254
  button.type = "button";
210
255
  button.appendChild(createElement("span", "btn-gen-plus", "+"));
211
- button.appendChild(document.createTextNode(" Generate another option"));
256
+ button.appendChild(document.createTextNode("Generate"));
257
+
258
+ const countSelect = document.createElement("select");
259
+ countSelect.className = "gen-count";
260
+ countSelect.setAttribute("aria-label", "Number of options to generate");
261
+ [1, 2, 3].forEach((n) => {
262
+ const opt = document.createElement("option");
263
+ opt.value = n;
264
+ opt.textContent = n;
265
+ countSelect.appendChild(opt);
266
+ });
267
+
268
+ const countLabel = createElement("span", "gen-count-label", "option");
269
+ countSelect.addEventListener("change", () => {
270
+ const count = parseInt(countSelect.value, 10);
271
+ countLabel.textContent = count === 1 ? "option" : "options";
272
+ });
273
+
274
+ genGroup.appendChild(button);
275
+ genGroup.appendChild(countSelect);
276
+ genGroup.appendChild(countLabel);
212
277
 
213
278
  const regenButton = createElement("button", "btn-regen");
214
279
  regenButton.type = "button";
@@ -229,8 +294,8 @@ function createGenerateBar(slideId) {
229
294
  e.stopPropagation();
230
295
  });
231
296
 
232
- button.addEventListener("click", () => generateMore(button, slideId, input));
233
- row.appendChild(button);
297
+ button.addEventListener("click", () => generateMore(button, slideId, input, countSelect));
298
+ row.appendChild(genGroup);
234
299
  row.appendChild(regenButton);
235
300
  row.appendChild(input);
236
301
  bar.appendChild(row);
@@ -299,6 +364,22 @@ function createSummarySlide(index) {
299
364
  summaryGrid.id = "summary-grid";
300
365
  section.appendChild(summaryGrid);
301
366
 
367
+ // Final instructions textarea
368
+ const finalNotesContainer = createElement("div", "final-notes");
369
+ const finalNotesLabel = createElement("label", "final-notes-label", "Additional instructions (optional)");
370
+ finalNotesLabel.setAttribute("for", "final-notes-input");
371
+ const finalNotesInput = createElement("textarea", "final-notes-input");
372
+ finalNotesInput.id = "final-notes-input";
373
+ finalNotesInput.placeholder = "Add any final notes or instructions for implementation...";
374
+ finalNotesInput.rows = 2;
375
+ finalNotesInput.addEventListener("input", (e) => {
376
+ finalNotes = e.target.value.trim();
377
+ saveSelectionsToStorage();
378
+ });
379
+ finalNotesContainer.appendChild(finalNotesLabel);
380
+ finalNotesContainer.appendChild(finalNotesInput);
381
+ section.appendChild(finalNotesContainer);
382
+
302
383
  const submitButton = createElement("button", "btn-generate", "Submit Selections");
303
384
  submitButton.type = "button";
304
385
  submitButton.id = "btn-generate";
@@ -380,10 +461,18 @@ function createSummaryCard(slide) {
380
461
  const text = selectedOption.aside.length > 120
381
462
  ? selectedOption.aside.slice(0, 120).trimEnd() + "..."
382
463
  : selectedOption.aside;
383
- aside.textContent = text;
464
+ aside.innerHTML = escapeHtml(text).replace(/\\n/g, "<br>").replace(/\n/g, "<br>");
384
465
  card.appendChild(aside);
385
466
  }
386
467
  }
468
+
469
+ // Show user notes if present
470
+ const noteData = optionNotes[slide.id];
471
+ if (noteData && noteData.label === selectedLabel && noteData.notes) {
472
+ const notesDisplay = createElement("div", "summary-notes");
473
+ notesDisplay.innerHTML = `<span class="summary-notes-label">Your notes:</span> ${escapeHtml(noteData.notes)}`;
474
+ card.appendChild(notesDisplay);
475
+ }
387
476
  }
388
477
 
389
478
  return card;
@@ -74,7 +74,7 @@ function disableDeckInteractions() {
74
74
  document.querySelectorAll(".btn-gen-more, .btn-regen").forEach((button) => {
75
75
  button.disabled = true;
76
76
  });
77
- document.querySelectorAll(".gen-prompt").forEach((input) => {
77
+ document.querySelectorAll(".gen-prompt, .gen-count").forEach((input) => {
78
78
  input.disabled = true;
79
79
  });
80
80
  document.querySelectorAll(".model-pill, .model-list-item, .model-default-check").forEach((el) => {
@@ -98,7 +98,16 @@ async function submitDeck() {
98
98
  }
99
99
 
100
100
  try {
101
- await postJson("/submit", { token: sessionToken, selections });
101
+ // Build notes object with only notes for selected options
102
+ const notes = {};
103
+ for (const [slideId, noteData] of Object.entries(optionNotes)) {
104
+ if (noteData && selections[slideId] === noteData.label && noteData.notes) {
105
+ notes[slideId] = noteData.notes;
106
+ }
107
+ }
108
+ const payload = { token: sessionToken, selections, notes };
109
+ if (finalNotes) payload.finalNotes = finalNotes;
110
+ await postJson("/submit", payload);
102
111
  clearSelectionsStorage();
103
112
  isClosed = true;
104
113
  if (submitButton) {
@@ -242,33 +251,39 @@ function restoreGenerateButton(slideId) {
242
251
  const pending = pendingGenerate.get(slideId);
243
252
  if (!pending || pending.isRegen) return;
244
253
 
245
- if (pending.skeleton && pending.skeleton.parentElement) {
246
- pending.skeleton.remove();
254
+ // Clear timeout if any
255
+ if (pending.timeoutId) clearTimeout(pending.timeoutId);
256
+
257
+ // Remove all skeletons from DOM (query directly, don't rely on array)
258
+ const slideElement = document.querySelector(`.slide[data-id="${CSS.escape(slideId)}"]`);
259
+ if (slideElement) {
260
+ slideElement.querySelectorAll(".option-skeleton").forEach((skel) => skel.remove());
247
261
  }
262
+
248
263
  pending.button.classList.remove("loading");
249
264
  const plus = pending.button.querySelector(".btn-gen-plus");
250
265
  if (plus) plus.textContent = "+";
251
- if (pending.button.childNodes[1]) {
252
- pending.button.childNodes[1].textContent = pending.originalText;
253
- }
266
+ pending.button.lastChild.textContent = "Generate";
267
+
254
268
  if (pending.input && !isClosed) pending.input.disabled = false;
269
+ if (pending.countSelect && !isClosed) pending.countSelect.disabled = false;
255
270
 
256
271
  pendingGenerate.delete(slideId);
257
272
  }
258
273
 
259
- function updateDimmedStateAfterInsert(slideElement, slideId, insertedOption) {
274
+ function reapplySelectionAfterInsert(slideElement, slideId) {
260
275
  const selected = selections[slideId];
261
276
  if (!selected) return;
262
- insertedOption.classList.add("dimmed");
263
277
  const selectedEl = Array.from(slideElement.querySelectorAll(".option")).find(
264
278
  (el) => el.dataset.value === selected
265
279
  );
266
280
  if (selectedEl) {
267
281
  selectedEl.classList.add("selected");
282
+ selectedEl.setAttribute("aria-checked", "true");
268
283
  }
269
284
  }
270
285
 
271
- function insertGeneratedOption(slideId, option) {
286
+ function insertGeneratedOption(slideId, option, model) {
272
287
  const slide = slides.find((entry) => entry.id === slideId);
273
288
  if (!slide) return;
274
289
  slide.options.push(option);
@@ -281,9 +296,9 @@ function insertGeneratedOption(slideId, option) {
281
296
 
282
297
  optionsGrid.className = `options ${optionCountClass(slide.options.length, slide.columns)}`;
283
298
 
284
- const optionCard = createOptionCard(option, slideId, true);
299
+ const optionCard = createOptionCard(option, slideId, model);
285
300
  optionsGrid.appendChild(optionCard);
286
- updateDimmedStateAfterInsert(slideElement, slideId, optionCard);
301
+ reapplySelectionAfterInsert(slideElement, slideId);
287
302
  equalizeBlockHeights(slideElement);
288
303
 
289
304
  const pick = slideElement.querySelector(".slide-pick");
@@ -301,6 +316,7 @@ function replaceSlideOptions(slideId, newOptions) {
301
316
  slide.options = newOptions;
302
317
 
303
318
  delete selections[slideId];
319
+ delete optionNotes[slideId];
304
320
  saveSelectionsToStorage();
305
321
 
306
322
  const slideElement = document.querySelector(`.slide[data-id="${CSS.escape(slideId)}"]`);
@@ -309,16 +325,28 @@ function replaceSlideOptions(slideId, newOptions) {
309
325
  const optionsGrid = slideElement.querySelector(".options");
310
326
  if (!optionsGrid) return;
311
327
 
328
+ // Remove regeneration overlay and state
329
+ const overlay = optionsGrid.querySelector(".regen-overlay");
330
+ if (overlay) overlay.remove();
331
+ optionsGrid.classList.remove("regenerating");
332
+ optionsGrid.style.position = "";
333
+
312
334
  optionsGrid.innerHTML = "";
313
- optionsGrid.style.opacity = "";
314
- optionsGrid.style.pointerEvents = "";
315
335
  optionsGrid.className = `options ${optionCountClass(newOptions.length, slide.columns)}`;
316
336
 
317
337
  newOptions.forEach((option) => {
318
338
  const card = createOptionCard(option, slideId, false);
339
+ card.classList.add("option-regenerated");
319
340
  optionsGrid.appendChild(card);
320
341
  });
321
342
 
343
+ // Remove animation class after animation completes
344
+ setTimeout(() => {
345
+ optionsGrid.querySelectorAll(".option-regenerated").forEach((el) => {
346
+ el.classList.remove("option-regenerated");
347
+ });
348
+ }, 500);
349
+
322
350
  equalizeBlockHeights(slideElement);
323
351
 
324
352
  const pick = slideElement.querySelector(".slide-pick");
@@ -343,8 +371,37 @@ function connectEvents() {
343
371
  if (!payload || typeof payload.slideId !== "string" || !payload.option) {
344
372
  return;
345
373
  }
346
- restoreGenerateButton(payload.slideId);
347
- insertGeneratedOption(payload.slideId, payload.option);
374
+ const pending = pendingGenerate.get(payload.slideId);
375
+ const model = pending?.model || null;
376
+
377
+ // Remove one skeleton
378
+ if (pending?.skeletons?.length > 0) {
379
+ const skel = pending.skeletons.shift();
380
+ if (skel.parentElement) skel.remove();
381
+ }
382
+
383
+ insertGeneratedOption(payload.slideId, payload.option, model);
384
+
385
+ // Track received count and restore button when all received
386
+ if (pending) {
387
+ pending.receivedCount = (pending.receivedCount || 0) + 1;
388
+ if (pending.receivedCount >= (pending.expectedCount || 1)) {
389
+ restoreGenerateButton(payload.slideId);
390
+ } else {
391
+ // Reset timeout for next option
392
+ if (pending.timeoutId) clearTimeout(pending.timeoutId);
393
+ pending.timeoutId = setTimeout(() => {
394
+ const current = pendingGenerate.get(payload.slideId);
395
+ if (!current || current.isRegen) return;
396
+ const received = current.receivedCount || 0;
397
+ const expected = current.expectedCount || 1;
398
+ restoreGenerateButton(payload.slideId);
399
+ if (received < expected) {
400
+ showSaveToast(`Generated ${received} of ${expected} options`, true);
401
+ }
402
+ }, 30000);
403
+ }
404
+ }
348
405
  });
349
406
 
350
407
  events.addEventListener("generate-failed", (event) => {
@@ -358,6 +415,8 @@ function connectEvents() {
358
415
  restoreGenerateButton(payload.slideId);
359
416
  if (payload.reason === "timeout") {
360
417
  showSaveToast("Generation timed out — try again", true);
418
+ } else {
419
+ showSaveToast("Generation failed", true);
361
420
  }
362
421
  }
363
422
  });
@@ -387,6 +446,8 @@ function connectEvents() {
387
446
  restoreRegenButton(payload.slideId);
388
447
  if (payload.reason === "timeout") {
389
448
  showSaveToast("Regeneration timed out — try again", true);
449
+ } else {
450
+ showSaveToast("Regeneration failed", true);
390
451
  }
391
452
  }
392
453
  });
@@ -414,7 +475,7 @@ function connectEvents() {
414
475
  });
415
476
  }
416
477
 
417
- async function generateMore(button, slideId, input) {
478
+ async function generateMore(button, slideId, input, countSelect) {
418
479
  if (isClosed || pendingGenerate.size > 0) return;
419
480
 
420
481
  const slideElement = document.querySelector(`.slide[data-id="${CSS.escape(slideId)}"]`);
@@ -427,21 +488,42 @@ async function generateMore(button, slideId, input) {
427
488
 
428
489
  const prompt = input ? input.value.trim() : "";
429
490
  if (input) input.value = "";
491
+
492
+ const count = countSelect ? parseInt(countSelect.value, 10) || 1 : 1;
493
+
494
+ // Create skeletons for each option being generated
495
+ const skeletons = [];
496
+ for (let i = 0; i < count; i++) {
497
+ const skeleton = createElement("div", "option-skeleton");
498
+ optionsGrid.appendChild(skeleton);
499
+ skeletons.push(skeleton);
500
+ }
430
501
 
431
- const skeleton = createElement("div", "option-skeleton");
432
- optionsGrid.appendChild(skeleton);
433
-
434
- const originalText = button.childNodes[1] ? button.childNodes[1].textContent || "" : "";
435
502
  button.classList.add("loading");
436
503
  const plus = button.querySelector(".btn-gen-plus");
437
504
  if (plus) plus.textContent = "";
438
- if (button.childNodes[1]) button.childNodes[1].textContent = " Generating...";
505
+ button.lastChild.textContent = "Generating...";
439
506
  if (input) input.disabled = true;
507
+ if (countSelect) countSelect.disabled = true;
508
+
509
+ // Set timeout for generation (30s per option)
510
+ const timeoutId = setTimeout(() => {
511
+ const pending = pendingGenerate.get(slideId);
512
+ if (pending && !pending.isRegen) {
513
+ const received = pending.receivedCount || 0;
514
+ restoreGenerateButton(slideId);
515
+ if (received === 0) {
516
+ showSaveToast("Generation timed out — try again", true);
517
+ } else {
518
+ showSaveToast(`Generated ${received} of ${count} options`, true);
519
+ }
520
+ }
521
+ }, 30000 * count);
440
522
 
441
- pendingGenerate.set(slideId, { button, skeleton, originalText, input });
523
+ pendingGenerate.set(slideId, { button, skeletons, input, countSelect, model: selectedModel || null, expectedCount: count, receivedCount: 0, timeoutId });
442
524
 
443
525
  try {
444
- const body = { token: sessionToken, slideId };
526
+ const body = { token: sessionToken, slideId, count };
445
527
  if (prompt) body.prompt = prompt;
446
528
  if (hasModelBar) {
447
529
  body.model = selectedModel;
@@ -469,8 +551,21 @@ async function regenerateSlide(button, slideId) {
469
551
  const prompt = input ? input.value.trim() : "";
470
552
  if (input) input.value = "";
471
553
 
472
- optionsGrid.style.opacity = "0.4";
473
- optionsGrid.style.pointerEvents = "none";
554
+ // Create skeleton overlay
555
+ const optionCount = slide.options?.length || 2;
556
+ const colClass = optionsGrid.classList.contains("cols-3") ? "cols-3" :
557
+ optionsGrid.classList.contains("cols-1") ? "cols-1" : "cols-2";
558
+ const overlay = createElement("div", `regen-overlay ${colClass}`);
559
+ for (let i = 0; i < optionCount; i++) {
560
+ overlay.appendChild(createElement("div", "regen-skeleton"));
561
+ }
562
+ const status = createElement("div", "regen-status", "Regenerating options...");
563
+ overlay.appendChild(status);
564
+
565
+ // Position overlay relative to options grid
566
+ optionsGrid.style.position = "relative";
567
+ optionsGrid.classList.add("regenerating");
568
+ optionsGrid.appendChild(overlay);
474
569
 
475
570
  const originalText = button.textContent || "";
476
571
  button.classList.add("loading");
@@ -481,7 +576,7 @@ async function regenerateSlide(button, slideId) {
481
576
  const genMoreBtn = slideElement.querySelector(".btn-gen-more");
482
577
  if (genMoreBtn) genMoreBtn.disabled = true;
483
578
 
484
- pendingGenerate.set(slideId, { button, originalText, input, isRegen: true, optionsGrid, genMoreBtn });
579
+ pendingGenerate.set(slideId, { button, originalText, input, isRegen: true, optionsGrid, genMoreBtn, overlay });
485
580
 
486
581
  try {
487
582
  const body = { token: sessionToken, slideId };
@@ -501,7 +596,7 @@ function restoreRegenButton(slideId) {
501
596
  if (!pending || !pending.isRegen) return;
502
597
  pendingGenerate.delete(slideId);
503
598
 
504
- const { button, originalText, input, optionsGrid, genMoreBtn } = pending;
599
+ const { button, originalText, input, optionsGrid, genMoreBtn, overlay } = pending;
505
600
  if (button) {
506
601
  button.classList.remove("loading");
507
602
  button.disabled = false;
@@ -509,8 +604,11 @@ function restoreRegenButton(slideId) {
509
604
  }
510
605
  if (input) input.disabled = false;
511
606
  if (optionsGrid) {
512
- optionsGrid.style.opacity = "";
513
- optionsGrid.style.pointerEvents = "";
607
+ optionsGrid.classList.remove("regenerating");
608
+ optionsGrid.style.position = "";
609
+ }
610
+ if (overlay && overlay.parentElement) {
611
+ overlay.remove();
514
612
  }
515
613
  if (genMoreBtn) genMoreBtn.disabled = false;
516
614
  }
@@ -29,14 +29,14 @@ function optionTemplate(hasBlocks: boolean): string {
29
29
  function modelHints(generateModel?: string, thinking?: string, action?: string): string {
30
30
  if (!generateModel) return "";
31
31
  const verb = action === "replace-options" ? "replace-options" : "add-option";
32
- let hint = `\nGenerate ${verb === "replace-options" ? "options " : ""}using model "${generateModel}" via subagent({ agent: "generator", model: "${generateModel}", output: false }), then push with ${verb}.`;
32
+ let hint = `\nGenerate ${verb === "replace-options" ? "options " : ""}using deck_generate({ model: "${generateModel}", task: "..." }), then push with ${verb}.`;
33
33
  if (thinking && thinking !== "off") {
34
34
  hint += `\nUse thinking level: "${thinking}".`;
35
35
  }
36
36
  return hint;
37
37
  }
38
38
 
39
- export function buildGenerateMoreResult(slideId: string, slide: DeckSlide | undefined, prompt?: string, generateModel?: string, thinking?: string): string {
39
+ export function buildGenerateMoreResult(slideId: string, slide: DeckSlide | undefined, prompt?: string, generateModel?: string, thinking?: string, count: number = 1): string {
40
40
  const title = slide?.title ?? slideId;
41
41
  const context = slide?.context ? `\nContext: ${slide.context}` : "";
42
42
 
@@ -54,15 +54,22 @@ export function buildGenerateMoreResult(slideId: string, slide: DeckSlide | unde
54
54
  const { hasBlocks, formatHint } = formatHintForSlide(slide);
55
55
  const template = optionTemplate(hasBlocks);
56
56
  const userInstructions = prompt ? `\nUser instructions: "${prompt}"` : "";
57
+
58
+ const optionWord = count === 1 ? "option" : "options";
59
+ const callInstructions = count === 1
60
+ ? `YOU MUST generate one distinctive additional option and call design_deck with add-option.`
61
+ : `YOU MUST generate ${count} distinctive additional options. Call design_deck with add-option ${count} times (once per option).`;
57
62
 
58
63
  return (
59
- "The design deck is still open.\n\n" +
60
- `User requested another option for slide \"${title}\".${context}${userInstructions}\n\n` +
64
+ "The design deck is still open and waiting for your response.\n\n" +
65
+ `User clicked "Generate ${count} ${optionWord}" for slide \"${title}\".${context}${userInstructions}\n\n` +
61
66
  `Existing options:\n${existingText}\n\n` +
62
- `Generate one distinctive additional option and call design_deck again:${modelHints(generateModel, thinking, "add-option")}\n\n` +
67
+ `${callInstructions} ` +
68
+ `Do not skip this step or decide the user has enough options — they explicitly requested ${count === 1 ? "another one" : `${count} more`}.${modelHints(generateModel, thinking, "add-option")}\n\n` +
63
69
  `design_deck({\"action\":\"add-option\",\"slideId\":\"${slideId}\",\"option\":${JSON.stringify(template)}})\n\n` +
64
70
  `The option field must be a JSON string with: label, optional description, optional aside (explanatory notes below preview), and optional recommended.\n` +
65
- `${formatHint}`
71
+ `${formatHint}` +
72
+ (count > 1 ? `\n\nRemember: Call add-option ${count} times, each with a different option. Make each option distinctive.` : "")
66
73
  );
67
74
  }
68
75
 
@@ -75,9 +82,10 @@ export function buildRegenerateResult(slideId: string, slide: DeckSlide | undefi
75
82
  const userInstructions = prompt ? `\nUser instructions: "${prompt}"` : "";
76
83
 
77
84
  return (
78
- "The design deck is still open.\n\n" +
79
- `User requested ALL options be regenerated for slide \"${title}\".${context}${userInstructions}\n\n` +
80
- `Generate ${optionCount} fresh, distinctive options and call design_deck to replace all existing options:${modelHints(generateModel, thinking, "replace-options")}\n\n` +
85
+ "The design deck is still open and waiting for your response.\n\n" +
86
+ `User clicked "Regenerate all" for slide \"${title}\".${context}${userInstructions}\n\n` +
87
+ `YOU MUST generate ${optionCount} fresh, distinctive options and call design_deck with replace-options. ` +
88
+ `Do not skip this step — the user explicitly requested regeneration.${modelHints(generateModel, thinking, "replace-options")}\n\n` +
81
89
  `design_deck({\"action\":\"replace-options\",\"slideId\":\"${slideId}\",\"options\":\"[${template}, ...]\"})` +
82
90
  `\n\nThe options field must be a JSON string containing an array of ${optionCount} option objects.\n` +
83
91
  `Each option needs: label, optional description, optional aside, optional recommended, and either previewHtml or previewBlocks.\n` +