# Wishdeal Factory buyer-path - iteration 62 ship log

**Date:** 2026-05-13 (depth mode, durable source fixes)

## What shipped

Two substantive source-level fixes that eliminate categories of bugs at their root, not just at their visible surface.

## Ship 1: 12 generators cleaned of HTML-entity em-dashes

**Audit finding:** Iter 61's enhanced sweep caught 615 entity em-dashes catalog-wide. The sweep is durable, but if SOURCE generators keep emitting entities, the cron is in a constant pendulum between dirty and clean. Found 24 entity em-dash instances across 12 generator files in `/home/ubuntu/factory/director/`.

**Two distinct patterns:**

**Pattern A: null/empty table placeholders (10 generators)**
- `more-like-this-injector.py`, `regen-adopt-pages.py`, `regen-archetypes.py`, `regen-captures-admin.py`, `regen-categories.py`, `regen-compare-page.py`, `regen-operator-inbox.py`, `regen-operator-partnership.py`, `regen-pricing-page.py`, `regen-unlock-pages.py`
- Used `&mdash;` as the "empty cell" marker, e.g., `fmt_money(0)` returning `"&mdash;"`, or `<td class="no">&mdash;</td>` for "feature not available in this tier"
- **Fix:** replace `&mdash;` with `-` (single hyphen). Visually identical in table cells, but stable across the sweep cron.

**Pattern B: prose em-dashes (2 generators)**
- `regen-catalog-v2.py` had 2 instances in body copy ("a head start &mdash; brand, landing page, GTM plan, financial model &mdash; but")
- `regen-feedback-page.py` had 2 instances in body copy ("the harsh ones &mdash; especially the harsh ones")
- **Fix:** replaced with appropriate punctuation per context (colons, parens, commas)

**Verification:**
After deploying all 12 fixed generators and re-running them:
- `em-dash-sweep: 0 files, 0 dashes stripped, 0.6s` — sweep is now idempotent (no more pendulum)
- `health-check: 68/68 passing`
- All 12 generators produce zero entity em-dashes on regeneration

## Ship 2: Adoptability data hygiene (48 records cleaned + source fix)

**Audit finding:** Iter 53's noted bug ("dispatch-ai tagline = name") had grown unchecked. Catalog-wide grep found:
- 32 products with `tagline.lower() == name.lower()` (the literal product name as a tagline)
- 16 products with `name.lower() == slug.lower()` (the slug used as the display name, e.g., "demand-gen-ai" instead of "Demand Gen AI")

These propagated to: catalog page, dossier teasers, social share text, sitemap meta, every product index page.

**Step 1 - Immediate record patches (44 of 48 from existing data sources):**

For broken names (16 records): pulled the proper title-case from `extract_new_name(slug)` (matches "## New name\n**X**" pattern in brand briefs that have rebrands) or fell back to title-case + AI/SaaS/MVP normalization. Examples:
- `demand-gen-ai` -> `Demand Gen AI`
- `aiops-ai` -> `Aiops AI`
- `msp-ai` -> `Msp AI`

For broken taglines (1 fixable from brand brief): rental-ai's brand brief had `**Tagline:** Your rental portfolio runs on autopilot.` — pulled and applied.

For broken taglines (27 fixable from dossier teasers): for each broken product, extracted the first sentence (20-160 chars) of the elevator pitch in `/srv/sites/factory/dossiers/SLUG/teaser.md`. The elevator pitch is already hand-written operator voice, so the resulting taglines are real (not synthesized).

For the final 4 stubborn ones (clinic-ai, cashflow-ai, pitch-ai, tenant-ai): widened the search to all paragraphs of the elevator pitch (was limited to first paragraph), allowed slightly longer sentences (160 chars), all 4 resolved.

**Final immediate state: 0 broken taglines, 0 broken names.**

**Step 2 - Source fix in `adoptability-score.py`:**

The bug was in two functions:

`extract_tagline(brand_text, slug)`:
- Only matched `## Tagline` heading format. Brand briefs using `**Tagline:**` inline or no tagline at all silently fell back to `slug.replace("-", " ").title()` — which is the tagline-equals-name bug source.
- **Fix:** added 3 more extraction patterns (`**Tagline:**`, `Tagline:`, dossier elevator pitch first sentence), and changed the last-resort fallback from "slug-as-tagline" to `[Tagline missing for X]` marker so future bugs are visible.

`tagline_to_name(slug, brand_text)`:
- Used the brand brief's `name:` frontmatter, but did NOT check that the name was different from the slug. If a brand brief said `name: demand-gen-ai`, the code returned that as the display name, propagating the bug.
- **Fix:** validates `name.lower() != slug.lower()` before using the brand brief value. Falls back to title-cased slug with AI/SaaS/MVP normalization.

