Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
04ded98
research(pdr-010): competitor family taxonomy + apples-to-apples DB
kas1987 Jun 6, 2026
a5bb574
PDR-010: expand competitor coverage and add independent grouping heat…
kas1987 Jun 6, 2026
a0c2e4c
PDR-010: add full-body heatmap and normalize ZELEX line grouping
kas1987 Jun 6, 2026
2249094
PDR-010: add PNG heatmaps, top10 matrices, and lineup brief
kas1987 Jun 6, 2026
2ba3c35
PDR-010: track independent catalog heatmap PNG artifacts
kas1987 Jun 6, 2026
b0e6e73
fix(taxonomy): remove Tayu from UNAVAILABLE_COMPETITORS
kas1987 Jun 6, 2026
9a97d9b
fix(ci): untrack PNG heatmap artifacts that trip the image guard
kas1987 Jun 6, 2026
e682646
feat(gallery+db): character page gallery UX overhaul + Sculpt/Classic…
kas1987 Jun 6, 2026
9cc4536
fix(pdr-010): address all PR #28 Copilot/Gemini review findings
kas1987 Jun 6, 2026
b432425
fix: apply PR #29 review comment fixes
kas1987 Jun 6, 2026
42e3332
feat: ship compare funnel and analytics sanity guardrails
kas1987 Jun 6, 2026
c67cd2a
feat(pdr-010): add Howie CEO package — strategic briefs + interactive…
kas1987 Jun 6, 2026
99a5577
docs(pdr-010): add like-kind manufacturing & cross-family tooling int…
kas1987 Jun 6, 2026
9fbd6b0
docs(pdr-010): add manufacturing intelligence matrix brief for CEO pa…
kas1987 Jun 6, 2026
6de707a
docs(pdr-010): add full PDR with Mermaid path analysis across all epi…
kas1987 Jun 6, 2026
a1657f9
feat(pdr-010): embed Mermaid decision charts into CEO dashboard
kas1987 Jun 6, 2026
1d690e9
Import PDR recovery pack and enforce explicit PDR path gating
kas1987 Jun 16, 2026
d0b4000
Add CI PDR_PATH guard and complete recovery-task traceability
kas1987 Jun 16, 2026
b398b39
Apply PDR-001 status primitives in shared card components
kas1987 Jun 16, 2026
86a256f
Use shared status primitives on homepage family tiles
kas1987 Jun 16, 2026
dff2e3c
Continue PDR-001 rollout and add PDR-002 homepage intent router
kas1987 Jun 16, 2026
8e040f3
Advance PDR-003 compare workflow with decision summaries
kas1987 Jun 16, 2026
49aacb6
Unify compare-entry UX and remove remaining homepage inline layout st…
kas1987 Jun 16, 2026
f674476
Implement PDR-004 ranked quiz funnel and handoff CTAs
kas1987 Jun 16, 2026
1efa755
feat: add customization options guide with body/head tracks
kas1987 Jun 16, 2026
93b28c1
feat: add options deep-links, filter analytics, and noscript fallback
kas1987 Jun 16, 2026
8c700b9
chore: package latest root HTML set into v2 HTML
kas1987 Jun 16, 2026
3dde4ee
feat: add community hub, SWOT gap review, and v2 packaging automation
kas1987 Jun 16, 2026
e828318
chore: harden validation and v2 packaging best practices
kas1987 Jun 16, 2026
d00e69f
feat: enforce v2 freshness in CI and data-drive community hub
kas1987 Jun 16, 2026
e4f6463
feat: tie community hub to KB with schema guard and events page
kas1987 Jun 16, 2026
053f834
chore: add community-events schema guard and KB sync utility
kas1987 Jun 16, 2026
b9db475
chore: add check-mode sync guard and community data contract
kas1987 Jun 16, 2026
c86a118
chore: refresh v2 package checksum artifact
kas1987 Jun 16, 2026
44c4d3e
chore: add community provenance UI and event scaffold utility
kas1987 Jun 16, 2026
5ecac99
docs: finalize community data operations and CI sync policy guidance
kas1987 Jun 16, 2026
1de043b
Merge origin/main into feat/pdr-010-ceo-roi-analysis
kas1987 Jun 16, 2026
05cacac
fix: repair validate-site metadata gate after main merge
kas1987 Jun 16, 2026
aff6c9b
fix: resolve main merge conflicts and add required page metadata
kas1987 Jun 16, 2026
df6cd89
HZZ-FE-002: Refine luxury design system v2 (assets/site.css)
kas1987 Jun 16, 2026
56dea56
Add Claude Design frontend PDRs and tickets
kas1987 Jun 16, 2026
184a4bb
HZZ-FE-001: Redesign homepage as Concierge Atlas landing
kas1987 Jun 16, 2026
a50ec52
HZZ-FE-003: Body compare UX
kas1987 Jun 16, 2026
9f18930
HZZ-FE-003: Align compare buyer-read & handling to real DB data
kas1987 Jun 16, 2026
7ca42ba
HZZ-FE-005: Quiz match results funnel
kas1987 Jun 16, 2026
73fbfe4
HZZ-FE-007: Browse and family UX
kas1987 Jun 16, 2026
01b8715
HZZ-FE-004: Character detail premium conversion
kas1987 Jun 16, 2026
9adc829
HZZ-FE-006: Concierge intake UX
kas1987 Jun 16, 2026
288b156
docs: add Claude Design premium intake & buyer-fit package (reference)
kas1987 Jun 16, 2026
564b520
feat(intake): port Claude Design guided buyer-fit UX into contact.html
kas1987 Jun 16, 2026
dae19f6
feat(configurator): port Claude Design live configurator as new page
kas1987 Jun 16, 2026
b01a3f3
HZZ-FE-008: Mobile navigation and CTA pass
kas1987 Jun 16, 2026
5d91d99
HZZ-FE-009: Visual QA review findings
kas1987 Jun 16, 2026
f85bd43
fix(qa): resolve FE-009 minor findings (HZZ-FE-010..013)
kas1987 Jun 16, 2026
3cfabad
docs(qa): add reconciliation analysis + live interaction QA evidence
kas1987 Jun 16, 2026
70f3f3c
data: salvage competitor-analysis pipeline from PR #27 (design dropped)
kas1987 Jun 16, 2026
2182861
fix(scrape): restore try/finally so browser always closes on error
kas1987 Jun 16, 2026
8c9f783
ci: trigger check after PR retarget to main
kas1987 Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .cursor/rules/pdr-path-required.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
description: Require explicit PDR path for all Cursor tasks
alwaysApply: true
---

