Date: 2026-05-13 (depth mode, architecture investigation + targeted repair)
Two things: a complete architectural understanding of the skeleton-page bug, and one more hand-repair (the highest-scored enterprise-archetype broken page).
After iter 56 surfaced 63 broken pages and the hypothesis that they came from one shared template, iter 57 traced the build pipeline end to end and confirmed the root cause.
/Users/wes/factory-templates/:marketplace-or-tool.html (256 lines)enterprise.html (257 lines)technical.html (228 lines)service-business.html (239 lines)boutique-creative.html (199 lines)editorial.html (178 lines){{HERO_HEADLINE}}, {{HERO_DEK}}, {{BA_HEADLINE}}, {{BEFORE_LIST}}, {{JOURNEY_STEPS}}, {{USE_CARDS}}, {{FEATURES}}, {{CTA_HEADLINE}}, etc.audit-ai-placeholders.json: ``json { "PRODUCT_NAME": "Ledgerline", "HERO_HEADLINE": "Close every quarter without losing the weekend.", "HERO_DEK": "...", "PILLAR_1_VALUE": "78%", ... } ``
/Users/wes/factory-templates/_render.py (31 lines) substitutes via regex: ``python out = re.sub(r'\{\{([A-Z0-9_]+)\}\}', rep, tpl) left = re.findall(r'\{\{[A-Z0-9_]+\}\}', out) if left: print("WARN unfilled placeholders:", ...) out = re.sub(r'\{\{[A-Z0-9_]+\}\}', '', out) `` CRITICAL: when a placeholder is missing from the JSON, the render script substitutes it with EMPTY STRING. No error, no skip, just empty.
Only 2 placeholder JSONs exist in /Users/wes/factory-templates/:
audit-ai-placeholders.json (template/example data - actually contains "Ledgerline" content, suggests this was a sample)dispatch-ai-placeholders.json (the one we already know is working - dispatch-ai page is the "operator-quality" reference)For every other product that uses one of these archetype templates, the placeholders JSON was never created. The render script ran anyway, substituted empty string for every {{PLACEHOLDER}}, and produced a skeleton page.
By archetype, of the 61 broken product pages:
| Archetype | Broken count | Notes |
|---|---|---|
| marketplace-or-tool | 49 | 14 empty tags each - the largest cluster |
| enterprise | 6 → 5 (revops repaired this iter) | 5-6 empty tags |
| technical | 5 | Variable empty count |
| boutique-creative | 1 | Single outlier |
The right systemic fix has three parts:
Part 1: Generate placeholder JSONs from existing per-product data sources. Each product has:
/home/ubuntu/factory/director/brands/SLUG-brand.md (palette, voice, archetype, tagline)/srv/sites/factory/adoptability.json (Fermi numbers, ICP, GTM, TAM, scores)/srv/sites/factory/dossiers/SLUG/teaser.md (hand-written elevator pitch, skeptic memo, brand positioning - the OPERATOR-VOICE content we need)/home/ubuntu/factory/dossiers-private/SLUG/full.md (deeper hand-written sections)A generator script can pull from these sources to build a per-product placeholder JSON. The dossier teaser already contains operator-voice copy (elevator pitch, skeptic-memo excerpt) - that is the highest-quality input for hero deck and audience signals.
Part 2: Run _render.py per product. Existing script, no changes needed.
Part 3: Add validation to _render.py. Currently it warns about missing placeholders but produces broken output anyway. Should either error out or write to a broken.html quarantine path so cron operators see the failure.
Two reasons:
So I documented the architecture clearly + hand-repaired one more product. iter 58 or Wes can take the systemic fix with full context.
Live at https://wishdeal.com/factory/builds/revenue-operations-ai/. The enterprise template has a different skeleton shape than marketplace-or-tool: it has hero-card "Procurement at a glance", "Trusted by" logo row, security section, compare matrix, ICPs section, features, and CTA-final. 8 sections needed filling (counting sub-sections).
Notable repair decisions specific to the enterprise template:
<span class="logo-mark"> placeholders that would render as visual fake-logo boxes. Replaced the entire section with an honest framing: "This is a Wishdeal Factory listing. We have no customer logos to show yet, because we have no customers yet." Links to /factory/honest/. This is the same anti-fake-proof move as iters 53/54./srv/sites/factory/builds/revenue-operations-ai/index.html (8 section fills + procurement card completion + honest logo replacement)/Users/wes/factory-templates/marketplace-or-tool.html (256 lines, template structure documented)/Users/wes/factory-templates/enterprise.html (257 lines, template structure documented)/Users/wes/factory-templates/_render.py (31 lines, render logic documented)/Users/wes/factory-templates/audit-ai-placeholders.json (49 lines, sample placeholder structure documented)/Users/wes/factory-templates/dispatch-ai-placeholders.json (47 lines, sample placeholder structure documented)The fastest path to fix the remaining 60 broken pages is:
Option A: Systemic generator fix (1-2 iters, fixes all 60) Write a Python script in /Users/wes/factory-templates/ that:
/srv/sites/factory/adoptability.json and /home/ubuntu/factory/dossiers-private/*/full.mdarchetype matches one of the 6 templates AND whose page is currently skeleton-broken:<slug>-placeholders.json from the product's brand brief + dossier teaser + adoptability data_render.py <archetype>.html <slug>-placeholders.json <output>.htmlThis is the high-leverage move. ~1 iter to write + test + run.
Option B: Continued hand-repairs (~30 iters, fixes 60 at quality) Continue at 2 per iter. Higher quality per page, slower total throughput. The pattern is proven (iters 55, 56, 57 demonstrate it).
Option C: Hybrid Hand-repair the top 10 highest-Adoptability broken pages over the next 5 iters (depth). Then run a bulk generator on the remaining 50 (breadth). Best per-page quality at the top, while still clearing the backlog systemically.
I recommend Option C because the highest-Adoptability pages are the highest-traffic and most worth hand-quality work. The lower-Adoptability long tail benefits more from "non-skeleton" than from polish.
_render.py to fail loudly on missing placeholders, so future builds do not silently produce skeleton pagesThe factory has now systematically:
Iter 58 should pick the strategy from Option C above unless Wes has weighed in differently.