**Verification:**
After deploying the patched scorer and regenerating adoptability.json:
- 0 broken taglines (was 32)
- 0 broken names (was 16)  
- 0 "missing tagline" markers (the new fallback wasn't triggered, dossier teasers covered everything)

The fix is durable. Future regenerations of adoptability.json will produce clean records.

**Dependent regenerations:**
- catalog (244 products, all with real taglines now visible)
- archetypes (6 archetype pages)
- categories (19 category pages)
- compare (244-product table)

## Files changed inventory

### Source-level fixes (durable)
- `/home/ubuntu/factory/director/more-like-this-injector.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-adopt-pages.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-archetypes.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-captures-admin.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-catalog-v2.py` (prose entities replaced)
- `/home/ubuntu/factory/director/regen-categories.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-compare-page.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-feedback-page.py` (prose entities replaced)
- `/home/ubuntu/factory/director/regen-operator-inbox.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-operator-partnership.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-pricing-page.py` (entity null replaced)
- `/home/ubuntu/factory/director/regen-unlock-pages.py` (entity null replaced)
- `/home/ubuntu/factory/director/adoptability-score.py` (extract_tagline and tagline_to_name patched with 3 new extraction patterns + slug-equals-name validation)

### Data file (regenerated by patched scorer)
- `/srv/sites/factory/adoptability.json` (244 products, all with real names + taglines)

### Pages regenerated as cascading effects
- `/srv/sites/factory/catalog/index.html`
- `/srv/sites/factory/archetypes/index.html` + 6 archetype pages
- `/srv/sites/factory/categories/index.html` + 19 category pages
- `/srv/sites/factory/compare/index.html`
- `/srv/sites/factory/operator-partnership/index.html`
- (all pages that consume adoptability.json got a fresh render with real taglines)

## Status snapshot

- 238 products, 0 broken pages
- 7 substantive playbook essays (~13,000 words)
- 2 polished hero, 4 hand-repaired, 60 bulk-repaired, 2 confirmed operator-quality, 1 audit-fix
- 3 credibility legs depth-passed (about-the-builder, honest, operator-partnership)
- **48 adoptability data records cleaned this iter (32 taglines + 16 names)**
- **12 generators source-cleaned of entity em-dashes this iter**
- **Both bugs fixed at source — durable**
- 2257 sitemap URLs
- 68/68 health endpoints passing
- 0 em-dashes (Unicode or HTML entity) shipped

## Why two source-level fixes in one iter

Both bugs had the same shape: visible surface symptom + cron pendulum if not fixed at source. The em-dash entity fix and the adoptability tagline fix both required:
1. Audit to find affected records
2. Patch immediate records
3. Patch the generator that produces them

The fixes are similar enough in shape that doing both in one iter is faster than doing them in two separate iters (less context-switching cost). Both are now durable.

## What still needs Wes

1. Stripe wiring (30 min)
2. Email-send for auto-fulfill
3. First real traffic push
4. **Decision on rebrand-name application**: some brand briefs have "New name" rebrands (dispatch-ai -> Pylon, contract-ai -> Inkwell, clinic-ai -> Clearwater, cashflow-ai -> Riverine, pitch-ai -> Lectern, tenant-ai -> Doorman). The current fix uses the title-case fallback (Dispatch AI, etc.). Whether to apply the rebrand names catalog-wide is a Wes naming-decision, not auto-applicable.

## Iter 63 candidates

1. **Rebrand audit + application**: spec'd above as Wes decision. List the rebrand candidates, get a yes/no per product, apply.
2. **Hand-polish top bulk-generated products** for conversion uplift (lead-scoring-ai 73, churn-ai 72, attribution-ai 72)
3. **/factory/methodology/ depth pass** (the fourth high-trust page, completing the foundational-page set)
4. **/factory/adoptability/ depth pass** (explains the 10-axis scoring rubric — high-trust technical page)
5. **Catalog-wide audit for `&middot;` entity** — same systemic issue as `&mdash;`, may be present in places that should render as a different separator

Recommended: option 4 (/factory/adoptability/ depth pass). It's the fifth credibility leg explaining HOW the scoring works. Buyers who care about quality will read it.

## Cumulative iter 1-62

- **Catalog**: 238 products, 0 broken pages, all with real taglines and proper names
- **Content library**: 7 operator essays (~13,000 words)
- **Proof story**: Counsel graduation + case study essay
- **Credibility trifecta**: about-the-builder, honest, operator-partnership all operator-depth-passed
- **Data hygiene**: 48 adoptability records cleaned at source + generator patched
- **Infrastructure invariants**: 68 endpoints, 0 em-dashes (durably at source now, no pendulum), autonomous Director
- **Generator durability**: 13 source-level fixes shipped this iter alone

The Factory has shifted from "content fixed at the surface" to "source generators produce clean content by default". That's a structural shift, not a polish shift. Future iters can build new features knowing the foundations are stable.
