Skip to content

data: salvage competitor-analysis pipeline from PR #27 (design dropped) - #35

Open
kas1987 wants to merge 58 commits into
mainfrom
feat/competitor-analysis-data
Open

data: salvage competitor-analysis pipeline from PR #27 (design dropped)#35
kas1987 wants to merge 58 commits into
mainfrom
feat/competitor-analysis-data

Conversation

@kas1987

@kas1987 kas1987 commented Jun 16, 2026

Copy link
Copy Markdown
Owner

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 dedup
  • scripts/build_independent_db.py — updated source docstring (+BestRealDoll)
  • scripts/inspect_scrape.py — ROOT-relative path resolution
  • scripts/print_report.py — ROOT-relative path resolution
  • scripts/scrape_competitors.js — pipeline refactor (removed unused deep-dive path)

Deliberately NOT included

Integrity

  • node --check scripts/scrape_competitors.js → OK
  • python -m py_compile on all 5 salvaged .py scripts → OK

Recommendation

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.

kas1987 and others added 30 commits June 6, 2026 11:05
- 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>
kas1987 and others added 21 commits June 16, 2026 12:04
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>
Copilot AI review requested due to automatic review settings June 16, 2026 19:41
@kas1987
kas1987 changed the base branch from main to feat/howiezz-fe-design June 16, 2026 19:42

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

high

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)

high

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)

high

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)

high

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)

high

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)

medium

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)

medium

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)

medium

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)

medium

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
  1. Consistent indentation of 4 spaces per indentation level is required by PEP 8. (link)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 448 to 451
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())
Comment on lines 1323 to 1331
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)

Comment on lines 209 to +214
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>
Base automatically changed from feat/howiezz-fe-design to main June 16, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants