Creative ideas + design notes
Commits with substantial prose (≥120 chars) — the rationale behind each move.
43c1043 · 2026-06-03 · deploy: repoint aichat to /opt/big-red (actual run dir) + protect .pm2 from rsync --delete
Audit found the live aichat pm2 process runs from /opt/big-red (user bigred),
not /root/Projects/big-red — so /deploy was landing in the wrong dir and
smoke-testing the wrong port, a no-op against prod that always reported fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9333465 · 2026-06-02 · fix(port): align all config to live prod port 9936 (was 9935)
Prod 'aichat' serves on 9936 behind nginx (public /api/health 200 reports
port 9936), but ecosystem.config.js/.deploy.conf/server.js fallback all said
9935. The drift broke the /deploy smoke-test (hit 9935) and armed a future
pm2 reload to evict the process off the port nginx proxies to -> 502.
Now ecosystem env default, .deploy.conf HEALTH_URL, and server.js fallback
all = 9936. Verified locally: binds 9936, /api/health 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dc09b57 · 2026-05-21 · widget: move launcher + panel + tag from bottom-left to bottom-right
Steve directive 2026-05-21. Flips all positioning rules (closed FAB, live-slide
calc, tagline, panel anchor, mobile overrides) from left→right. Panel slide-
out direction inverted accordingly so launcher now slides LEFT when open to
sit beside the panel.
Memory updated: lower-LEFT rule is RETIRED, canonical is now lower-RIGHT
across all 61 DW microsites — they pick up automatically since they all
load the same widget.js from chat.designerwallcoverings.com.
c69895e · 2026-05-19 · add .deploy.conf so /deploy skill can ship aichat to Kamatera
Names the pm2 service (aichat), the remote project path, the health
URL, and the eventual public URL the 60 sister sites already point at.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ca5e47a · 2026-05-19 · fix: resolve claude CLI to absolute path at startup + extend pm2 PATH env
Two-part fix for "spawn claude ENOENT" that caused /api/chat to fail
under pm2 (the entire user-facing reason Big Red did nothing).
Root cause: Node's spawn() uses the parent process's PATH for binary
lookup, not the `env` option passed to it. pm2 strips ~/.local/bin
from the inherited PATH so spawn('claude') hit ENOENT every call.
- ecosystem.config.js: set PATH explicitly in pm2 env so child env
inherits the right paths on both Mac2 and Kamatera (Kamatera uses
/root/.local/bin).
- server.js: resolve `claude` to an absolute path ONCE at module load
by checking known install dirs in order, then pass the absolute
path to spawn(). Logs the resolved location on startup. Falls back
to bare "claude" if no install dir matches (so the failure is loud,
not silent).
Verified locally: warm-up now logs "claude CLI warm" instead of
"warm-up skipped: spawn claude ENOENT", and /api/chat returns a
proper retail-persona response in ~10s with live Shopify lookup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a2d0c5a · 2026-05-19 · guard .bak/.pre-/.orig/.swp paths from static + broaden .gitignore
404 any request whose path matches editor/snapshot backup suffixes before
the express.static handler can serve them. Also broaden .gitignore so
those files never get tracked in the first place. Mechanical safety pass
— no chat, persona, or vendor-scrub code touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8379170 · 2026-05-12 · feat(big-red/tick2): Shopify lookup 60s TTL cache (next: SKU resolver fallback)
In-memory Map keyed by SKU, 60s TTL. Wraps lookupSkuLive() so repeat
questions about the same SKU within a minute hit local memory instead
of Shopify Admin GraphQL.
- Cache age surfaced in response as _cache_age_ms when serving from
cache; absent on fresh fetches (so consumers can detect freshness).
- Null results cached too — repeated 'no-such-SKU' queries don't
hammer Shopify any more than valid SKUs do.
- Background sweep every 5 min drops entries older than TTL*4 so the
Map doesn't grow unbounded across long uptime. setInterval().unref()
so it doesn't block shutdown.
Live timing (DWKK-150018 against running big-red):
call 1 (fresh): ~600ms — Shopify roundtrip
call 2 (cache): 52ms — _cache_age_ms: 52
calls 3-6: 34-41ms each — pure cache hits
YOLO tick 2 — next tick (T+30m): expand SKU resolver to fall back into
sister catalogs (scalamandre, sisterparish, etc.) when shopify_products
misses, so retired/unsynced SKUs still resolve.
56b06d5 · 2026-05-12 · feat(big-red/tick1): per-mode launcher ring color (next: shopify TTL cache)
Closed-state launcher ring now reflects mode:
- retail → red glow (unchanged default)
- wholesale → trade-blue (#5fa8ff) ring + blue tag 'TRADE · BIG RED'
- admin → operator-amber (#f0a93a) ring + amber tag 'OPERATOR · BIG RED'
Live (open) state still goes green across all modes — preserves the
red→green active-chat metaphor while letting the closed launcher
telegraph which persona Big Red will load when clicked.
CSS attribute selectors keyed on data-mode set on the launcher element.
Three new keyframe animations (blue/amber/green pulses) match the
existing red rhythm. Tag color shadow follows mode on hover.
YOLO tick 1 — next tick (T+30m): Shopify lookup TTL cache.
bd46b8f · 2026-05-12 · feat(big-red/tick0): chat-only /embed.html (panel iframe replacement)
Replaces the full Big Red desktop UI in the widget panel with a lean
chat-only view: small avatar + mode pill + context line + msg history
+ Web Speech mic + ElevenLabs TTS playback. Reads mode/app/page/title
from query string, propagates to /api/chat. Esc inside the embed
postMessages bigred:close → widget closes the panel.
Tick 1 (T+30m via ScheduleWakeup) — per-mode launcher ring color so
admin/wholesale modes are visually distinct from retail in the host page.
0f3f22b · 2026-05-12 · feat: per-mode personas (retail / wholesale / admin) + app context
Widget reads data-mode + data-app from its <script> tag and propagates
both into the iframe URL params; the chat endpoint reads them from
body OR Referer query string and selects one of three personas:
- retail (default): warm customer-service, hides vendor/margin/wholesale
- wholesale: trade-savvy, surfaces vendor + MOQ + lead time + class A
- admin: direct operator voice for Steve, dense replies, code/SQL/shell
welcome, 240s Claude timeout (vs 60s for customer-facing modes)
Live tested: admin/lawyer-directory-builder → 'Need a clarification —
what's the integration point? Options: 1. chat widget on the site...'
(operator voice). Default retail → 'We've got beautiful silk...
smooth finishes with that lovely natural luster' (sales voice).
The DW SKU lookup (live Shopify price verify) still triggers on any
mode that mentions a SKU — same facts, different voice phrasing them.
09a1667 · 2026-05-11 · feat(widget): avatar launcher with red->green live state on click
Replaces the BIG/RED text glyph with Steve's circular avatar headshot
(220px PNG seeded from agent-avatars/steve.png at public/avatar/steve.jpg
— matches the /api/avatar exists path).
Visual state machine:
- Closed: red ring + red-pulse animation + red wash overlay (mix-blend
multiply) so the avatar reads tinted but recognizable
- Hover: scales 1.08
- Open ('live'): green ring + green-pulse + green wash overlay; tag pill
next to the launcher swaps from 'TALK TO BIG RED' (red) to 'LIVE — TAP
TO HIDE' (green). Live indicator dot appears in the iframe title bar.
- Iframe loads /?embed=1&voice=1 so Big Red can default to voice mode
Falls back gracefully to 'BR' monogram if the avatar 404s, so the widget
still renders on cold installs before the user uploads a face.