{"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"}]}