← back to Wallco Ai
fix(regenerate-reverse): fall back to stored prompt when source PNG missing
06ad933726bb2d0142b64238cf2a9744846e0171 · 2026-05-24 01:00:06 -0700 · Steve Abrams
The orphan-PNG sweep (commit 323e1fa) cleared ~33k pre-seamless artifacts
from data/generated/, but it also pruned final designs that the catalog
still references via local_path. As a result, clicking "↻ Reverse-Engineer
+ Regenerate Whole" in /admin/ghost-review on those designs returns
"source PNG missing on disk" and dead-ends the repair flow.
The regenerate-WHOLE flow doesn't strictly need the source — it exists to
start fresh. Step 1 was a Gemini-Vision pass to reverse-engineer a clean
prompt from the source PNG. Without the PNG, the next-best inspiration is
the stored generation prompt in spoon_all_designs.prompt — already known to
have produced this design once. Append the anti-defect directive and skip
straight to Step 2 (image gen).
Behavior:
- source exists → unchanged vision-reverse-engineered path
- source missing + d.prompt available → stored-prompt-fallback path
- source missing + no stored prompt → 404 with detail (genuinely unrecoverable)
Response now includes prompt_origin: 'vision-reverse-engineered' | 'stored-prompt-fallback'
so the UI can show which path ran.
Smart Fix (/api/design/:id/smart-fix) and Crop Fix
(/api/design/:id/crop-fix) still require the source PNG — those are
image-editing operations on the actual pixels, not regen-from-prompt.
Their 404 message stays. Recommended UI hint when source is missing: nudge
admin toward Reverse-Engineer + Regenerate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 06ad933726bb2d0142b64238cf2a9744846e0171
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 24 01:00:06 2026 -0700
fix(regenerate-reverse): fall back to stored prompt when source PNG missing
The orphan-PNG sweep (commit 323e1fa) cleared ~33k pre-seamless artifacts
from data/generated/, but it also pruned final designs that the catalog
still references via local_path. As a result, clicking "↻ Reverse-Engineer
+ Regenerate Whole" in /admin/ghost-review on those designs returns
"source PNG missing on disk" and dead-ends the repair flow.
The regenerate-WHOLE flow doesn't strictly need the source — it exists to
start fresh. Step 1 was a Gemini-Vision pass to reverse-engineer a clean
prompt from the source PNG. Without the PNG, the next-best inspiration is
the stored generation prompt in spoon_all_designs.prompt — already known to
have produced this design once. Append the anti-defect directive and skip
straight to Step 2 (image gen).
Behavior:
- source exists → unchanged vision-reverse-engineered path
- source missing + d.prompt available → stored-prompt-fallback path
- source missing + no stored prompt → 404 with detail (genuinely unrecoverable)
Response now includes prompt_origin: 'vision-reverse-engineered' | 'stored-prompt-fallback'
so the UI can show which path ran.
Smart Fix (/api/design/:id/smart-fix) and Crop Fix
(/api/design/:id/crop-fix) still require the source PNG — those are
image-editing operations on the actual pixels, not regen-from-prompt.
Their 404 message stays. Recommended UI hint when source is missing: nudge
admin toward Reverse-Engineer + Regenerate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
server.js | 96 ++++++++++++++++++++++++++++++++++++++-------------------------
1 file changed, 58 insertions(+), 38 deletions(-)
diff --git a/server.js b/server.js
index a5315ae..2f558f6 100644
--- a/server.js
+++ b/server.js
@@ -7146,44 +7146,64 @@ app.post('/api/design/:id/regenerate-reverse', express.json({ limit: '4kb' }), a
if (cached) d = { ...cached };
}
if (!d) return res.status(404).json({ error: 'design not found' });
- if (!d.local_path || !fs.existsSync(d.local_path)) {
- return res.status(404).json({ error: 'source PNG missing on disk' });
- }
- const srcB64 = fs.readFileSync(d.local_path).toString('base64');
-
- // Step 1: Vision describes the design as a clean prompt
- const describePrompt = (
- `You are reverse-engineering a wallpaper-design generation prompt from this image.\n` +
- `Write a 2-3 sentence prompt that another AI could use to generate a CLEAN version of this exact design.\n\n` +
- `Capture: the motifs (what's in the design), their colors, the layout/composition, the overall style/era.\n\n` +
- `CRITICAL: do NOT reproduce any visible defects. Specifically:\n` +
- ` - NO ghost layers (no faded duplicates of the same motif at lower opacity)\n` +
- ` - NO translucent overlays of the whole scene\n` +
- ` - NO unwanted seam-breaks at the tile edges\n` +
- ` - NO half-tones, NO atmospheric haze on flat designs\n` +
- `Describe the INTENDED design — what the artist would have wanted — then add the directive:\n` +
- `"Render as a flat single-layer screenprint with hard razor-sharp silhouette edges. Tileable repeat. No translucency, no duplicates, no fading, no atmospheric perspective."\n\n` +
- `Reply with ONLY the prompt text. No preamble, no markdown.`
- );
const t0 = Date.now();
- let describeJ;
- try {
- describeJ = await geminiCall({
- model: 'gemini-2.0-flash',
- parts: [
- { inline_data: { mime_type: 'image/png', data: srcB64 } },
- { text: describePrompt },
- ],
- note: 'regenerate-reverse-describe-' + id,
- });
- } catch (e) {
- if (e.code === 'NO_KEY') return res.status(500).json({ error: 'GEMINI_API_KEY not set' });
- return res.status(502).json({ error: 'vision describe failed: ' + e.message });
- }
- const newPrompt = (geminiText(describeJ) || '').trim();
- if (!newPrompt || newPrompt.length < 30) {
- return res.status(502).json({ error: 'gemini vision returned empty/short prompt: ' + newPrompt.slice(0, 200) });
+ const sourceMissing = !d.local_path || !fs.existsSync(d.local_path);
+ let newPrompt;
+ let promptOrigin;
+
+ if (sourceMissing) {
+ // Fallback path: source PNG was swept (orphan-PNG cleanup) — use the
+ // stored generation prompt directly. The point of regenerate-WHOLE is
+ // to start fresh, so the source PNG is inspiration, not a requirement.
+ // If there's no stored prompt either, we genuinely can't regenerate.
+ const storedPrompt = String(d.prompt || '').trim();
+ if (!storedPrompt || storedPrompt.length < 20) {
+ return res.status(404).json({
+ error: 'source PNG missing on disk AND no stored prompt to regenerate from',
+ local_path: d.local_path || null,
+ stored_prompt_len: storedPrompt.length,
+ });
+ }
+ newPrompt = storedPrompt +
+ '\n\nRender as a flat single-layer screenprint with hard razor-sharp silhouette edges. ' +
+ 'Tileable repeat. No translucency, no duplicates of motifs, no fading, no atmospheric perspective.';
+ promptOrigin = 'stored-prompt-fallback';
+ } else {
+ const srcB64 = fs.readFileSync(d.local_path).toString('base64');
+ // Step 1: Vision describes the design as a clean prompt
+ const describePrompt = (
+ `You are reverse-engineering a wallpaper-design generation prompt from this image.\n` +
+ `Write a 2-3 sentence prompt that another AI could use to generate a CLEAN version of this exact design.\n\n` +
+ `Capture: the motifs (what's in the design), their colors, the layout/composition, the overall style/era.\n\n` +
+ `CRITICAL: do NOT reproduce any visible defects. Specifically:\n` +
+ ` - NO ghost layers (no faded duplicates of the same motif at lower opacity)\n` +
+ ` - NO translucent overlays of the whole scene\n` +
+ ` - NO unwanted seam-breaks at the tile edges\n` +
+ ` - NO half-tones, NO atmospheric haze on flat designs\n` +
+ `Describe the INTENDED design — what the artist would have wanted — then add the directive:\n` +
+ `"Render as a flat single-layer screenprint with hard razor-sharp silhouette edges. Tileable repeat. No translucency, no duplicates, no fading, no atmospheric perspective."\n\n` +
+ `Reply with ONLY the prompt text. No preamble, no markdown.`
+ );
+ let describeJ;
+ try {
+ describeJ = await geminiCall({
+ model: 'gemini-2.0-flash',
+ parts: [
+ { inline_data: { mime_type: 'image/png', data: srcB64 } },
+ { text: describePrompt },
+ ],
+ note: 'regenerate-reverse-describe-' + id,
+ });
+ } catch (e) {
+ if (e.code === 'NO_KEY') return res.status(500).json({ error: 'GEMINI_API_KEY not set' });
+ return res.status(502).json({ error: 'vision describe failed: ' + e.message });
+ }
+ newPrompt = (geminiText(describeJ) || '').trim();
+ if (!newPrompt || newPrompt.length < 30) {
+ return res.status(502).json({ error: 'gemini vision returned empty/short prompt: ' + newPrompt.slice(0, 200) });
+ }
+ promptOrigin = 'vision-reverse-engineered';
}
// Step 2: Generate fresh image from the reverse-engineered prompt
@@ -7236,8 +7256,8 @@ RETURNING id`);
psqlExecLocal(`UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${newId}', is_published=TRUE WHERE id=${newId}`);
psqlExecLocal(`UPDATE spoon_all_designs SET is_published=FALSE WHERE id=${id}`);
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
- appendFixEvent({ kind: 'regenerate-reverse', src_id: id, new_id: newId, elapsed_s: elapsed, new_prompt: newPrompt.slice(0, 400) });
- return res.json({ ok: true, new_id: newId, new_prompt: newPrompt, elapsed_s: elapsed, src_id: id });
+ appendFixEvent({ kind: 'regenerate-reverse', src_id: id, new_id: newId, elapsed_s: elapsed, prompt_origin: promptOrigin, new_prompt: newPrompt.slice(0, 400) });
+ return res.json({ ok: true, new_id: newId, new_prompt: newPrompt, prompt_origin: promptOrigin, elapsed_s: elapsed, src_id: id });
} catch (e) {
console.error('[regenerate-reverse]', e);
return res.status(500).json({ error: e.message });
← b6c85f3 feat(ghost-review): bulk-select with marquee drag + Delete &
·
back to Wallco Ai
·
add dedupe-flagged-jsonl.js + watch-and-dedupe.sh a7d1745 →