data: salvage competitor-analysis pipeline from PR #27 (design dropped) - #35
data: salvage competitor-analysis pipeline from PR #27 (design dropped)#35kas1987 wants to merge 58 commits into
Conversation
- 24 brands, 233 classified body profiles across 6 ZELEX families - SQLite DB with competitor_rows, brand_summary, brand_geographic, market_tiers, market_regions, inventory_only tables - Pricing, quality signals, geographic metadata per brand - Irontech official + Tayu sitemap + Dollstudio multi-brand crawl - RealDoll 13-model inventory captured (classification pending) - Approved PDR-010 design spec added
Tayu is scraped via scrape_tayu() in main(); listing it as pending-source in UNAVAILABLE_COMPETITORS made every generated report mark Tayu as both covered and a gap simultaneously. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The two independent-catalog heatmap PNGs were force-added in e137d22 despite *.png being in .gitignore. The CI hygiene guard rejects image files in the tree. Removing them from the index; local copies remain. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… estimated bodies Gallery / character page (character.html): - Remove "· Slot N" from series eyebrow — series name only - Backdrop image now uses gallery[1] to differ from the hero - Image count derived from gallery.length (not stale static field) - Auto-rotate every 4.5 s with gold progress bar; pauses on hover - Lightbox with prev/next buttons, keyboard nav (←/→/Esc), click-outside close DB — estimated hero bodies for new families: - db/family_taxonomy.json: ZS165D (Sculpt) + ZC165D (Classic) added; member_counts set to 1 - db/body_profiles.json: full profile entries for ZS165D + ZC165D - db/body_measurements.json: estimated spec-cards (PDR-005/007 design targets) - db/character_profiles.json: persona entries for The Athlete + The Standard All four HTML pages (browse, contact, quiz, series) carry forward minor polish edits from prior session. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- assets/site.css: add isolation:isolate to .btn.concierge to contain
the z-index:-1 ::before pseudo-element within the button stacking context
- scripts/analyze_independent_groupings.py: use .as_posix() for
generated_from so the path uses forward slashes on Windows
- scripts/scrape_competitors.js: wrap scraping loop in try…finally so
browser.close() is guaranteed even on exception
- scripts/build_competitor_catalog_taxonomy.py:
- use `or ""` (not default "") on body_html/title/handle to guard
against explicit null values returned by the Shopify API
- add height sanity check (50–250 cm) in scrape_supplier to drop
mis-parsed values like 16 cm (Game Lady regression)
- include "hybrid" material in tpe_share_pct calculation
- wrap every network scrape call in main() with try/except so one
supplier failure does not abort the entire run
- docs/pdr/PDR-010-competitor-family-coverage-roi-validation.md:
- fix market concentration band table (90–100% header; correct
WM/XT/SE/6YE/AS Doll to 60–69% band; move Game Lady/Tayu/Jiusheng
out of <60% into 60–69%)
- recalculate ROI composites using the stated weight formula
(avail×2, conv×2, roadmap×1.5, others×1, total 10.5):
Muse 3.5→3.7, Icon 4.0→3.9, Siren 3.8→3.7,
Empress 2.3→2.0, Classic 3.4→3.1, Sculpt 2.3→2.0
- db/independent_catalog_groupings.json: fix Windows backslash in
generated_from path
- docs/research/source-log.md: correct Irontech URL to
https://www.irontechdoll.com/ (matches scraper endpoint)
- docs/research/competitor-family-coverage-matrix.md: fix Game Lady
height range from 16-171 cm to 156-171 cm (scraper mis-parse)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- quiz.html: recalculate fromSecond after fallback block so splitAt divider stays anchored to first runner-up character index - character.html resetBar(): guard inner rAF callback against autoTimer being cleared during the two-frame delay - character.html goTo(): set lbImgEl.alt dynamically using p.name and photo index for screen-reader accessibility - character.html openLightbox/closeLightbox: save lightboxAutoWasRunning before stopAuto() so auto-rotation only restarts if it was running - contact.html showError(): remove dead msgEl variable (assigned, never used) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… dashboard Adds the full CEO package for Howie's leadership review use: - docs/howie-ceo-package/howie-ceo-dashboard.html — standalone interactive Family Activation Simulator: 6 family cards with live KPI recalculation, health pill (HEALTHY/AT RISK/CRITICAL), scenario ROI, and decision brief links. All data labeled as analyst estimates; no server dependency. - docs/howie-ceo-package/00–05 — command brief, dedicated brief library, team execution guide, KPI scoreboard, deep-dive guide, decision log template. All files carry prominent ESTIMATES NOTICES (competitive research, not actuals). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erdependency analyses 03-like-kind-manufacturing-scenarios.md: identifies ZE T168E (dist 0.96 to Sculpt centroid) and ZG S155C/ZG162D as zero-to-minimal-tooling paths to seed Classic and Sculpt families. 04-cross-family-tooling-interdependencies.md: maps bridge frames across all 6 families, defines the convergence frame concept (H160, W57 activates Classic + Sculpt from one commission), identifies Sculpt/Icon bridge zone, and models the ~$200-350K cascade that eliminates both dead-end families while reinforcing Icon and Empress. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ckage Introduces the Build-to-Stage framework: Family × Component Layer × Demand Confidence Tier matrix showing how market intelligence (family centroids, dimensional distance scores, market gap coverage) feeds directly into pre-staged work orders. Distinguishes what current data enables vs. what requires customer/order data, and identifies Tier 1 pre-stage actions (ZE T168E for Sculpt, ZG S155C for Classic) that can start immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…c phases Covers 5 decision points — family activation paths, tooling investment options, manufacturing model (MTO vs. Build-to-Stage), data signal maturity, and family expansion sequence — each with Mermaid flowcharts showing pros/cons per path. Includes investment summary across Immediate / Q3 / Q3-Q4 / 2027 horizons and a single-table decision summary for Howie. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds interactive PDR-010 decision path section to howie-ceo-dashboard.html: - Mermaid v10.9.0 CDN + dark-theme initialization - Phase Map (full-width): P0 done → P1 active → P2-P4 planned/future - DP-1: Classic/Sculpt activation paths (A/B/C with verdicts) - DP-2: Q3 tooling investment options (Convergence/Bridge/Both) - DP-3: Make-to-Order → Build-to-Stage transition - DP-4: Family expansion sequence (Zone A/B, Empress, Siren) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Distinct 5-state trust chips, consistent accessible focus rings, reusable .panel / .field-* / .opt / .progress-* primitives, sticky compare-table, unified mobile spacing rhythm, extended reduced-motion coverage. PDR: docs/pdr/PDR-FE-002-luxury-design-system-v2.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds governed PDRs (PDR-FE-000..009), tickets (HZZ-FE-000..009), Claude Design prompts, and Command Center frontend queue JSON for the HowieZZ frontend rebuild. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single clear primary action (Find your match), three parallel buyer paths, canonical data-driven six-family rail (honest in-development for Classic/Sculpt), compare-bodies preview at first decision block, concierge closing CTA. Removed redundant .doors block and duplicate static family-chooser. PDR: docs/pdr/PDR-FE-001-homepage-concierge-landing.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-dead-end empty state (quiz/browse CTAs), selected-body chip strip with per-body remove + 0-4 counter, two-line plain-language buyer-read & handling rows, softened gold to atelier-neutral. Reuses FE-002 compare-table primitives. PDR: docs/pdr/PDR-FE-003-body-compare-ux.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace hardcoded family/weight maps with lookups into the loaded model (profiles + families): handling tiers derive from real weight_kg (28-39.9kg spread, dead >42kg bucket removed), buyer-read cites each body's silhouette/ target_buyer + WHR/BWR vs its family range, surfaces family_confidence wording (exact/near/loose) and "Estimated dimensions" note. Measurement-led per docs/body-family-method.md and body-family-copy-guide.md. PDR: docs/pdr/PDR-FE-003-body-compare-ux.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ranked top-3 family results with match %, measurement-led "why this matched" panel citing the winning family's real WHR/BWR ranges + user axis leaning, copy-guide voice, recommended character cards with per-body WHR/BWR, honest in-development routing (no dead-ends; nearest active family via the method's normalized-distance metric), concierge CTAs. PDR: docs/pdr/PDR-FE-005-quiz-match-results.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Explicit family/series/character taxonomy explainer; family availability derived from real characters.json membership (Available / Limited / In Development) rather than assumed; honest in-development cards for Classic & Sculpt and a limited-release note for Empress (n=1) instead of empty/dimmed grids; premium % + target buyer surfaced (measurement-led); concierge CTA paths from both pages into compare/contact with ?family= context. PDR: docs/pdr/PDR-FE-007-browse-family-ux.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
At-a-glance decision panel (silhouette, premium tier, match confidence, ratios vs family band), best-for + worth-knowing modules, weight-based handling meter (real weight_kg over 28-39.9kg), and compare/build-similar/ask-concierge routing cluster. Self-fetches character_profiles.json; copy synthesized from real data (positioning/energy/target_buyer); estimated + shoot-pending states stay honest. PDR: docs/pdr/PDR-FE-004-character-detail-conversion.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Private-consultation framing; context summary card adapting to ?id=/?family=/ ?b=/compare-handoff routes with WHR/BWR stats + family premium % from DB; silhouette-family intent field auto-selected from context; trust/discretion sidebar; made-to-order no-pressure copy; consultation-framed success/error. 18+ consent required, mailto fallback, and PII-free analytics all preserved. PDR: docs/pdr/PDR-FE-006-concierge-intake.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guided intake + live configurator prototypes from Claude Design, with porting plan into the ZX/site.css stack. Reference material for the contact.html intake port and the new configurator page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audience switch (New buyer / Collector) re-tailoring copy depth; six interactive buyer-fit fields (intended use, timeline, gradient realism scale, handling, shipping/privacy, customization swatches) writing non-PII hidden inputs to the existing submit payload; live "Your fit so far" sidebar. Reuses ZX prefill + family-premium framing; 18+ consent, mailto fallback, and PII-free analytics preserved. Prototype hex mapped to site.css variables. PDR: docs/pdr/PDR-FE-006-concierge-intake.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New configurator.html: parametric SVG silhouette redrawn live from each family's published WHR/BWR ranges (clamped to the family envelope), realism scale, skin/ hair/eye tinting, concierge tip popouts, live build summary, and a concierge handoff CTA to contact.html (?family= + non-PII config_summary). Classic & Sculpt labelled in-development (derived from zero live bodies). Boots via ZX like the other pages; site.js nav/footer gain a "Configure" link. PDR: docs/pdr/PDR-FE-006-concierge-intake.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Off-canvas hamburger drawer below 860px for the 9-link nav (desktop unchanged; same .links markup so active/focus/scroll-elevation keep working; no-JS safe); aria-expanded + Esc + scrim + scroll-lock. Hero and concierge/intent action clusters stack full-width with primary on top; tap targets >=44px. Reduced-motion disables drawer/scrim animation. PDR: docs/pdr/PDR-FE-008-mobile-navigation-cta-pass.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Auditor pass over all funnel pages (desktop + mobile Chrome screenshots). Verdict: PASS - 0 blockers, 0 major, 5 minor. Verified missing-image .monotile fallback, honest Classic/Sculpt in-development states, estimated- dimension disclosure, 18+/privacy, and the FE-008 mobile drawer. Minor issues logged as follow-up tickets HZZ-FE-010..013. Screenshots kept local (gitignored) to avoid the repo image guard. PDR: docs/pdr/PDR-FE-009-visual-qa-screenshot-review.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- M-1/FE-010: verified browse renders 76 cards (headless screenshot artifact, not a bug); add html[data-zx-loaded] CI readiness signal in ZX.load(). - M-2/FE-011: body overflow-x:hidden guard + quiz h1 mobile floor 36->28px. - M-3/FE-012: footer reconciled to "six silhouette families across four series". - M-4/FE-013: --muted #9a9a9a -> #ababab for small-label contrast comfort. PDR: docs/pdr/PDR-FE-009-visual-qa-screenshot-review.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconciliation: worktree-agent branches (782b510) are stale ancestors; fe-design vs feat/pdr-010 merges clean (0 conflicts); only PR #27 (pdr-001-design-system-v2) genuinely diverges. Recommends landing #34 as canonical design, salvaging #27's competitor-analysis data only. Live QA (real Chrome CDP @390px): overflow PASS all pages (canScrollX=no), mobile nav drawer PASS (open/Esc/scrim/close + active link), navToggle 44px. Closes FE-009 M-2 and M-5 with evidence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings ONLY the competitor-analysis data/pipeline scripts from feat/pdr-001-design-system-v2 (PR #27), stacked on the canonical design branch feat/howiezz-fe-design (PR #34). PR #27's competing visual design is intentionally NOT included. Salvaged (6 scripts, pdr-001 versions): - scripts/analyze_independent_groupings.py (Windows-safe path str()) - scripts/build_competitor_catalog_taxonomy.py (height guards, brand/material refinements, unavailable-dedup) - scripts/build_independent_db.py (updated source docstring: +BestRealDoll) - scripts/inspect_scrape.py (ROOT-relative path) - scripts/print_report.py (ROOT-relative path) - scripts/scrape_competitors.js (pipeline refactor) Skipped (identical on base, no duplication): - docs/agent-source-material/{README.md,handoff-prompt.md,manifest.json} - docs/PDR-005/006/007/010 analysis briefs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a major frontend redesign of the ZELEX Atlas, adding a live configurator, a body comparison tool, a community hub, and an upgraded guided concierge intake form, alongside automated packaging and CI validation workflows. The code review identified several critical issues, notably a bug in the comparison state logic where exceeding the 4-body limit results in silent failures and incorrect analytics tracking across multiple pages. Additionally, the contact page currently ignores the configurator's customization parameters, trailing slashes in URLs break page-view tracking, a global focus style causes visual glitches on rounded elements, and a minor PEP 8 indentation violation exists in the Python sanity script.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
I am having trouble creating individual review comments. Click here to see my feedback.
assets/site.js (122-129)
When the comparison list already contains 4 bodies, calling addCompareBody will append the new body to the array, but setCompareBodies will silently slice it back to 4 elements, discarding the newly added body. However, the function still returns added: true, which misleads the UI and analytics into thinking the body was successfully added. We should check the limit before attempting to add.
function addCompareBody(bodyCode) {
const code = String(bodyCode || '').trim();
if (!code || code === '.') return { added: false, bodies: getCompareBodies() };
const cur = getCompareBodies();
if (cur.includes(code)) return { added: false, bodies: cur };
if (cur.length >= 4) return { added: false, bodies: cur };
const next = setCompareBodies(cur.concat(code));
return { added: true, bodies: next };
}
body.html (287-294)
The comparison button is marked as done and updated to "Added to Compare" even if the limit of 4 bodies has been exceeded and the body was not actually added. We should conditionally update the UI based on result.added and notify the user if the limit is reached.
compareBtnEl.addEventListener('click', () => {
const result = ZX.addCompareBody(bc);
if (result.added) {
compareBtnEl.classList.add('done');
compareBtnEl.textContent = 'Added to Compare';
ZX.track('compare_add', { body_code: bc, family: family || '', series: series || '', compare_count: result.bodies.length, context: 'body_detail', source_page: 'body' });
} else if (result.bodies.includes(bc)) {
compareBtnEl.classList.add('done');
compareBtnEl.textContent = 'Added to Compare';
} else {
alert('Comparison list is full (maximum 4 bodies).');
}
});character.html (670-675)
Similar to the body detail page, the comparison button here is marked as done even if the limit of 4 bodies is exceeded. We should check result.added and alert the user if the limit is reached.
compareCharBtn.addEventListener('click', () => {
const result = ZX.addCompareBody(c.body_code);
if (result.added) {
compareCharBtn.classList.add('done');
compareCharBtn.textContent = 'Added to compare';
trackCompareAdd(result, 'character_detail');
} else if (result.bodies.includes(c.body_code)) {
compareCharBtn.classList.add('done');
compareCharBtn.textContent = 'Added to compare';
} else {
alert('Comparison list is full (maximum 4 bodies).');
}
});
browse.html (265-299)
The addCompareFromBrowse function duplicates the localStorage comparison logic instead of reusing the shared ZX.addCompareBody helper. This leads to the same silent failure where the button is marked as "Added to Compare" even if the limit of 4 bodies is exceeded. We should refactor this to use the shared helper.
function addCompareFromBrowse(characterId) {
if (!_model || !_model.byId || !_model.byId[characterId]) return;
const c = _model.byId[characterId];
const code = c.body_code || '';
if (!code) return;
const result = ZX.addCompareBody(code);
if (result.added) {
refreshCompareQuick();
ZX.track('compare_set_changed', {
action: 'add',
compare_count: result.bodies.length,
body_codes: result.bodies.join(','),
context: 'browse_list',
source_page: 'browse'
});
ZX.track('compare_add', {
body_code: code,
character_id: c.character_id,
family: (c.body && c.body.family) ? c.body.family : '',
series: c.series || '',
compare_count: result.bodies.length,
context: 'browse_list',
source_page: 'browse'
});
const btn = document.querySelector('[data-compare-char="' + characterId + '"]');
if (btn) {
btn.classList.add('done');
btn.textContent = 'Added to Compare';
}
} else if (result.bodies.includes(code)) {
const btn = document.querySelector('[data-compare-char="' + characterId + '"]');
if (btn) {
btn.classList.add('done');
btn.textContent = 'Added to Compare';
}
} else {
alert('Comparison list is full (maximum 4 bodies).');
}
}compare.html (223-231)
In compare.html, adding a body when the limit of 4 is already reached will silently discard the new body due to slice(0, 4), but still trigger the compare_add and compare_set_changed analytics events with the unsaved body code. We should guard against exceeding the limit and alert the user.
document.getElementById('addBody').onclick = () => {
const code = select.value;
const cur = selectedCodes().filter(Boolean);
if (cur.includes(code)) return;
if (cur.length >= 4) {
alert('Comparison list is full (maximum 4 bodies).');
return;
}
const next = cur.concat(code);
saveCodes(next);
ZX.track('compare_set_changed', { action: 'add', compare_count: next.length, body_codes: next.join(','), context: 'compare', source_page: 'compare' });
ZX.track('compare_add', { body_code: code, compare_count: next.length, context: 'compare', source_page: 'compare' });
paint(m);
};
contact.html (1027-1030)
The configurator page passes config_summary in the URL query string, but the contact page completely ignores it. This means the user's customized options (skin, eyes, hair, realism) are lost when they navigate to the contact page. We should parse config_summary and apply it to the FIT state.
if (typeof FIT !== 'undefined') {
var configSummary = ZX.qs('config_summary');
if (configSummary) {
var configObj = {};
configSummary.split(';').forEach(function(part) {
var kv = part.split('=');
if (kv.length === 2) configObj[kv[0]] = kv[1];
});
if (configObj.realism) {
var rIdx = ['Natural','Soft','Balanced','Bold','Dramatic'].indexOf(configObj.realism);
if (rIdx !== -1) FIT.pick('realism', rIdx);
}
if (configObj.skin) FIT.pick('skin', configObj.skin);
if (configObj.eye) FIT.pick('eye', configObj.eye);
if (configObj.hair) FIT.pick('hair', configObj.hair);
}
FIT.refresh();
}
assets/site.js (180-184)
If the browser path ends with a trailing slash (e.g., /browse/), location.pathname.split('/').pop() returns an empty string, causing the page view to fall back to 'index.html'. We should filter out empty segments to reliably extract the actual page name.
const segments = location.pathname.split('/').filter(Boolean);
const lastSegment = segments[segments.length - 1] || 'index.html';
const pageName = lastSegment.toLowerCase();
track('page_view', {
context: 'navigation',
source_page: pageName.replace('.html', '')
});assets/site.css (72)
Setting border-radius: 2px globally on :focus-visible overrides the original border-radius of any focused element. For example, rounded buttons (border-radius: var(--r-pill)) or cards (border-radius: var(--r-lg)) will suddenly snap to sharp 2px corners when focused, causing a jarring visual glitch. We should remove this property and let the outline naturally follow the element's existing border radius.
:focus-visible{outline:2px solid var(--gold);outline-offset:3px}scripts/analytics_event_sanity.py (207-211)
The indentation on lines 208 and 209 uses 6 spaces instead of the standard 8 spaces, violating PEP 8 style guidelines for consistent 4-space indentation.
if summary.get("total_events", 0) < args.min_total_events:
failures.append(
f"total_events below minimum: {summary.get('total_events', 0)} < {args.min_total_events}"
)
References
- Consistent indentation of 4 spaces per indentation level is required by PEP 8. (link)
There was a problem hiding this comment.
Pull request overview
This PR introduces a broader “ops + governance + community” slice (community hub pages, CI validators/guards, v2 HTML packaging determinism, analytics sanity tooling, and PDR/ticket recovery docs) while also carrying competitor-analysis pipeline script updates (path handling, taxonomy scrape refinements, and competitor scrape refactors).
Changes:
- Add data-driven Community + Community Events pages, plus community JSON contracts, validators, and CI enforcement.
- Add deterministic
v2 HTML/packaging automation (manifest + sha256) and a CI freshness guard. - Update competitor-analysis scripts (Python + Node) for path portability and scrape/taxonomy logic changes, and add analytics sanity suite tooling/docs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| v2 HTML/manifest.sha256 | Checksums for packaged HTML snapshot. |
| v2 HTML/manifest.json | Packaged HTML manifest (hashes/sizes/mtime). |
| v2 HTML/community.html | Packaged copy of community hub page. |
| v2 HTML/community-events.html | Packaged copy of community events page. |
| scripts/sync_community_channels_from_reference.ps1 | Sync/check-mode for community channels JSON. |
| scripts/scrape_competitors.js | Competitor scrape pipeline refactor. |
| scripts/scaffold_community_event.ps1 | Safe appender for community events entries. |
| scripts/run_analytics_sanity.ps1 | Wrapper to run analytics sanity checker. |
| scripts/run_analytics_sanity_suite.ps1 | Positive/negative analytics fixture suite runner. |
| scripts/run_analytics_sanity_ci_parity.ps1 | CI parity harness for analytics suite behavior. |
| scripts/refresh_v2_html.ps1 | Generates v2 HTML snapshot + manifest/hash. |
| scripts/print_report.py | ROOT-relative path resolution. |
| scripts/inspect_scrape.py | ROOT-relative path resolution. |
| scripts/build_independent_db.py | Source docstring update. |
| scripts/build_competitor_catalog_taxonomy.py | Scrape/taxonomy refinements + unavailable dedupe. |
| scripts/analyze_independent_groupings.py | Windows-safe path serialization in output JSON. |
| README.md | Docs updates (analytics config + PDR source-of-truth notes). |
| docs/tickets/HZZ-FE-013-small-label-contrast-pass.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-012-reconcile-families-series-copy.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-011-fix-mobile-horizontal-overflow.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-010-verify-browse-grid-headless-load.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-009-visual-qa-screenshot-review.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-008-mobile-navigation-and-cta-pass.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-007-improve-browse-and-family-ux.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-006-design-concierge-intake-ux.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-005-improve-quiz-match-result-funnel.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-004-upgrade-character-detail-conversion-ux.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-003-design-body-compare-ux.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-002-refine-luxury-design-system-v2.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-001-redesign-homepage-as-concierge-atlas-landing.md | New FE ticket doc. |
| docs/tickets/HZZ-FE-000-recover-frontend-pdr-source-of-truth.md | New FE ticket doc. |
| docs/research/v2-packaging-runbook.md | Runbook for v2 HTML packaging + CI freshness guard. |
| docs/research/SWOT-community-gap-2026-06-16.md | SWOT/gap writeup for community hub addition. |
| docs/research/community-kb-linkage.md | Notes tying community UI to KB provenance. |
| docs/research/community-data-contract.md | Contributor contract for community JSON data. |
| docs/prompts/claude-design/HZZ-FE-008-prompt.md | Prompt file for FE-008. |
| docs/prompts/claude-design/HZZ-FE-007-prompt.md | Prompt file for FE-007. |
| docs/prompts/claude-design/HZZ-FE-006-prompt.md | Prompt file for FE-006. |
| docs/prompts/claude-design/HZZ-FE-005-prompt.md | Prompt file for FE-005. |
| docs/prompts/claude-design/HZZ-FE-004-prompt.md | Prompt file for FE-004. |
| docs/prompts/claude-design/HZZ-FE-003-prompt.md | Prompt file for FE-003. |
| docs/prompts/claude-design/HZZ-FE-002-prompt.md | Prompt file for FE-002. |
| docs/prompts/claude-design/HZZ-FE-001-prompt.md | Prompt file for FE-001. |
| docs/prompts/claude-design/CLAUDE-DESIGN-BASE-CONSTRAINTS.md | Base constraints for Claude Design tasks. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/work_queue.json | Recovery-pack work queue. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/scripts/LOCAL_PR_COMMANDS.md | Local PR command notes for recovery. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/README.md | Recovery pack overview and sequence. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/research/source-log-template.md | Source log template. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/research/competitor-family-coverage-matrix-template.csv | Matrix CSV template. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-010-competitor-family-coverage-roi-validation.md | Recovery-pack PDR-010. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-009-six-family-product-taxonomy.md | Recovery-pack PDR-009. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-008-analytics-event-taxonomy.md | Recovery-pack PDR-008. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-007-seo-og-structured-data-build-layer.md | Recovery-pack PDR-007. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-006-concierge-intake-crm-ready-leads.md | Recovery-pack PDR-006. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-005-character-detail-premium-conversion.md | Recovery-pack PDR-005. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-004-quiz-match-score-funnel.md | Recovery-pack PDR-004. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-003-body-compare-tool.md | Recovery-pack PDR-003. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-002-homepage-concierge-landing.md | Recovery-pack PDR-002. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-001-luxury-design-system-v2.md | Recovery-pack PDR-001. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-000-recovery-and-source-of-truth.md | Recovery-pack PDR-000. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/handoffs/agent-handoff-packets.md | Role handoff packets. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/handoffs/agent-dispatch-board.md | Dispatch board. |
| docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/command-center/Command-Center-sync-plan.md | Command Center sync plan. |
| docs/pdr/PDR-implementation-audit.md | Implementation audit doc. |
| docs/pdr/PDR-FE-009-visual-qa-screenshot-review.md | FE QA PDR doc. |
| docs/pdr/PDR-FE-009-live-interaction-evidence.md | Live QA evidence capture. |
| docs/pdr/PDR-FE-008-mobile-navigation-cta-pass.md | FE-008 PDR doc. |
| docs/pdr/PDR-FE-007-browse-family-ux.md | FE-007 PDR doc. |
| docs/pdr/PDR-FE-006-concierge-intake.md | FE-006 PDR doc. |
| docs/pdr/PDR-FE-005-quiz-match-results.md | FE-005 PDR doc. |
| docs/pdr/PDR-FE-004-character-detail-conversion.md | FE-004 PDR doc. |
| docs/pdr/PDR-FE-003-body-compare-ux.md | FE-003 PDR doc. |
| docs/pdr/PDR-FE-002-luxury-design-system-v2.md | FE-002 PDR doc. |
| docs/pdr/PDR-FE-001-homepage-concierge-landing.md | FE-001 PDR doc. |
| docs/pdr/PDR-FE-000-frontend-source-of-truth-gate.md | Frontend PDR source-of-truth gate. |
| docs/pdr/PDR-analytics-smoke-test-checklist.md | Analytics smoke test checklist. |
| docs/pdr/PDR-analytics-sanity-thresholds.missing-keys.json | Negative fixture config. |
| docs/pdr/PDR-analytics-sanity-thresholds.json | Threshold config. |
| docs/pdr/PDR-analytics-sanity-thresholds.invalid.json | Negative fixture config. |
| docs/pdr/PDR-analytics-sample-events.ndjson | Analytics positive fixture. |
| docs/pdr/PDR-analytics-sample-events-low-attribution.ndjson | Analytics negative fixture. |
| docs/pdr/PDR-analytics-sample-events-broken.ndjson | Analytics negative fixture. |
| docs/pdr/PDR-analytics-event-taxonomy.md | Analytics taxonomy doc. |
| docs/pdr/PDR-analytics-dashboard-handoff-runbook.md | Dashboard handoff runbook. |
| docs/pdr/PDR-analytics-dashboard-cookbook.md | Dashboard cookbook. |
| docs/howie-ceo-package/README.md | CEO package index. |
| docs/howie-ceo-package/05-howie-decision-log-template.md | Decision log template. |
| docs/howie-ceo-package/04-howie-deep-dive-guide.md | Deep-dive guide. |
| docs/howie-ceo-package/03-like-kind-manufacturing-scenarios.md | Manufacturing scenario brief. |
| docs/howie-ceo-package/03-kpi-scoreboard-template.md | KPI template. |
| docs/howie-ceo-package/02-team-execution-guide.md | Execution guide. |
| docs/howie-ceo-package/01-dedicated-brief-library.md | Brief library index. |
| docs/howie-ceo-package/00-howie-ceo-command-brief.md | CEO command brief. |
| docs/handoffs/TASK-HZZ-P2-012-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-011-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-010-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-009-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-008-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-007-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-006-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-005-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-004-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-002-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P2-001-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P1-003-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P1-002-kickoff.md | Task kickoff doc. |
| docs/handoffs/TASK-HZZ-P1-001-kickoff.md | Task kickoff doc. |
| docs/design-packages/premium-intake/README.md | Premium intake design package doc. |
| docs/agents/customization-options-agent.md | Agent responsibilities doc. |
| db/community_events.json | Community events data. |
| db/community_channels.json | Community channels + provenance refs. |
| CONTRIBUTING.md | PDR authority/task gating guidance. |
| community.html | Community hub page. |
| community-events.html | Community events page. |
| CODEX.md | Codex PDR_PATH gate doc. |
| CLAUDE.md | Claude PDR_PATH gate doc. |
| body.html | “Add to Compare” wiring on body page. |
| AGENTS.md | Agent execution contract doc. |
| 404.html | Canonical/OG metadata additions. |
| .gitignore | Ignore rules updates (QA screenshots, zip allowlist). |
| .github/workflows/ci.yml | CI steps for community/packaging/analytics guards. |
| .github/scripts/validate-site.mjs | Auto-discover root HTML pages for validation. |
| .github/scripts/validate-pdr-path-task-files.mjs | Enforce pdr_path presence/existence in task docs. |
| .github/scripts/validate-community-events-data.mjs | Validate community events schema/targets. |
| .github/scripts/validate-community-data.mjs | Validate community channels schema/provenance. |
| .cursor/rules/pdr-path-required.mdc | Cursor rule enforcing PDR_PATH in prompts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| height = parse_number(r"Height:\s*([\d.]+)\s*cm", block) or parse_number(r"Body height[^\d]*([\d.]+)\s*cm", block) | ||
| if height is not None and height < 100: | ||
| height = None # guard against partial regex matches (e.g. "16.0" instead of "160") | ||
| weight = parse_number(r"Weight:\s*([\d.]+)\s*kg", block) or parse_number(r"weights?\s*(?:ca\.)?\s*([\d.]+)\s*kg", block) |
| material_text = " ".join((item.get("material") or "").lower() for item in items) | ||
| silicone_count = sum(1 for item in items if "silicone" in (item.get("material") or "").lower()) | ||
| tpe_count = sum(1 for item in items if any(k in (item.get("material") or "").lower() for k in ("tpe", "hybrid"))) | ||
| tpe_count = sum(1 for item in items if "tpe" in (item.get("material") or "").lower()) |
| rows = load_zelex_baseline() | ||
| for supplier in SUPPLIERS: | ||
| try: | ||
| rows.extend(scrape_supplier(supplier, session, families)) | ||
| except Exception as exc: | ||
| print(f"[main] scrape_supplier({supplier.brand}) failed: {exc}") | ||
| try: | ||
| rows.extend(scrape_irontech(session, families)) | ||
| except Exception as exc: | ||
| print(f"[main] scrape_irontech failed: {exc}") | ||
| try: | ||
| rows.extend(scrape_tayu(session, families)) | ||
| except Exception as exc: | ||
| print(f"[main] scrape_tayu failed: {exc}") | ||
| rows.extend(scrape_supplier(supplier, session, families)) | ||
| rows.extend(scrape_irontech(session, families)) | ||
| rows.extend(scrape_tayu(session, families)) | ||
| for sw_config in SW_COLLECTIONS: | ||
| try: | ||
| rows.extend(scrape_siliconwives(sw_config, session, families)) | ||
| except Exception as exc: | ||
| print(f"[main] scrape_siliconwives({sw_config.brand}) failed: {exc}") | ||
| try: | ||
| realdoll_inventory = capture_realdoll_inventory(session) | ||
| except Exception as exc: | ||
| print(f"[main] capture_realdoll_inventory failed: {exc}") | ||
| realdoll_inventory = None | ||
| rows.extend(scrape_siliconwives(sw_config, session, families)) | ||
| realdoll_inventory = capture_realdoll_inventory(session) | ||
|
|
| async function main() { | ||
| const output = { scraped_at: new Date().toISOString(), summary: [], total_products: 0, products: [] }; | ||
| const browser = await chromium.launch({ headless: true }); | ||
|
|
||
| try { | ||
| for (const target of TARGETS) { | ||
| console.log(`\n══ ${target.name} (${target.url}) ══`); | ||
| let products = []; | ||
| let method = 'shopify_api'; | ||
|
|
||
| try { | ||
| products = await fetchShopifyCatalog(target.url); | ||
| console.log(` Shopify API: ${products.length} total products`); | ||
| } catch (err) { | ||
| console.log(` Shopify failed (${err.message}), trying Playwright...`); | ||
| products = await playwrightCatalog(browser, target); | ||
| method = 'playwright_dom'; | ||
| } | ||
| for (const target of TARGETS) { | ||
| console.log(`\n══ ${target.name} (${target.url}) ══`); |
PR #27's version dropped the guard that guaranteed browser.close() if the scrape loop throws, leaking a headless Chromium on error. Re-wrap the target loop in try/finally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Salvages only the competitor-analysis data/pipeline layer from PR #27 (
feat/pdr-001-design-system-v2) into a clean branch. PR #27's competing visual design is intentionally dropped — per the reconciliation analysis, #34 (feat/howiezz-fe-design) is the canonical design. This branch is stacked on #34's base so the design + analysis PDRs carry through without duplication.What this carries (data/analysis layer only)
6 pipeline scripts (pdr-001 versions, genuine competitor-analysis improvements):
scripts/analyze_independent_groupings.py— Windows-safe path serialization (str()over.as_posix())scripts/build_competitor_catalog_taxonomy.py— sub-100cm height guards against partial regex matches, brand/material classification refinements, unavailable-competitor dedupscripts/build_independent_db.py— updated source docstring (+BestRealDoll)scripts/inspect_scrape.py— ROOT-relative path resolutionscripts/print_report.py— ROOT-relative path resolutionscripts/scrape_competitors.js— pipeline refactor (removed unused deep-dive path)Deliberately NOT included
assets/site.css, all*.html,docs/pdr/PDR-001-luxury-design-system-v2.md. FE: Concierge Atlas frontend redesign (HZZ-FE-001..009 + premium intake) #34 wins.Integrity
node --check scripts/scrape_competitors.js→ OKpython -m py_compileon all 5 salvaged.pyscripts → OKRecommendation
Once this and #34 land, PR #27 can be closed — its remaining value (this pipeline) is captured here, and its design is superseded by #34.