Before any plan or code change, the prompt must include:

`PDR_PATH: <exact file path in this repository>`

Enforcement:
1. If `PDR_PATH` is missing, ask for it and stop.
2. If the file path does not resolve inside this repo, ask for a valid repo path and stop.
3. Implement only the scoped requirements from that PDR file.
4. Do not use old external issues as implementation authority unless mirrored to a repo PDR file.

Preferred recovery baseline:
`docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack/docs/pdr/PDR-000-recovery-and-source-of-truth.md`
66 changes: 66 additions & 0 deletions .github/scripts/validate-community-data.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { existsSync, readFileSync } from 'node:fs';

let failures = 0;
const fail = (msg) => { console.error(' x ' + msg); failures++; };
const ok = (msg) => console.log(' ok ' + msg);

const FILE = 'db/community_channels.json';
if (!existsSync(FILE)) {
fail(FILE + ' is missing');
} else {
try {
const raw = JSON.parse(readFileSync(FILE, 'utf8'));

if (!raw.meta || typeof raw.meta !== 'object') fail('meta object is missing');
if (!Array.isArray(raw.channels) || raw.channels.length === 0) fail('channels[] must be non-empty');
if (!Array.isArray(raw.official_handles)) fail('official_handles[] must be present');

const refs = raw.meta && Array.isArray(raw.meta.source_refs) ? raw.meta.source_refs : [];
if (!refs.length) fail('meta.source_refs[] must include at least one provenance entry');

refs.forEach((r, i) => {
if (!r || typeof r !== 'object') {
fail('source_refs[' + i + '] invalid');
return;
}
if (!r.path || !r.kind) fail('source_refs[' + i + '] must include path and kind');
if (r.path && !existsSync(r.path)) fail('source_refs[' + i + '] path missing: ' + r.path);
});

const requiredChannelFields = ['name', 'purpose', 'status', 'cadence', 'primary', 'secondary', 'primaryLabel', 'secondaryLabel'];
raw.channels.forEach((c, i) => {
requiredChannelFields.forEach((k) => {
if (!c[k] || typeof c[k] !== 'string') fail('channels[' + i + '].' + k + ' must be a non-empty string');
});
[c.primary, c.secondary].forEach((u) => {
if (!u) return;
if (u.startsWith('http://') || u.startsWith('https://')) {
try { new URL(u); } catch { fail('channels[' + i + '] has invalid URL: ' + u); }
} else {
const localPath = u.split('?')[0];
if (!existsSync(localPath)) fail('channels[' + i + '] local target missing: ' + localPath);
}
});
});

const requiredHandleFields = ['platform', 'handle', 'url', 'verification'];
raw.official_handles.forEach((h, i) => {
requiredHandleFields.forEach((k) => {
if (!h[k] || typeof h[k] !== 'string') fail('official_handles[' + i + '].' + k + ' must be a non-empty string');
});
const p = (h.url || '').split('?')[0];
if (p && !(h.url.startsWith('http://') || h.url.startsWith('https://')) && !existsSync(p)) {
fail('official_handles[' + i + '] local target missing: ' + p);
}
});

if (!failures) ok(FILE + ' schema and provenance checks passed');
} catch (err) {
fail(FILE + ' parse error: ' + err.message);
}
}

