fix generate-all-steps: never overwrite existing content, right target per step

The previous version called target:'all' on every step unconditionally,
which caused the AI to rewrite steps that already had good titles and
descriptions — often making them worse or inaccurate.

New behaviour:
- Build a queue of only the steps that are actually missing content
  (placeholder title and/or empty description).
- Determine target per step: 'title', 'description', or 'all' — only
  fill what is blank; leave existing user-written text completely alone.
- If all steps already have titles and descriptions, show a toast and stop.
- Call reload() once at the end instead of patching this.steps mid-loop,
  so the editor and step list both update atomically from the store.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
2026-06-24 11:27:24 -05:00
co-authored by Claude Sonnet 4.6
parent c60b7247af
commit 2d1b474931
+31 -19
View File
@@ -228,36 +228,48 @@ class GuideEditor {
return; return;
} }
if (this.pendingSave) await this.flushStep(); if (this.pendingSave) await this.flushStep();
// Only fill fields that are actually empty — never overwrite user-written content.
const PLACEHOLDER_TITLES = new Set([
'', 'screen capture', 'window capture', 'region capture', 'capture', 'untitled step',
]);
const isEmptyDesc = (html) => !(html || '').replace(/<[^>]*>/g, '').trim();
const queue = this.steps
.map((step) => {
const titleEmpty = !step.title || PLACEHOLDER_TITLES.has(step.title.toLowerCase());
const descEmpty = isEmptyDesc(step.descriptionHtml);
if (!titleEmpty && !descEmpty) return null;
const target = titleEmpty && descEmpty ? 'all' : titleEmpty ? 'title' : 'description';
return { stepId: step.stepId, target };
})
.filter(Boolean);
if (!queue.length) {
this.onToast('All steps already have titles and descriptions.');
return;
}
if (button) setButtonLoading(button, true, 'Generating…'); if (button) setButtonLoading(button, true, 'Generating…');
let done = 0; let done = 0;
let failed = 0; let failed = 0;
const total = this.steps.length; const total = queue.length;
try { try {
for (const step of this.steps) { for (const { stepId, target } of queue) {
this.onToast(`AI: filling step ${done + 1} of ${total}`); this.onToast(`AI: filling step ${done + failed + 1} of ${total}`);
try { try {
const result = await api.ai.fillStep({ const result = await api.ai.fillStep({ guideId: this.guideId, stepId, target });
guideId: this.guideId, if (result?.ok) done++;
stepId: step.stepId, else failed++;
target: 'all',
});
if (result?.ok) {
done++;
// Keep the in-memory steps list fresh so subsequent steps see updated guide context.
const idx = this.steps.findIndex((s) => s.stepId === step.stepId);
if (idx >= 0) this.steps[idx] = result.step;
} else {
failed++;
}
} catch { } catch {
failed++; failed++;
} }
} }
// Reload the currently-visible step so the editor reflects its new text. // Reload re-fetches all steps from the store so the editor and list both reflect the new text.
if (this.selectedStepId) await this.reload(this.selectedStepId); await this.reload(this.selectedStepId);
const msg = failed const msg = failed
? `AI filled ${done} step${done === 1 ? '' : 's'} (${failed} failed).` ? `AI filled ${done} step${done === 1 ? '' : 's'} (${failed} failed).`
: `AI filled all ${done} step${done === 1 ? '' : 's'}.`; : `AI filled ${done} step${done === 1 ? '' : 's'}.`;
this.onToast(msg, failed ? { error: true } : undefined); this.onToast(msg, failed ? { error: true } : undefined);
} finally { } finally {
if (button) setButtonLoading(button, false); if (button) setButtonLoading(button, false);