{
    "entries": [
        {
            "id": "ctx-2026-05-05-01",
            "type": "context",
            "title": "Real Claude scan is now live on deedae.com",
            "body": "<p>End-to-end real scan flow is shipping:</p>\n    <ul>\n      <li>Claude Haiku 4.5 + web search finds cheapest legitimate price across the open web</li>\n      <li>Result classified into 3-tier trust ladder via curated retailer DB (106 retailers, 9 categories)</li>\n      <li>Color-coded badges render on result page: green (verified) / amber (double-check) / magenta (use caution)</li>\n      <li>24-hour Redis cache means repeat URLs are instant + free (Pete verified the cache hit working)</li>\n      <li>3-audits-per-IP-per-day rate limit protects API budget</li>\n      <li>~15-30s scan time for fresh URLs, &lt;1s for cached</li>\n    </ul>\n    <p>Splash, quiz, match, audit, scan, result pages all rendering correctly across all three skins. Pete confirmed: \"It ALL works. Even the cache when I hit the Sephora URL a second time.\"</p>",
            "tags": [
                "scan",
                "launch",
                "milestone"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "ctx-2026-05-05-02",
            "type": "context",
            "title": "109-key copy schema centralized across all skins",
            "body": "<p>Every visible string in the app now flows through <code>t('key.path')</code> from skin files. Zero hardcoded English in JSX.</p>\n    <ul>\n      <li>Naming convention LOCKED FOREVER: <code>{page}.{section}.{element}</code></li>\n      <li>Numbered iterables: <code>scan.step.3.status</code>, <code>result.verdict.bad.0</code></li>\n      <li>109 keys per skin, perfect 1:1 schema match across Glow/Editorial/Friend</li>\n      <li>SkinProvider falls back to Glow's value when a key is missing \u2014 degrades gracefully</li>\n      <li>Two HTML tags supported in copy values: <code>&lt;em&gt;</code> for italic accent font, <code>&lt;b&gt;</code> for bold</li>\n    </ul>\n    <p>Copy update flow: edit skin file in GitHub ? commit ? Vercel rebuilds in ~30s ? live. No JSX hunting, no scattered constants, no DB.</p>",
            "tags": [
                "architecture",
                "copy",
                "skins",
                "brand"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "ctx-2026-05-05-03",
            "type": "context",
            "title": "Three-tier retailer trust ladder is the trust signal architecture",
            "body": "<p>Confidence comes from category curation, NOT from review-site scores. Sephora rates 2.6/5 on Trustpilot but is the most legitimate prestige retailer in the US; Beautylish rates 4.4/5 because it's smaller. Rating ? trust.</p>\n    <p><strong>Tier 1 \u2014 verified ? (green):</strong> 44 retailers including authorized_major (Sephora, Ulta, Beautylish, Target, Walmart), authorized_dtc (38 brand sites), department_store (10 incl. Nordstrom, Macy's, Saks).</p>\n    <p><strong>Tier 2 \u2014 double-check ? (amber):</strong> 42 retailers including authorized_specialty (Dermstore, Bluemercury, Credo, Space NK), drugstore (Walgreens, CVS), discount_offprice (Nordstrom Rack, Saks OFF 5TH), membership_warehouse (Costco).</p>\n    <p><strong>Tier 3 \u2014 use caution ? (magenta):</strong> 10 retailers + everything unknown. Marketplace (eBay, Mercari, Poshmark, Depop, Etsy) and grey_market (Wish, AliExpress, Temu, DHgate, Shein).</p>\n    <p>All retailers default <code>inSkimlinks: null</code> until Friday's merchant export sync flips matching domains to <code>true</code>.</p>",
            "tags": [
                "trust",
                "retailer-db",
                "scan",
                "skimlinks"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "ctx-2026-05-05-04",
            "type": "context",
            "title": "Scan animation: 8-step rotator + cumulative retailer counter + fallback",
            "body": "<p>Real scans take 15-30s, vs the mock's ~4s. Without UX work, this would feel like a silent broken spinner.</p>\n    <p>Solution shipped:</p>\n    <ul>\n      <li>8 status messages cycle ~3.4s each (~28s total) \u2014 matches typical Claude scan duration</li>\n      <li>Each message has skin-aware <code>scan.step.N.status</code> + <code>scan.step.N.detail</code> copy</li>\n      <li>Cumulative retailer counter ticks 2-5 per ~700ms, caps at 52 retailers \u2014 feels like real work</li>\n      <li>If Claude takes &gt;25s, fallback message kicks in: Glow \"worth the wait\" / Editorial \"thorough work takes a moment\" / Friend \"lemme dig deeper\"</li>\n    </ul>\n    <p>Vercel function timeout is 60s on Hobby tier \u2014 enough margin for typical Claude responses.</p>",
            "tags": [
                "scan",
                "ux",
                "animation"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "dec-2026-05-05-01",
            "type": "decision",
            "title": "Trust ladder uses curated category, NOT review-site scores",
            "body": "<p>Initial proposal was to use Trustpilot ratings as a confidence signal. Rejected after data review: Trustpilot is a complaint platform, not a legitimacy signal. Most legitimate beauty retailers score 1.5-2.6 because happy customers don't write reviews; angry ones do.</p>\n    <p>Final architecture: <code>classifyRetailer()</code> reads <code>retailer-db.json</code>, maps domain ? category ? tier. Reviews never enter the trust calculation. BBB also rejected (pay-to-play accreditation, retailer-influenced complaint resolution).</p>\n    <p>Pete's call on color coding: green/amber/magenta. Magenta (not red) for tier 3 \u2014 many tier-3 retailers are legitimate-but-unknown rather than dangerous; red is too alarming.</p>",
            "tags": [
                "trust",
                "retailer-db",
                "decision"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "dec-2026-05-05-02",
            "type": "decision",
            "title": "Beautylish promoted to Tier 1 by Pete",
            "body": "<p>Initial classification put Beautylish at Tier 2 (authorized_specialty). Pete called it up to Tier 1 (authorized_major) given its 4.4/5 Trustpilot \u2014 unusually high for the category and signals devoted, satisfied customer base.</p>\n    <p>Now sits alongside Sephora, Ulta, Target, Walmart, and Amazon in the green-badge tier.</p>",
            "tags": [
                "retailer-db",
                "beautylish"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "dec-2026-05-05-03",
            "type": "decision",
            "title": "Per-skin trust badge labels \u2014 voice native, not generic",
            "body": "<p>Each tier label translates per skin via <code>confidence.tier-N</code> keys:</p>\n    <ul>\n      <li>Glow: \"verified ?\" / \"double-check ?\" / \"use caution ?\"</li>\n      <li>Editorial: \"Verified.\" / \"Worth verifying.\" / \"Caution advised.\"</li>\n      <li>Friend: \"i checked it ?\" / \"double check this one\" / \"be careful w this one\"</li>\n    </ul>\n    <p>Result page reads <code>t('confidence.tier-N')</code> with classifier's default as fallback.</p>",
            "tags": [
                "brand",
                "skins",
                "trust"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "dec-2026-05-05-04",
            "type": "decision",
            "title": "Non-beauty URLs: scan anyway, surface waitlist card",
            "body": "<p>When Claude classifies category as \"other\" (sneakers, tech, etc.), the result page renders normally PLUS a \"ping me when categories drop\" waitlist card.</p>\n    <p>Implementation:</p>\n    <ul>\n      <li>Scan prompt instructs Claude to set <code>category: 'beauty' | 'other'</code></li>\n      <li>Result page shows full audit (score, savings) regardless</li>\n      <li>If category is \"other\", waitlist card slots between product card and trust strip</li>\n      <li>Submit POSTs to <code>/api/leads</code> with <code>intent: 'category_waitlist'</code></li>\n      <li>New GA4 event: <code>category_waitlist_joined</code></li>\n    </ul>\n    <p>Turns potential narrow-product moment into list-building. Zero new infrastructure.</p>",
            "tags": [
                "non-beauty",
                "waitlist",
                "leads"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "dec-2026-05-05-05",
            "type": "decision",
            "title": "Copy naming convention: {page}.{section}.{element} \u2014 locked forever",
            "body": "<p>Three rules baked in:</p>\n    <ul>\n      <li>Keys describe location, not content. <code>splash.h1.line1</code> not <code>splash.dealfindingtext</code> \u2014 when copy changes, key still makes sense.</li>\n      <li>Numbered iterables: <code>quiz.q1.options.0.title</code>, <code>scan.step.3.status</code>, <code>result.verdict.bad.0</code> \u2014 predictable, scriptable.</li>\n      <li>No ambiguity between display state and copy: <code>action.happy.success</code> is the after-click label, never mixed with state names.</li>\n    </ul>\n    <p>Documented in doc-block at top of <code>lib/skins/glow.ts</code> so future copy stays organized. When adding a new key to one skin, must add to all three.</p>",
            "tags": [
                "architecture",
                "copy",
                "skins",
                "convention"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "dec-2026-05-05-06",
            "type": "decision",
            "title": "Single drop, not split \u2014 bundle copy refactor with real scan",
            "body": "<p>Pete called it: ship copy centralization in same drop as real scan, despite higher debug surface area. Half-shipping the architecture would be piling tech debt while pages are still small enough for tractable refactor.</p>\n    <p>Right call validated by outcome \u2014 single ~131KB zip, 14 files, all working after one cleanup cycle.</p>",
            "tags": [
                "architecture",
                "decision"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "dec-2026-05-05-07",
            "type": "decision",
            "title": "Star-swipe retailer rating UI: punted to v2",
            "body": "<p>Pete asked about user-driven retailer ratings (\"know this retailer, quick rate to help others\"). Punted because:</p>\n    <ul>\n      <li>Feature has no users. First 50 ratings would skew on early-adopter bias.</li>\n      <li>Pulls focus from real scan ship. ~2-3 hours to build right (UI, API, KV schema, anti-spam, aggregation).</li>\n      <li>Architecture isn't lost \u2014 once audits flow, ratings ship in a focused 2-hour session.</li>\n    </ul>\n    <p>Path forward: revisit when audit volume produces meaningful signal (~100 audits/day). For now, the per-skin label override does the trust-personalization work.</p>",
            "tags": [
                "v2",
                "trust",
                "scope"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "dec-2026-05-05-08",
            "type": "decision",
            "title": "Admin copy editor punted to month 2 (Layer 3)",
            "body": "<p>Discussed three architectural layers:</p>\n    <ul>\n      <li><strong>Layer 1 (shipped):</strong> centralized copy in skin files, edit via GitHub commits</li>\n      <li><strong>Layer 2 (punted):</strong> shared copy across skins with per-skin overrides \u2014 only useful at 5+ skins</li>\n      <li><strong>Layer 3 (month 2):</strong> password-protected /admin/copy dashboard, edits commit to GitHub via API</li>\n    </ul>\n    <p>Layer 3 needs admin auth to exist first (which is also needed for /admin/trends). Only worth building when copy iteration becomes weekly or more frequent.</p>\n    <p>For now, file edits are arguably better \u2014 every change is git-versioned with clear commit history.</p>",
            "tags": [
                "v2",
                "admin",
                "copy"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "act-2026-05-05-01",
            "type": "action",
            "title": "Friday: Skimlinks merchant export integration",
            "body": "<p>When Skimlinks full approval lands Friday, run one-shot script that:</p>\n    <ul>\n      <li>Reads merchant export CSV from Skimlinks dashboard</li>\n      <li>Walks each retailer in <code>lib/retailer-db.json</code></li>\n      <li>Sets <code>inSkimlinks: true</code> on every domain that matches</li>\n      <li>Commits + deploys</li>\n    </ul>\n    <p>Zero changes to scan prompt, API route, or UI. Tier 1 monetization goes hot the moment the deploy lands. ~5 minutes of work.</p>",
            "tags": [
                "skimlinks",
                "monetization",
                "friday"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "act-2026-05-05-02",
            "type": "action",
            "title": "Tiered Skimlinks priority logic in /api/scan",
            "body": "<p>After merchant export integrates, add prioritization logic:</p>\n    <ul>\n      <li>If a Skimlinks-network retailer has a price within 5% of cheapest, prefer it</li>\n      <li>Outside that threshold, always show the genuinely cheapest legit price</li>\n      <li>Honest by default \u2014 never hide a cheaper non-network price; just nudge when prices are close</li>\n    </ul>\n    <p>Implementation: in <code>/api/scan</code>, after Claude returns, do a second pass through <code>searchedRetailers</code> array, find any in-network ones, swap if within 5% threshold.</p>",
            "tags": [
                "skimlinks",
                "scan",
                "monetization"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "act-2026-05-05-03",
            "type": "action",
            "title": "IP whitelist for development testing",
            "body": "<p>Currently rate limit (3/IP/day) hits Pete during testing. Fix:</p>\n    <ul>\n      <li>Add <code>WHITELISTED_IPS</code> env var (comma-separated list)</li>\n      <li>In <code>/api/scan</code> POST handler, skip rate-limit check if request IP in whitelist</li>\n      <li>Pete finds his IP at <code>https://api.ipify.org</code></li>\n    </ul>\n    <p>Caveat: residential IPs change occasionally. For mobile testing, may need short-lived bypass token (<code>?bypass=DEEDAE_DEV_2026</code>) since cellular IPs change every connection.</p>\n    <p>Bundle with Friday's Skimlinks drop.</p>",
            "tags": [
                "dev-tooling",
                "rate-limit"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "act-2026-05-05-04",
            "type": "action",
            "title": "Repo cleanup: delete orphan files in lib/skins/ and components/",
            "body": "<p>Rename cascade left orphan files:</p>\n    <ul>\n      <li><code>lib/skins/skins-glow.ts</code></li>\n      <li><code>lib/skins/skins-editorial.ts</code></li>\n      <li><code>lib/skins/skins-friend.ts</code></li>\n      <li><code>components/x-Nav.tsx</code> (if still present)</li>\n    </ul>\n    <p>Not breaking anything \u2014 nothing imports them. Pure repo hygiene. Three quick deletes via GitHub trash icon. No urgency.</p>",
            "tags": [
                "cleanup",
                "repo-hygiene"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "act-2026-05-05-05",
            "type": "action",
            "title": "Smoke-test Editorial and Friend skins end-to-end",
            "body": "<p>Pete verified Glow works end-to-end. Two skins still need full smoke test:</p>\n    <ul>\n      <li>Switch to Editorial via vibe chip</li>\n      <li>Run an audit (cached URL is fine \u2014 verifies copy more than scan)</li>\n      <li>Verify all copy is in Editorial voice (not Glow): scan steps, verdicts, badge labels, action cards, waitlist card</li>\n      <li>Repeat for Friend skin</li>\n    </ul>\n    <p>~5 minutes per skin. Worth doing before Friday in case any 109-key gaps surface.</p>",
            "tags": [
                "qa",
                "skins"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "risk-2026-05-05-01",
            "type": "risk",
            "title": "109-key schema is now load-bearing",
            "body": "<p>Adding a new copy key to one skin without adding it to the other two will surface as missing text in production for users on the missing skin.</p>\n    <p>Mitigations in place:</p>\n    <ul>\n      <li>SkinProvider falls back to Glow's value when key missing \u2014 degrades gracefully</li>\n      <li>Doc-block at top of <code>glow.ts</code> documents the rule explicitly</li>\n      <li>109-key 1:1 audit ran clean at ship time</li>\n    </ul>\n    <p>Long-term mitigation: add a build-time test that diffs the three skin files and fails CI if key sets diverge. Not yet implemented. Belongs in next session if copy edits become frequent.</p>",
            "tags": [
                "architecture",
                "copy",
                "tech-debt"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "risk-2026-05-05-02",
            "type": "risk",
            "title": "Vercel deployment 403 root cause unknown",
            "body": "<p>During the rename cascade, ~20 failed builds in 30 minutes triggered a Vercel state where deedae.com returned 403 Forbidden \u2014 NOT the previous green deployment as initially assumed. Resolved when a successful build completed.</p>\n    <p>Possible causes (not investigated):</p>\n    <ul>\n      <li>Vercel auto-pause after rapid build-failure threshold</li>\n      <li>Deployment Protection setting accidentally engaged</li>\n      <li>Hobby tier rate limit on deploys</li>\n    </ul>\n    <p>If 403 surfaces again: check Settings ? Deployment Protection FIRST. If enabled, toggle off. If not the cause, may need to wait 10 min for circuit-breaker to clear.</p>",
            "tags": [
                "vercel",
                "deploy",
                "unknown"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "risk-2026-05-05-03",
            "type": "risk",
            "title": "Real scan duration may need optimization at scale",
            "body": "<p>Cold scans take 15-30s (Claude does up to 8 web searches). Mock was instant. The 8-step animation + retailer counter mitigate the wait UX, but if it still feels long after real users hit it, levers available:</p>\n    <ul>\n      <li>Faster model: Haiku ? smaller variant (would lose some accuracy)</li>\n      <li>Parallel sub-scans: split brand/retailer queries into concurrent calls</li>\n      <li>Cheaper search: cap web_search uses to 5 instead of 8 (less coverage)</li>\n      <li>Pre-warm popular products: cache top-100 beauty products on schedule</li>\n    </ul>\n    <p>Don't optimize until real user data shows it's a problem. Cached repeats are instant, which handles the virality case (people sharing audit links).</p>",
            "tags": [
                "scan",
                "performance",
                "future"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "note-2026-05-05-01",
            "type": "note",
            "title": "GitHub web UI rename cascade survival guide",
            "body": "<p>Lessons from 30+ failed builds during this session:</p>\n    <ul>\n      <li><strong>Each rename = its own commit = its own build attempt.</strong> Mid-cascade builds will all fail. This is normal and not a real problem \u2014 only the final commit's state matters.</li>\n      <li><strong>\"Add files via upload\" with new filenames creates parallel files.</strong> It does NOT replace existing files. To replace: open existing file ? pencil ? select all ? paste new content ? commit.</li>\n      <li><strong>Vercel build log truncation in Web UI is real.</strong> When stuck seeing only \"Build Failed\", click the Logs tab specifically (not Deployment). Or trigger a fresh commit and watch the log stream live during build.</li>\n      <li><strong>For new files (no existing file to edit):</strong> use \"Add file ? Create new file\", type filename exactly, paste content. The trap is naming it like the zip filename (<code>skins-glow.ts</code>) instead of the destination filename (<code>glow.ts</code>).</li>\n    </ul>\n    <p>Future drops: deliver as a ZIP with full folder structure preserved (e.g., <code>lib/skins/glow.ts</code> in zip, not <code>skins-glow.ts</code>) so unzip-and-drag-into-repo works in one shot. Reduces the renaming cognitive load to zero.</p>",
            "tags": [
                "deploy",
                "github",
                "lessons"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        },
        {
            "id": "note-2026-05-05-02",
            "type": "note",
            "title": "Copy update flow \u2014 cheat sheet",
            "body": "<p>Where every type of user-facing copy lives:</p>\n    <ul>\n      <li><strong>Scan animation:</strong> <code>lib/skins/{skin}.ts</code> ? <code>scan.step.N.status/detail</code></li>\n      <li><strong>Result verdicts:</strong> <code>lib/skins/{skin}.ts</code> ? <code>result.verdict.{bad/mid/good/best}.N</code></li>\n      <li><strong>Trust badge labels:</strong> <code>lib/skins/{skin}.ts</code> ? <code>confidence.tier-1/2/3</code></li>\n      <li><strong>Splash headline:</strong> <code>lib/skins/{skin}.ts</code> ? <code>splash.h1.line1/2/3</code></li>\n      <li><strong>Waitlist card:</strong> <code>lib/skins/{skin}.ts</code> ? <code>waitlist.eyebrow/heading/body/button</code></li>\n      <li><strong>Error messages:</strong> <code>lib/skins/{skin}.ts</code> ? <code>error.scan.*</code></li>\n      <li><strong>Rate limit + whitelist:</strong> <code>app/api/scan/route.ts</code> + Vercel env var</li>\n      <li><strong>Retailer DB:</strong> <code>lib/retailer-db.json</code> (manual edits OK)</li>\n      <li><strong>Tier classification logic:</strong> <code>lib/classifyRetailer.ts</code> (rare changes)</li>\n      <li><strong>Scan prompt to Claude:</strong> <code>lib/scan-real.ts</code> ? <code>SCAN_PROMPT</code> constant</li>\n    </ul>\n    <p>To change a string: open file ? pencil ? Cmd+F to find key ? edit value ? commit. Vercel rebuilds in ~30s. Two HTML tags supported in copy values: <code>&lt;em&gt;</code> for italic accent, <code>&lt;b&gt;</code> for bold. No others.</p>\n    <p>Rule: when adding a new key to one skin, copy it to all three in the same session. Otherwise other skins fall back to Glow's English.</p>",
            "tags": [
                "copy",
                "workflow",
                "cheat-sheet"
            ],
            "owner": "pete",
            "date": "2026-05-06",
            "pushed_at": "2026-05-06T15:28:20+00:00"
        }
    ]
}