if (failures) {
console.error('\n' + failures + ' community data validation error(s).');
process.exit(1);
}
45 changes: 45 additions & 0 deletions .github/scripts/validate-community-events-data.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { existsSync, readFileSync } from 'node:fs';

let failures = 0;
const fail = (msg) => { console.error(' x ' + msg); failures++; };
const ok = (msg) => console.log(' ok ' + msg);

const FILE = 'db/community_events.json';
if (!existsSync(FILE)) {
fail(FILE + ' is missing');
} else {
try {
const raw = JSON.parse(readFileSync(FILE, 'utf8'));
if (!Array.isArray(raw.events) || raw.events.length === 0) {
fail('events[] must be a non-empty array');
} else {
raw.events.forEach((ev, i) => {
['title', 'date', 'mode', 'summary', 'ctaLabel', 'ctaHref'].forEach((k) => {
if (!ev[k] || typeof ev[k] !== 'string') fail('events[' + i + '].' + k + ' must be a non-empty string');
});

if (ev.date && !/^\d{4}-\d{2}-\d{2}$/.test(ev.date)) {
fail('events[' + i + '].date must be YYYY-MM-DD');
}

if (ev.ctaHref) {
if (ev.ctaHref.startsWith('http://') || ev.ctaHref.startsWith('https://')) {
try { new URL(ev.ctaHref); } catch { fail('events[' + i + '] has invalid URL: ' + ev.ctaHref); }
} else {
const localPath = ev.ctaHref.split('?')[0];
if (!existsSync(localPath)) fail('events[' + i + '] local target missing: ' + localPath);
}
}
});
}

if (!failures) ok(FILE + ' schema checks passed');
} catch (err) {
fail(FILE + ' parse error: ' + err.message);
}
}

if (failures) {
console.error('\n' + failures + ' community-events validation error(s).');
process.exit(1);
}
107 changes: 107 additions & 0 deletions .github/scripts/validate-pdr-path-task-files.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { join, relative } from 'node:path';

const ROOT = process.cwd();
const MARKDOWN_SUFFIX = '.md';
const TASK_ID_PATTERN = /^\s*-?\s*task_id\s*:/im;
const PDR_PATH_PATTERN = /^\s*-?\s*pdr_path\s*:\s*(.+)$/gim;

const SCAN_DIRS = [
'docs/handoffs',
'docs/pdr/recovery-pack'
];

const REQUIRED_TASK_FILES = [
'AGENTS.md',
'CLAUDE.md',
'CODEX.md'
];

let failures = 0;

function walkMarkdownFiles(dirPath, out) {
if (!existsSync(dirPath)) return;
for (const entry of readdirSync(dirPath)) {
const fullPath = join(dirPath, entry);
const stats = statSync(fullPath);
if (stats.isDirectory()) {
walkMarkdownFiles(fullPath, out);
} else if (stats.isFile() && fullPath.toLowerCase().endsWith(MARKDOWN_SUFFIX)) {
out.push(fullPath);
}
}
}

function getPdrPaths(content) {
const matches = [];
for (const match of content.matchAll(PDR_PATH_PATTERN)) {
const pathValue = (match[1] || '').trim().replace(/^`|`$/g, '');
if (pathValue.length > 0) {
matches.push(pathValue);
}
}
return matches;
}

function validateTaskFile(filePath) {
const rel = relative(ROOT, filePath).replace(/\\/g, '/');
const content = readFileSync(filePath, 'utf8');

if (!TASK_ID_PATTERN.test(content)) return;

const pdrPaths = getPdrPaths(content);
if (pdrPaths.length === 0) {
console.error(`ERROR ${rel}: task file has task_id but no pdr_path`);
failures += 1;
return;
}

for (const pdrPath of pdrPaths) {
const resolved = join(ROOT, pdrPath);
if (!existsSync(resolved)) {
console.error(`ERROR ${rel}: pdr_path does not exist -> ${pdrPath}`);
failures += 1;
}
}
}

function validateRequiredTaskDocs() {
for (const relPath of REQUIRED_TASK_FILES) {
const fullPath = join(ROOT, relPath);
if (!existsSync(fullPath)) {
console.error(`ERROR ${relPath}: required policy file is missing`);
failures += 1;
continue;
}

const content = readFileSync(fullPath, 'utf8');
if (!/PDR_PATH/i.test(content)) {
console.error(`ERROR ${relPath}: missing PDR_PATH policy text`);
failures += 1;
}
}
}

function main() {
console.log('Running PDR_PATH task-file guard...');

validateRequiredTaskDocs();

const markdownFiles = [];
for (const dir of SCAN_DIRS) {
walkMarkdownFiles(join(ROOT, dir), markdownFiles);
}

for (const filePath of markdownFiles) {
validateTaskFile(filePath);
}

if (failures > 0) {
console.error(`PDR_PATH validation failed with ${failures} error(s).`);
process.exit(1);
}

console.log('PDR_PATH guard passed.');
}

main();
21 changes: 10 additions & 11 deletions .github/scripts/validate-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,26 @@ let failures = 0;
const fail = (msg) => { console.error(' ✗ ' + msg); failures++; };
const ok = (msg) => console.log(' ✓ ' + msg);

// Pages built on the shared kit (must load assets/site.js).
const KIT_PAGES = [
'index.html', 'browse.html', 'series.html', 'body.html', 'character.html',
'family.html', 'quiz.html', 'craft.html', 'contact.html',
];
// Standalone legacy page — kept for reference, intentionally self-contained.
const STANDALONE_PAGES = ['index-gallery-original.html'];
const PAGES = [...KIT_PAGES, ...STANDALONE_PAGES];
// Standalone legacy page(s) intentionally not on the shared runtime.
const STANDALONE_PAGES = new Set(['index-gallery-original.html']);

// Auto-discover root HTML pages so new pages are always validated.
const PAGES = readdirSync('.')
.filter((n) => n.endsWith('.html'))
.sort();

console.log('Validating pages…');
for (const f of PAGES) {
if (!existsSync(f)) { fail(`${f} is missing`); continue; }
const html = readFileSync(f, 'utf8');

// shared-kit reference (kit pages only)
if (KIT_PAGES.includes(f) && !html.includes('assets/site.js')) {
// shared-kit reference (all non-legacy pages)
if (!STANDALONE_PAGES.has(f) && !html.includes('assets/site.js')) {
fail(`${f} does not reference assets/site.js`);
}

// baseline SEO / social metadata (kit pages only)
if (KIT_PAGES.includes(f)) {
if (!STANDALONE_PAGES.has(f)) {
const META_CHECKS = [
[/<meta\s+name="description"\s+content="[^"]+"/i, 'meta description'],
[/<link\s+rel="canonical"\s+href="https?:\/\/[^"]+"/i, 'canonical link'],
Expand Down
65 changes: 65 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
permissions:
contents: read

env:
COMMUNITY_CHANNELS_REFERENCE_PATH: _local/reference/zelexdoll-theme/docs/community_channels.json
COMMUNITY_CHANNELS_REQUIRE_SOURCE: "false"

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
Expand Down Expand Up @@ -38,6 +42,67 @@ jobs:
- name: Validate pages, inline JS, and catalog JSON
run: node .github/scripts/validate-site.mjs

- name: Validate community data schema + provenance
run: node .github/scripts/validate-community-data.mjs

- name: Validate community events data schema
run: node .github/scripts/validate-community-events-data.mjs

- name: Guard — community channel sync status (check mode)
shell: pwsh
run: |
if ($env:COMMUNITY_CHANNELS_REQUIRE_SOURCE -eq 'true') {
./scripts/sync_community_channels_from_reference.ps1 -CheckOnly -RequireSource -SourcePath $env:COMMUNITY_CHANNELS_REFERENCE_PATH
} else {
./scripts/sync_community_channels_from_reference.ps1 -CheckOnly -SourcePath $env:COMMUNITY_CHANNELS_REFERENCE_PATH
}

- name: Guard — PDR_PATH headers in agent task files
run: node .github/scripts/validate-pdr-path-task-files.mjs

- name: Guard — v2 HTML package is refreshed
shell: pwsh
run: |
./scripts/refresh_v2_html.ps1 1
git diff --exit-code -- "v2 HTML"
if ($LASTEXITCODE -ne 0) {
Write-Error "v2 HTML package artifacts are stale. Run ./scripts/refresh_v2_html.ps1 and commit updated package files."
exit 1
}

- name: Analytics sanity suite
shell: pwsh
run: |
echo "Using threshold config: docs/pdr/PDR-analytics-sanity-thresholds.json"
$cfg = Get-Content -Raw docs/pdr/PDR-analytics-sanity-thresholds.json | ConvertFrom-Json
echo "Analytics config schema_version: $($cfg.schemaVersion)"
./scripts/run_analytics_sanity_suite.ps1

- name: Analytics config guardrail (expected fail)
shell: pwsh
run: |
pwsh -File scripts/run_analytics_sanity_suite.ps1 -ConfigPath docs/pdr/PDR-analytics-sanity-thresholds.invalid.json
if ($LASTEXITCODE -ne 2) {
Write-Error "Expected exit code 2 for invalid analytics config, got $LASTEXITCODE"
exit 1
}

- name: Analytics config guardrail missing keys (expected fail)
shell: pwsh
run: |
pwsh -File scripts/run_analytics_sanity_suite.ps1 -ConfigPath docs/pdr/PDR-analytics-sanity-thresholds.missing-keys.json
if ($LASTEXITCODE -ne 2) {
Write-Error "Expected exit code 2 for missing-keys analytics config, got $LASTEXITCODE"
exit 1
}

- name: Upload analytics sanity artifacts
uses: actions/upload-artifact@v4
with:
name: analytics-sanity-summaries
path: .artifacts/analytics
if-no-files-found: error

- name: Guard — no images, video, or secrets committed
run: |
set -e
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ zelexdoll-theme/

# ---- Build artifacts / distributables ----
*.zip
!docs/pdr/recovery-pack/HowieZZ_PDR_Recovery_Pack.zip
*.pdf
db/*.db
db/catalog.db
Expand Down Expand Up @@ -83,3 +84,4 @@ desktop.ini
.vscode/
.idea/
*.swp
docs/pdr/qa-screenshots/
2 changes: 2 additions & 0 deletions 404.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<meta name="description" content="The page you were looking for has moved or never existed. Return to the ZELEX Character Atlas.">
<meta name="theme-color" content="#121212">
<meta name="robots" content="noindex,follow">
<link rel="canonical" href="https://www.zelexdoll.com/404.html">
<meta property="og:title" content="ZELEX — Page Not Found">
<link rel="icon" type="image/svg+xml" href="/assets/favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&family=Montserrat:wght@300;400;500;600&display=swap" rel="stylesheet">
Expand Down
Loading