# Memory — 2026-02-24

---

## Overture X Marketing — Feb 24 (Drew / woplas)

### Session Summary
Full 3-post day for Overture X (Twitter). No thread (Feb 23 already had one — max 1 thread/2–3 days rule).

### Assets Delivered
All finals at `/home/aria/products/overture/marketing/x-posts/`

| Post | File | Format | Copy |
|------|------|--------|------|
| 🌅 Morning | `2026-02-24-post1-final.png` | Candid Photo + Brand Bar | "You pulled the data. You have the chart. You still don't know why it happened. That's not a data problem. That's an insight problem." Reply CTA: "What's the most frustrating dashboard moment you've had? 👇" |
| ☀️ Midday | `2026-02-24-post2-final.png` | Text-Forward (warm off-white bg) | "Revenue dropped 18% in Q4. The dashboard said: 'Revenue dropped 18% in Q4.' That's not insight. That's a mirror. Overture shows you the why." |
| 🌇 Evening | `2026-02-24-post3-final.png` | Before/After Stacked Split | "You see churn went up 12%. Overture shows it's concentrated in your Q4 cohort, linked to a support SLA breach in week 3. Same data. Different intelligence." Reply CTA: overtureinsights.com |

### Post 3 Revision History
Drew provided a reference image and gave iterative feedback:
1. Switch to stacked vertical layout (BEFORE top / AFTER bottom) — done
2. Pill badges top-left each panel, Inter ExtraBold font (NOT Fraunces)
3. Gradient scrim behind quote text
4. After-panel brightness too aggressive → dialed back (1.08 brightness, 1.10 color — no contrast boost)
5. Text margins too narrow → increased to 140px each side

### Key Learnings (Drew preferences)
- Badge font: Inter ExtraBold (bold sans-serif), NOT Fraunces
- Text margins: 140px min on 1080px canvas
- Before/After: stacked vertically, not side-by-side
- Brightness: subtle lift only, not aggressive
- Drew approves with "done" — brief and direct

### Status
✅ Drew approved and signed off — "done, please save to our logs"

---

## Clocklane Dev Session #product-clocklane › CL: development

### Meta Pixel — clocklane.com landing page
- Added Meta Pixel `895591059745946` to `/home/aria/projects/clocklane/index.html`
- Pixel provided by Ferlie (`_shinmae`)
- Base code in `<head>`, noscript fallback in `<body>` (Vite rejects `<img>` in `<head>` noscript)
- `fbq('track', 'Lead')` fires on successful "Get Early Access" form submit in `LandingPage.tsx`
- Added `Window.fbq` TypeScript declaration
- Deployed to Cloud Run (`clocklane-00076-sb6`) via `gcloud run deploy`

### Dev env broken — no CSS loading (Ragan)
- Port 3064 was serving a stale Next.js build
- CSS file hash changed after rebuild → old hash returned 500
- Fix: kill process, `npm run build`, restart on port 3064
- App is the Next.js POS at `/home/aria/products/clocklane/apps/main/`
- Landing page (Vite) is at `/home/aria/projects/clocklane/`

### Clocklane app — changes made today (all at `/home/aria/products/clocklane/apps/main/`)

#### Team management page (`/app/app/team/page.tsx`)
1. **Loading skeleton** — updated from flat `h-16 animate-pulse` blocks to structured skeleton matching actual member card layout (avatar circle, name, email, date, action placeholders)
2. **Card borders** — changed `bg-white/3 border-white/8 hover:border-white/12` → `bg-[#14171E] border-white/5 hover:border-white/8` for rows; join code panel → `bg-[#1A1D24] border-white/5`

#### Sales report page (`/app/app/reports/sales/page.tsx`)
3. **Total Sales color** — added `valueColor` prop to `SummaryCard`, Total Sales now `text-green-400`
4. **Tooltip dark mode** — all 3 chart tooltips: added `color: "#E8E9EC"`, `itemStyle`, `labelStyle`
5. **Bar chart hover bg** — added `cursor={{ fill: "transparent" }}` to Sales by Category tooltip
6. **Auth race condition on refresh** — `storeId` from localStorage fires before Firebase auth → "Not authenticated". Fix: pull `loading: storeLoading` from `useStore()`, guard fetch on `!storeLoading`, added proper `error` state + Retry button

#### Labor report page (`/app/app/reports/labor/page.tsx`)
7. **Hours format** — added `formatHoursMin()` helper, Employee Breakdown table now shows `6h 24m` instead of `6.4h`
8. **Auth race condition** — same fix as sales: `storeLoading` guard, error state, Retry button
9. **Bar chart hover bg** — `cursor={{ fill: "transparent" }}` on Hours by Employee tooltip; also fixed tooltip text colors on both charts

#### EOD page (`/app/app/eod/page.tsx`)
10. **Auth race condition** — `storeLoading` guard on both `fetchData` and history fetch, added to dependency arrays

#### POS ClocklanePOS.tsx (`/src/components/ClocklanePOS.tsx`)
11. **Total Sales color in POS reports tab** — changed `color: t.accent` (red) to `color: t.success` (green) for Total Sales KPI card
12. **Timesheets — active shift indicator** — when clocked in but not clocked out: shows `● IN` + clock-in time in `t.success` green with `t.successMuted` background instead of just `—`
13. **Clock In/Out button — loading state** — both buttons now `disabled` + `opacity: 0.6` during `clockLoading`; show "Loading..." text
14. **Clock In/Out button — Invalid Date flash** — `handleClockIn` now uses `new Date()` as fallback if `result.clockIn` is invalid; render guards on both date validity AND `elapsedTime` initialized
15. **Clock action text cycling bug** — added `clockActionType: "in" | "out" | null` state; buttons check their own type for text (`clockActionType === "out" ? "Clocking out..."`) preventing "Clocking in..." from showing during clock-out transition

### Auth Race Condition Pattern (global)
**Root cause:** `storeId` initialized from `localStorage` immediately → triggers API before Firebase `auth.currentUser` is ready → "Not authenticated"
**Fix pattern:** `const { storeId, store, loading: storeLoading } = useStore()` → guard fetch with `if (!storeId || storeLoading) return`
**Pages audited:** sales ✅, labor ✅, eod ✅, transactions ✅, dashboard ✅, team ✅, settings ✅, employees ✅

### Standard card styles for Clocklane app
- Row items: `bg-[#14171E] border border-white/5 hover:border-white/8`
- Panels/sections: `bg-[#1A1D24] border border-white/5`
- Nested items: `bg-[#0C0F14] border border-white/10`
- Tooltip style: `contentStyle={{ backgroundColor: "#1A1D24", border: "1px solid #333", borderRadius: 8, color: "#E8E9EC" }} itemStyle={{ color: "#E8E9EC" }} labelStyle={{ color: "#E8E9EC" }}`
- Bar chart cursor: always add `cursor={{ fill: "transparent" }}` to Tooltip to suppress hover bg

### People
- **Ragan** (`gansukii`) — lead dev on Clocklane, active today with many dev requests
- **Zel** (`zelvillarosa`) — asked Ragan for help with something, no GitHub access
- **Ferlie** (`_shinmae`) — provided Meta Pixel ID for Clocklane

---

## Session — Feb 23-24 UTC (Raven DM + Heartbeats)

### exec Approval Gate — FIXED
- Added `tools.exec.ask: "off"` + `security: "full"` to `~/.openclaw/openclaw.json` under `tools.exec`
- Config hot-reloads but required a gateway restart (`gateway action=restart`) to fully apply
- Raven's Discord DM sessions no longer require approval for exec commands

### Git LFS Migration — Product Repos
**Run:** bash `/home/aria/.openclaw/workspace/scripts/lfs-migrate-products.sh` and `lfs-pull-rebase-push.sh`
**Results:** 78 ✅ pushed | 19 ⏭ skipped (no remote) | 11 ❌ failed (diverged history)

**Failed repos (need `git pull --no-rebase` + retry):**
ai-teacher-assistant, attendance-monitoring, digital-entertainment-space-toy-app-space, dinq, finance-rotating-savings-group, gambling-awareness, image-recognition-platform, inventory-tracking, personal-accounting, vertical-saas-for-craft-small-batch-production, wanderkit

**Also done:** All HTTPS remotes converted to SSH across all product repos.

### Wanderkit Specifics
- 6.8GB working tree, 4.4GB `.git` (node_modules from concept-b-assembly tracked in history)
- Remote had 3 commits we didn't have: privacy policy, contact info update, backlog renumber
- **Moved ALL videos + frames** to `~/For Deletion/wanderkit/` (~5.7GB freed from working tree)
- Working tree now 120MB
- Push STILL failing — 4.4GB git history needs to transfer; push kept timing out
- **TODO:** Run `git gc --aggressive` or BFG repo cleaner to slim history before retrying push

### symphdevtools org access
- Raven temporarily made symphdevtools **owner** of `symphco` org for LFS push
- Needs to revert to **member** after push is complete (remind Raven!)

### Nestiq Full Design — COMPLETE
- 28/28 P0 screens generated (NEST-001 through NEST-042)
- Brand: Airy Cerulean `#7BB8D4`, Warm Stone `#C8BFB0`, Soft Amber `#EFA940`; Nunito 700 / Inter 400-500
- Artifacts: brand-guidelines.md/.html, design-brief.md/.html, screen-coverage.md, design-system-prompt.md
- Notified Jhai in #product-nestiq; ready for Build phase

### SafeNow — Shipped Feb 23
- Disaster type color-coded pill badges (keyword-matched) — commit `6720868`
- Active/Archived tabs splitting by `processed` field — commit `e8e9fe7`
- Badge moved inline after headline (no emoji) per Paul's feedback

### Symphony Webchat — Streaming Dupe Fix
- Text appeared doubled during streaming (race between bubble + messages array)
- Fixed by subagent; notified in #aria-status

### FaceTrace Marketing Assets — Generated Feb 23
- Instagram post + meme generated for Xian Baylin
- Saved to `agency/products/image-recognition-platform/market/assets/`
- facetrace-ig-post.png + facetrace-meme.png

### git-lfs installed
- `brew install git-lfs` completed; `git lfs version` = 3.7.1
- `git lfs install` run globally

### Johndorf-core Claude agents — Lingering
- Two `claude --dangerously-skip-permissions` processes (PIDs 1435285, 1691712) running 10-12h
- Tasks were completed Feb 23; these are orphan PTY sessions consuming ~845MB RAM
- johndorf-core itself healthy (200 OK on port 3055)
- OK to kill if RAM needed: `kill 1435285 1691712`

---

## DinQ Landing Page Rebuild — Session Feb 24 UTC

### What Was Done
- Rebuilt `/home/aria/projects/dinq-landing/` from scratch (Next.js 15 + Tailwind v4)
- Pages: Nav, Hero, Social proof bar, Problem (3 pain points), Features (4), Pricing (3 tiers: Community $0 / Club $29 / Facility $79), CTA, Footer
- `page.tsx` (31,205 bytes), `globals.css` (1,770 bytes)
- Server running HTTP 200 on port 3101 → `dinq.landing.symph.co`

### Key Corrections / Rules Established
- **NEVER use PNG/image for geometric shapes** — always inline SVG with CSS animations
  - Logged to `memory/error-log.md`
  - Logged to `/home/aria/products/dinq/CLAUDE.md` Gotchas section
- `HeroGeometric()` component added to `page.tsx` (line 147): 4 stroke-only shapes
  - Colors: `rgba(212,255,0, 0.05–0.10)`, animations: `floatA` (35s), `floatB` (42s reverse)
  - `prefers-reduced-motion` guard in `globals.css`
- Noise texture (`data:image/svg+xml` SVG feTurbulence) is intentional — keep it

### CLAUDE.md Pipeline — Now Mandatory
- Template: `skills/build-orchestrator/templates/CLAUDE.md.template`
- Generator: `skills/build-orchestrator/scripts/generate-claude-md.sh`
- Auto-runs when product advances to Build phase (proceed API route)
- Audited with `claude-md-improver` — target B grade (70+)
- DinQ CLAUDE.md score: 88/B+ (was 62/C+)
- PIPELINES.md updated with Steps 0/0b/0c

### Ralph Loop — Taste Pass Confirmed
- Session `dawn-fjord` ran Ralph loop; exited with `<promise>TASTE_PASS</promise>`
- All 6 taste protocol criteria confirmed

### Pending (next session)
- **Analyze `dinq.pro` animation style** — Ed wants hero geometric SVG animations to match that site
  - `dinq.pro` is a pickleball open play manager (fair queue rotation)
  - App structure: AuthProvider, GroupProvider, GameProvider, AuthGate, TopNavbar, BottomNav
  - Design: `--color-accent-primary: #00ade5` (cyan), dark theme
  - CSS animations: `fadeIn`, `scaleIn`, `slideInUp`, `fadeOut`, `slideUp`, `slideDown`, `meshMove`, `marquee`, `shimmer`
  - Site is fully client-side rendered (Next.js BAILOUT_TO_CLIENT_SIDE_RENDERING) — JS analysis needed
  - Page JS chunks: `bbe3775f04d1afce.js` + `a8c04dce16e5e666.js`
- Update `HeroGeometric()` and `globals.css` animations to match `dinq.pro` style if different
- Screenshot `dinq.landing.symph.co` to confirm final visual

### Browser Tool Status
- Was down (OpenClaw gateway timeout `ws://127.0.0.1:18789`) — `openclaw gateway restart` was triggered
- Gateway confirmed running again (systemd) by next check
- If browser is needed: `openclaw gateway restart` then check status before use

### DinQ App Phase Breakdown (from dinq.pro analysis)
User (edikuun) asked about app phases — analysis was in progress at compaction.
- DinQ manages pickleball open play queue: fair rotations so everyone plays equally
- Key providers imply: auth → group/court management → game/session lifecycle
- Need to complete JS bundle analysis to answer fully

### Key File Paths
- DinQ CLAUDE.md: `/home/aria/products/dinq/CLAUDE.md`
- Landing page: `/home/aria/projects/dinq-landing/` (port 3101)
- Build context: `/home/aria/projects/dinq-landing/BUILD-CONTEXT.md` (2089 lines, 89KB)
- Generator script: `skills/build-orchestrator/scripts/generate-claude-md.sh`
- Symphony proceed route: `/home/aria/projects/symph-agency/src/app/api/products/[slug]/proceed/route.ts`

---

## WanderKit — Batch A PROD Deploy Session (Feb 24 UTC)

### Context
Tom explicitly approved Batch A (#130 + #134) for PROD deploy after confirming gold brown (#996515) on DEV.

### Batch A Contents
- **#130**: BookingStatus type narrowed to `'not-booked' | 'confirmed'`; dead `pending`/`cancelled` cases removed from ActivityModal, ItineraryTab, ActivitiesTab
- **#134**: Transit color → gold brown `#996515` (bright) / `#7A5010` (dark); Misc → rose `#F43F5E`; expenses breakdown cap removed (no more `slice(0,4)` or "+N more")

### Commit
- HEAD commit for PROD: `1dc1a9f` — "feat(batch-a): category color audit + booking status cleanup — #130 #134"
- Changes span commits `925d8a6` → `1dc1a9f` (HEAD~3..HEAD)
- 8 files changed total (some in earlier auto-backup commits, all verified via `git diff HEAD~3 HEAD --name-only`)

### Files Changed
- `src/lib/utils/category.ts` — getCategoryBrightColor() Transit=`#996515`, getCategoryHexColor() Transit=`#7A5010`
- `src/app/globals.css` — `.category-transit` (stone), `.category-misc` (rose)
- `src/types/entities.ts` — BookingStatus narrowed
- `src/components/trip/ActivityModal.tsx` — removed pending case
- `src/components/trip/ItineraryTab.tsx` — removed pending/cancelled cases
- `src/components/trip/ActivitiesTab.tsx` — removed pending case
- `src/components/trip/OverviewTab.tsx` — removed "+1 more" cap
- `public/mockups/overview-widget-redesign.html`

### Deploy Status
- PROD deploy was approved and ready to trigger at compaction time
- Deploy command: `gcloud builds submit --config=cloudbuild.yaml --substitutions=SHORT_SHA=$(date +%Y%m%d%H%M) --project=symph-wanderkit`
- Repo: `/home/aria/projects/wanderkit`

### Next Batches (after Batch A ships)
| Batch | Tickets | Status |
|-------|---------|--------|
| B | #129 (auto-disable draft chip, silent) + #131 (Payment TBD option) | After Batch A |
| C | #132 (countdown format) + #133 (currency on-blur) | After Batch B |
| D | #103a (Mobile Audit) + #103b (Full PWA) | Sequential, after Batch C |
| Backlog | #135 (Donut chart redesign — rounded corners + gaps) | P2, after Batch C |

### Key Decisions Logged
- **BookingStatus**: Only `'not-booked'` and `'confirmed'` are settable from UI; `pending`/`cancelled` were dead code
- **Category colors final**: Transit=gold brown `#996515`, Misc=rose `#F43F5E`; full palette in `category.ts`
- **Batching plan**: A → B → C → D (always DEV first → Tom verify → PROD)
- **Feedback tickets**: WK-001 through WK-004 resolved; next available WK-005

### WanderKit Key File Paths
- Backlog: `/home/aria/products/wanderkit/design/DEVELOPMENT-BACKLOG.md`
- Category utils: `/home/aria/projects/wanderkit/src/lib/utils/category.ts`
- App at: `/home/aria/projects/wanderkit/` (DEV port 3006 → `wanderkit.dev.apps.symph.co`)
- PROD: `wanderkit.symph.co` → Cloud Run `wanderkit-prod` (project `symph-wanderkit`)
- Latest confirmed PROD revision before Batch A: `wanderkit-prod-00033-d8f` (#128)

---

## Video QA Pipeline — #taste-kitchen thread with loewe (04:00–04:46 UTC)

### Context
Continued from the marketing asset QA pipeline conversation. loewe requested extending the pipeline to support video generation, then doing a full end-to-end test.

### Decisions Made
- **Two-stage video generation pipeline** (final design):
  - Stage 1: Veo generates cinematic content — text instructions STRIPPED from prompts. QA scores visual keyframes. Visual failures → Generator retry with revised prompt (max 3x). Text failures → skip retry, proceed to Stage 2.
  - Stage 2: ffmpeg burns captions/CTA/URL via Pillow PNG overlay. No Veo involved. Text is pixel-perfect.
- **80/100 confidence threshold** before surfacing to human review (loewe confirmed)
- **Option A retries** (full loop, not single-attempt) confirmed by loewe
- **Pre-generation pacing QA** deferred (can optimize later)
- **No worktree** for Brandcast app — no commits yet, worked directly

### What Was Built (this session)

**Python (marketing-qa-reviewer):**
- `generator/scene_card_reviewer.py` — 5-layer pre-generation QA for scene cards (arc, color, hook quality, pacing, motion consistency). 100/100 on revised VectorPM cards.
- `generator/video_critique_router.py` — classifies video frame failures as REGENERATE vs POST_PROCESS vs ESCALATE. Distinguishes Veo model ceiling (text) from fixable visual failures.
- `generator/video_generator_loop.py` — full Generator ↔ Discriminator closed loop for video. Calls Veo, QA, route, retry, burn captions.
- `utils/caption_burner.py` — ffmpeg text overlay. Uses Pillow PNG composite fallback (drawtext not compiled in the linuxbrew ffmpeg build).

**Brandcast app (`/home/aria/products/brandcast/apps/main`):**
- `src/app/api/video/qa/route.ts` — new QA endpoint: ffmpeg keyframe extraction + Python QA pipeline
- Updated `generate/route.ts` — accepts `product` + `sceneCount`, auto-runs QA after Veo download, returns `qa` in response
- `VideoNode.tsx` — added `QABadge` component (PASS/WARNING/FAIL badge with collapsible keyframe thumbnails)
- `GeneratorNode.tsx` — added `product?: string` field
- `page.tsx` — threads `product` from scene cards through to the QA pipeline
- Initial git commit: `20904ba` (root commit, all 281 files)

**HEARTBEAT.md** — updated Brandcast Copilot Jobs section to include SceneCardReviewer self-review step before writing scene cards.

**Symphony (symph-agency) deployed:**
- `DesignProduction.tsx` retry flow: replaced Math.random() mock with real `/api/aria/generate-asset` call
- Pitch deck route: improved prompt with aesthetic profile injection (no full QA for discovery assets)
- Commit: `4b587a5`

### Key Technical Findings
- **Veo 3.1 cannot render legible text** — any text in video prompts results in garbled characters, mixed scripts, hallucinated words. B3 TEXT HARD FAIL fires on every frame with text. This is a model ceiling, not a fixable prompt issue.
- **VectorPM test video QA results**: hook/scene2/scene3 all FAIL (text artifacts), closing frame PASS 83/100. Caption burn-in fixed text layer cleanly.
- **ffmpeg linuxbrew build** (v8.0.1) does NOT have `--enable-libfreetype` → no drawtext filter. CaptionBurner uses Pillow PNG overlay fallback instead.
- **Brandcast app location**: `/home/aria/products/brandcast/apps/main/` (port 3071 in caddy-ports.json)
- **Veo generation path**: `POST /api/video/generate` (starts operation) → `PUT /api/video/generate` (polls + downloads + saves MP4 to `/home/aria/.openclaw/media/brandcast/videos/`)

### People
- **loewe** — led the brainstorm. Confirmed: Option A (full retry loop), 80/100 threshold, extend existing pipeline (not new module), text still needed in videos.

### Files Modified Today (video pipeline)
- `/home/aria/projects/marketing-qa-reviewer/generator/scene_card_reviewer.py` (new)
- `/home/aria/projects/marketing-qa-reviewer/generator/video_critique_router.py` (new)
- `/home/aria/projects/marketing-qa-reviewer/generator/video_generator_loop.py` (new)
- `/home/aria/projects/marketing-qa-reviewer/utils/caption_burner.py` (new)
- `/home/aria/projects/marketing-qa-reviewer/generator/__init__.py` (updated exports)
- `/home/aria/products/brandcast/apps/main/src/app/api/video/qa/route.ts` (new)
- `/home/aria/products/brandcast/apps/main/src/app/api/video/generate/route.ts` (updated)
- `/home/aria/products/brandcast/apps/main/src/components/video/VideoNode.tsx` (QABadge added)
- `/home/aria/products/brandcast/apps/main/src/components/video/GeneratorNode.tsx` (product field)
- `/home/aria/products/brandcast/apps/main/src/app/video/[sessionId]/page.tsx` (product threading)


---

## Tiangge Superadmin Waitlist — Session Feb 24 UTC (Jhai Salcedo)

### What Was Built
- **Superadmin Waitlist page**: `/src/app/superadmin/waitlist/page.tsx` — table with name, email, role badge, joinedAt, source; summary stat cards; search/filter; CSV export
- **Firestore security rules** updated to allow `superadmin` role to read `waitlist` collection; deployed via `firebase deploy --only firestore:rules --project symph-tiangge`
- **"I am a…" dropdown** already existed in `src/components/waitlist-modal.tsx` (Shopper/Merchant/Organizer) — no changes needed
- **Shopper badge** fixed from dark-on-dark to brand accent colors (`bg-accent/20 text-secondary border-accent/40`)
- **Renamed "General" → "Shopper"** throughout: filter pills, counts, badge display, normalizing legacy `general` entries
- **Server-side API route** (`/src/app/api/superadmin/waitlist/route.ts`) — replaces client-side `getDocs` to avoid stale Firestore cache; uses Admin SDK; requires `Authorization: Bearer <idToken>`; verifies `superadmin` role via Firestore
- **Nav item** added to superadmin layout (`ClipboardList` icon → "Waitlist")
- **Dashboard** updated to show Waitlisters stat card + quick action

### Key Technical Decisions
- **Server-side API**: Client-side Firestore with security rules timed out; Admin SDK route bypasses rules and avoids cache. This pattern should be applied to other superadmin pages (users, merchants, organizers).
- **`general` → `shopper` normalization**: Legacy entries stored as `"general"` display as "Shopper" everywhere
- **Waitlist collection name**: `waitlist` (no namespace prefix, even though `.env.production` has `NEXT_PUBLIC_FIRESTORE_NAMESPACE=prod`)

### Pending / Next Steps
- Jhai needs to hard-refresh `/superadmin/waitlist` and confirm data loads
- Investigate missing person's submission — ask Jhai for name/email to search Firestore directly
- Consider adding a "source button" column to track which landing page CTA button drove the signup (requested, not yet implemented)

### Key File Paths
- Waitlist page: `/home/aria/products/tiangge/apps/main/src/app/superadmin/waitlist/page.tsx`
- API route: `/home/aria/products/tiangge/apps/main/src/app/api/superadmin/waitlist/route.ts`
- WaitlistModal: `/home/aria/products/tiangge/apps/main/src/components/waitlist-modal.tsx`
- Firestore rules: `/home/aria/products/tiangge/apps/main/firestore.rules`
- Backlog: `/home/aria/products/tiangge/docs/BACKLOG.md`

### Tiangge Key Info
- Dev URL: `https://tiangge.dev.apps.symph.co` (port 3014)
- Production: `https://tiangge.app` (same PM2 process)
- Firestore project: `symph-tiangge`
- Brand palette: Primary `#465940` (Sage), Secondary `#C07B5A` (Terracotta), Accent `#E8A598` (Peach) — NO blue
- Waitlist role values: `"merchant"` | `"organizer"` | `"shopper"` (legacy `"general"` normalized to `"shopper"`)
- Deploy: `bash /home/aria/products/tiangge/apps/main/deploy.sh`
- Worktrees: `/home/aria/products/tiangge/worktrees/`
- PM2 process: `tiangge` (id 81), port 3014

### Backlog Priority 1 (as of this session)
- Subscription (Symph Pay integration)
- Featured listing
- Customer support
- Elevated analytics
- Source button tracking for waitlist (new — Jhai requested)


## Video QA Pipeline Debugging Session (loewe)

### Session Summary
Ran VectorPM video generation pipeline with full QA stack. Fixed critical routing bugs.

### Key Fixes Applied
1. **B4 Brand Identity → POST_PROCESS** (was treating as visual failure → endless retries)
   - Video frames don't have logos — logo burn-in happens in Stage 2
   - Added `_BRAND_IDENTITY_PATTERNS` to router

2. **Motion blur detection strengthened**
   - Added explicit patterns: `motion blur`, `severe blur`, `B2 HARD FAIL`
   - Before: motion blur was falling through as "brand identity only"

3. **Ignorable patterns refined**
   - B2 Rendering FLAGs (soft JPEG artifacts) now filtered for video frames
   - Only B2 HARD FAILs trigger action

### Pipeline Run Results
- Scene cards: PASS 89/100 (pacing warning only)
- Stage 1: 1 attempt, all frames routed to POST_PROCESS ✅
- Stage 2: Caption burn-in successful (2 overlays)
- Final status: FAIL (color delta-E issues remain)

### Files Modified
- `/home/aria/projects/marketing-qa-reviewer/generator/video_critique_router.py` — routing logic
- `/home/aria/projects/marketing-qa-reviewer/generator/scene_card_reviewer.py` — arc regex fix

### Output
- Video: `brandcast-video-2026-02-24T05-05-18-804-10ny-captioned.mp4`
- Sent to Discord thread with loewe

### Next Steps
- Tune scene card prompts for better brand color emphasis
- Consider logo overlay in Stage 2 (not just text)
- Test with motion blur detection fix

---

## WanderKit — Concept I v3 Status (Feb 24 UTC, ~05:56)

### What Was Completed
- **Concept I v3 chat bug fix** — root cause: 15 hidden animated messages used `opacity: 0` (kept ~1050px layout space in flex), pushing pre-loaded `.pre` banter off-screen at t=0
  - Fix: `.msg-row { display: none; }` / `.msg-row.show { display: flex; }` / `.msg-row.pre { display: flex; }` same for `.typing-row`
  - Applied to BOTH `chat-merged/chat-9x16.html` AND `chat-merged/chat-16x9.html`
- **9x16 frames re-captured** — `chat-merged/frames-9x16/` (269 frames, 24fps, 11200ms) ✅
- **Start frame screenshots sent to Tom** — `ci-start-9x16.jpg`, `ci-start-16x9.jpg` — Tom approved

### What Still Needs To Happen (v3 incomplete)
1. **Capture 16x9 frames** — re-run capture for `chat-merged/chat-16x9.html` → `chat-merged/frames-16x9/` (need separate inline script at 1920×1080 viewport)
2. **Re-encode chat clips** — `tmp/chat-merged-9x16.mp4` and `tmp/chat-merged-16x9.mp4` from new frames
3. **Reassemble v3** — run `assemble.sh` to produce new masters and web cuts
4. **Send compressed v3 to Tom** — Tom explicitly requested this ("send the compressed video for review here")
5. **On Tom's go** — upload masters to Google Drive (new `concept-i/` subfolder)

### Key Working Dir & Files
- **Concept I dir**: `/home/aria/products/wanderkit/market/videos/concept-i/`
- `chat-merged/chat-9x16.html` — phone (1080×1920), v3 display:none fix applied ✅
- `chat-merged/chat-16x9.html` — WhatsApp Web desktop (1920×1080), v3 fix applied ✅
- `chat-merged/frames-9x16/` — 269 frames, done ✅
- `chat-merged/frames-16x9/` — NOT YET CAPTURED ❌
- `assemble.sh` — v2 assembly script; structure: chat-merged(11.25s)→S3(4s)→TC1(2s)→S4(4s)→TC2(2s)→S5(4s)→TC3(2s)→Logo(2.5s); FADE=0.3; total=29.7s
- `tom-uploads/concept-i-music.mp3` — Tom's own track (108s, peaks at 0dB); use `volume=0.25` in ffmpeg

### Video Segments (v2 — stale; need to re-encode chat clip then reassemble)
- Chat: `tmp/chat-merged-9x16.mp4` / `tmp/chat-merged-16x9.mp4` (NEED RERENDER)
- S3 composites: `tmp/s3-16x9.mp4` / `tmp/s3-9x16.mp4` (still valid)
- S4/S5 copies, text cards: all still valid in `tmp/`

### capture-chat.js Notes
- File at `/home/aria/products/wanderkit/market/videos/concept-i/capture-chat.js`
- Uses 1080×1920 viewport hardcoded — **need separate inline version for 16x9** (1920×1080)
- Puppeteer Chrome: `~/.cache/puppeteer/chrome/linux-145.0.7632.77/chrome-linux64/chrome`
- Required args: `--no-sandbox`, `--disable-setuid-sandbox`, `--disable-dev-shm-usage`, `--disable-gpu`, `--force-device-scale-factor=1`, `--allow-file-access-from-files`
- node_modules: `/home/aria/products/wanderkit/market/videos/concept-i/s1-chat/node_modules/puppeteer` (may need to check path)

### Days 6–10 Carousels
- Still PENDING — waiting for briefs from Tom

### D7 Engagement Post
- HTML at `/home/aria/products/wanderkit/market/day7-post/d7-engagement.html` — written, not yet rendered
- Render to PNG when ready

---

## Johndorf Core — Platform Fragmentation Reframe (Feb 24 UTC, ~05:00)

### Narrative Shift
**OLD framing:** "Running three branches is hard" (operational complexity)
**NEW framing:** "Four platforms, zero unified view" (data silo / integration problem)

Johndorf Core is now positioned as an **integration + intelligence layer** sitting on top of four existing systems:
| System | Domain |
|--------|--------|
| **SAP** | Finance / General Ledger |
| **CRM** | Sales / Leads / Reservations |
| **PayRuler** | Payroll / HR / Attendance |
| **Prime ERP** | Collections / Contract Processing / Operations |

Core problem: management stitches data from all four via manual spreadsheets → fragmented, error-prone, delayed.

### Slides Changed
**Slide 2 (The Challenge)** — rewrote to show four platform cards with "Four platforms. Zero unified view." headline; red banner explaining manual spreadsheet stitching.

**Slide 3 (The Solution)** — rewrote to show architecture flow diagram:
- Source systems row (SAP, CRM, PayRuler, Prime ERP)
- Connector arrows (→ integration layer)
- "JOHNDORF CORE Unified Intelligence Layer" center box
- Output cards (Business KPIs, Risk Monitoring, Trend Analytics, Ops Health)

### Icon Imports Updated in `presentation/page.tsx`
Added: `Users`, `Server`, `Banknote`, `Settings2`, `GitMerge`, `XCircle`, `Layers`
Removed: `Map`

### Build Status at Compaction
Build was running in session `fast-willow` (pid 3984515). Result unknown — may have compile errors from unused imports (`Eye`, `Calendar`, `AlertTriangle`).

### Next Steps
1. Check build result for session `fast-willow`; fix any TS errors
2. Deploy (`rm -rf .next/standalone/.next/static && cp -r .next/static .next/standalone/.next/static`; restart on port 3055)
3. Verify presentation at `https://johndorf-core.dev.apps.symph.co/presentation`
4. Rewrite demo script (`/home/aria/projects/johndorf-core/public/johndorf-core-demo-script.txt`) — update Slide 2/3 sections to use SAP/CRM/PayRuler/Prime ERP framing
5. Re-deploy and confirm both assets live

### Key Paths
- Presentation: `/home/aria/projects/johndorf-core/src/app/(authenticated)/presentation/page.tsx`
- Demo script: `/home/aria/projects/johndorf-core/public/johndorf-core-demo-script.txt`
- Dashboard: `/home/aria/projects/johndorf-core/src/app/(authenticated)/dashboard/page.tsx`
- Server: port 3055, `https://johndorf-core.dev.apps.symph.co`
- Start command: `PORT=3055 nohup node start-server.js` (in `/home/aria/projects/johndorf-core/`)
- Static files fix (MANDATORY after each build): `rm -rf .next/standalone/.next/static && cp -r .next/static .next/standalone/.next/static`


---

## Clocklane App Session — #cl-development (Feb 24, 2026 early AM)

### Repo: `symphco/clocklane-app` (Next.js, port 3064)
- Dev URL: `clocklane.dev.apps.symph.co`
- Prod URL: `clocklane.com` (Cloud Run service `clocklane` in `theagency-ai`, region `us-central1`)
- Deploy: `gcloud run deploy clocklane --source . --project theagency-ai --region us-central1 --allow-unauthenticated --port 8080 --set-build-env-vars "NEXT_PUBLIC_*" --set-env-vars "NODE_ENV=production,FIREBASE_SERVICE_ACCOUNT_KEY_B64=..."`
- Git: `development` → test, `main` → prod (always merge development→main for prod push)
- NEVER deploy `symphco/clocklane` (Vite landing page) to clocklane.com — app lives there now

### Changes shipped this session:
1. **Staff On Duty slide-over** — Dashboard metric card clickable → slide-over panel (name, clock-in time, elapsed, green pulse dot); API extended to return `activeStaff[]` with name+clockIn
2. **`formatHoursMinutes` util** — `src/lib/labor.ts` canonical function; replaced all `.toFixed(1)}h` usages in POS timesheets, labor report, dashboard
3. **Sales report UX** — removed redundant By Day/Week/Month dropdown; From/To date pickers only shown when Custom preset selected; Export CSV always visible; added skeleton loader
4. **Bar chart hover fix** — `cursor={{ fill: "transparent" }}` on `<Tooltip>` in Top Selling Products bar chart
5. **Clock Out button text bug fix** — added `clockActionType: "in"|"out"|null` state to prevent "Clocking in..." showing after clock-out
6. **Timesheet page overhaul**:
   - Weekly/Monthly view toggle
   - Stats bar (Total Hours, Labor Cost, Staff Worked, Overtime count)
   - Earnings column in weekly grid
   - Overtime flag (>40h/week = amber "OT" badge + row highlight)
   - Totals footer row (daily subtotals + grand totals)
   - Monthly summary: Days Worked, Avg/Day, Total Hours, Total Pay per employee
   - CSV export (both views)
7. **Currency context in timesheets** — all hardcoded `$` replaced with `fmt()` from `CurrencyCtx`; covers stats bar, weekly/monthly pay columns, footer totals, shift detail modal, CSV values

### Ported from `symphco/clocklane` → `symphco/clocklane-app`:
- Meta Pixel (ID: 895591059745946) in `layout.tsx`
- `fbq('track', 'Lead')` on beta form submit
- Live clock widget in hero (analog hands + HH:MM:SS ticker)
- Correct SVG wordmark logo (from assets.landing.symph.co)
- GTM (GTM-MQ6N6LVM) was already present

### Key file locations (clocklane-app):
- POS component: `src/components/ClocklanePOS.tsx` (monolithic, ~2300 lines)
- Dashboard: `src/app/app/dashboard/page.tsx`
- Sales report: `src/app/app/reports/sales/page.tsx`
- Labor report: `src/app/app/reports/labor/page.tsx`
- Currency context: `CurrencyCtx` + `useCurrency()` hook in ClocklanePOS.tsx (line ~54-55)
- Hours util: `formatHoursMinutes()` in `src/lib/labor.ts`

### Ragan's workflow preference:
- Always test on development branch/dev URL first
- "Push to prod" = merge development→main + Cloud Run deploy
- Always use `symphco/clocklane-app` for prod (not the Vite landing page repo)

### Currency fix (in progress at compaction — Feb 24, ~07:47 UTC)
**Task:** Replace all hardcoded `$` signs in `TimesheetsPage` with `fmt()` from `useCurrency()`

**Already done:**
- Added `const fmt = useCurrency();` to `TimesheetsPage`
- Stats bar "Labor Cost": `$${stats.totalCost.toFixed(2)}` → `fmt(stats.totalCost)`
- Weekly CSV header: `"Total Pay ($)"` → `"Total Pay"`
- Weekly CSV row values: `row.totalEarnings.toFixed(2)` → `fmt(row.totalEarnings)`
- Monthly CSV header: `"Total Pay ($)"` → `"Total Pay"`
- Monthly CSV row values: `row.totalEarnings.toFixed(2)` → `fmt(row.totalEarnings)`

**Still pending (UI table cells):**
- Weekly grid Pay column `<td>`: `` `$${row.totalEarnings.toFixed(2)}` `` → `{fmt(row.totalEarnings)}` (~line 2138)
- Weekly grid `<tfoot>` total pay cell: `` `$${stats.totalCost.toFixed(2)}` `` → `{fmt(stats.totalCost)}` (~line 2160)
- Monthly summary Pay column `<td>`: same pattern (~line 2210)
- Monthly summary `<tfoot>` total pay: same pattern (~line 2226)
- Shift detail modal pay: `` `$${(selectedTs.earnings || 0).toFixed(2)}` `` → `fmt(selectedTs.earnings || 0)` (~line 2251)

**After currency fix:** Build, test on dev, commit to `development`, merge to `main`, deploy to Cloud Run (`clocklane` service, `theagency-ai` project, `us-central1`)

---

## RoundSave — Phases 1–4 Complete (Feb 24 UTC, Med)

### Product
- **Name:** RoundSave — Digital Paluwagan Platform (rotating savings group)
- **URL:** `https://roundsave.dev.apps.symph.co` (port 3035)
- **Path:** `/home/aria/products/roundsave/`
- **Git remote:** `git@github.com:symphco/product-roundsave.git`
- **Active branch:** `feature/phase1-core-mechanics`

### Phases Completed
| Phase | Commits | Key Deliverables |
|-------|---------|-----------------|
| 1 | `f486417` | CycleTracker, PaymentLogModal, PayoutModal, group-actions.ts (startGroup, logPayment, verifyPayment, triggerPayout, advanceCycle, approveMember, removeMember, rejectMember) |
| 2 | `51b5a27` | /transactions page (tabs, filters, summary cards, CSV export), ContributionMatrix, Dashboard real data + quick actions |
| 3+4 | `fc0044d` | notification-actions.ts, /notifications page (unread badge, Today/Earlier grouping), trust-score-actions.ts (calculateAndUpdateTrustScore, getTrustScoreBreakdown), /profile (trust score breakdown, edit mode), /settings (notification prefs, privacy, password reset), AppSidebar unread badge (30s auto-refresh) |
| Bug fix | `481bd88` | Fixed missing `}` in firestore.rules (dev_notifications were incorrectly nested inside dev_trust_scores block); added collectionGroup rule for member queries |

### App Stack
- Next.js 15 + Tailwind CSS + shadcn/ui + Firebase Auth + Firestore
- Firestore namespace: `dev_` prefix (dev_groups, dev_contributions, dev_payouts, dev_trust_scores, dev_notifications, dev_users)
- Currency: Philippine Peso ₱ via `formatCurrency()` from `@/lib/hooks/useAuth`
- Trust tiers: Bronze 0–49, Silver 50–69, Gold 70–89, Platinum 90–100
- Trust formula: on-time rate × 40 + completion rate × 30 + min(tenure_months, 10) × 3 = max 100; init at 50

### In-Progress at Compaction — Trust Score Guard
Adding new-user guard to `trust-score-actions.ts` → `calculateAndUpdateTrustScore()` and `getTrustScoreBreakdown()`:
```typescript
// Guard: if user has zero activity, preserve their current score (don't drop from default 50)
if (totalDueContributions === 0 && totalGroupsJoined === 0) {
  return userData.trustScore ?? 50;
}
```
**Blocked:** `edit` tool failed — the identical block appears at line ~128 (calculateAndUpdateTrustScore) AND line ~262 (getTrustScoreBreakdown). Must use unique surrounding context per function to disambiguate the edit targets.

### Key Technical Notes
- `getCountFromServer` used in `getUnreadCount()` for efficient unread badge (no full doc scan)
- `collectionGroup(db, 'members')` in trust-score-actions.ts → requires `/{path=**}/members/{memberId}` Firestore rule
- Notifications are always fire-and-forget (try/catch, never block main action)
- Server start: `nohup node_modules/.bin/next start -p 3035`
- Log: `/home/aria/projects/.aria-logs/roundsave.log`

### Next Steps
1. Complete trust score guard edit (use unique context to disambiguate the two identical blocks)
2. Med confirms Phases 1–4 work in browser → merge to master
3. Phase 5: PWA push notifications, mobile improvements, landing page refresh

---

## VectorPM FB & Insta — Content Creation Session (James Kuizon, Feb 23–24 UTC)

### Person
- **James Michael Kuizon** (`jhamezzzzz1315`) — VectorPM social media / growth team
- Communication style: direct, iterative, gives clear feedback on what's wrong

### Channel
- Discord: `#vectorpm-fb-insta` (channel ID: 1473220041133523036)

### Assets Created This Session

#### Static Posts
1. **Monday Drop post** — "Monday means momentum." dark kanban board design → sent + revised caption
2. **Awareness post 1** — "Meet VectorPM. The PM tool built for teams that ship." — feature list + benefits
3. **Benefits post 1** — 2×2 benefits grid (Kanban, Sprint, Workload, Tracking)
4. **Benefits post 2** — "Less time planning. More time shipping." (stat "4×" was REMOVED — James caught it)

All posts: 1080×1080px, HTML → puppeteer → PNG, dark bg `#111111`, orange `#ea580c`, Space Grotesk, correct Vector logo SVG

#### Reels (Remotion + OpenAI TTS Nova voice)
1. **Awareness Reel** — `vectorpm-awareness-reel-final.mp4` — 20s, "Your team is always behind" → meet VectorPM → features → CTA
2. **Benefits Reel** — `vectorpm-benefits-reel-final.mp4` — 22s, "Your team is losing hours" → 3 pain lines → VectorPM fixes this → 3 benefits → dark CTA slide

All files saved: `/home/aria/products/vector/marketing/videos/`

### Standing Rules (James)
- **ALWAYS add voiceover to all VectorPM reels** — Nova voice, scene-synced. Never deliver a reel without it. (Set 2026-02-24)
- **Never mention competing products** (Slack, etc.) in copy
- Post timing: Monday Drop @ 8:30 AM PHT, Reel @ Wednesday 9 AM PHT

### Tools & Scripts Built
- `gen-voiceover-synced.py` / `gen-benefits-voiceover.py` — OpenAI TTS + ffmpeg adelay/amix scene sync
- `screenshot-benefits.cjs`, `screenshot.cjs` — puppeteer rendering scripts
- Remotion project at `/home/aria/products/vector/marketing/remotion/` (node_modules installed)
  - `VectorAd` composition — awareness reel scenes
  - `VectorBenefitsAd` composition — benefits reel scenes

### Content Generator App Built
- **vector-ideas.dev.apps.symph.co** — VectorPM content generator (cloned from overture-ideas)
- Port 3009, `/home/aria/projects/vector-ideas/`
- Dark theme, orange accent, correct Vector logo
- Login page was showing Overture branding → fixed after James flagged it

### Corrections / Learnings
- **NEVER invent stats in marketing copy** — "4×" was fabricated, James caught it. Always use real data or soft language.
- **Each visual text line needs its own voiceover beat** — don't compress 3 staggered lines into 1 segment
- **When voiceover windows < 2s per line**, combine into one flowing segment — never try to fit 3 sentences into 1.2s (sounds like rapping)
- **CTA slide**: if background and logo are the same color (orange on orange) → logo is invisible. CTA now uses dark bg `#111111` with orange logo
- **atempo max 1.2x** is the target ceiling — anything higher sounds noticeably fast

### Vector Brand Reference (Confirmed)
- Logo: SVG stacked horizontal rectangles (NOT aperture, NOT checkmark, NOT radar)
- Icon color: `#ea580c` orange
- BG: `#111111`
- Font: Space Grotesk
- Tagline: "Move fast. Ship faster."
- URL: vectorpm.io


---

## WanderKit Dev Session — #wk-development (2026-02-24)

### Google Sign-In DEV auth loop (in progress)
- **Symptom**: After Google auth, WanderKit loading page appears → redirect back to login (auth loop)
- **PROD**: Works correctly for both `tjrgarcia19@gmail.com` and `tom.garcia@symph.co`
- **DEV domain**: `wanderkit.dev.apps.symph.co`
- **Root cause suspect**: OAuth redirect URIs / authorized JavaScript origins NOT configured in Google Cloud Console for DEV domain; OR Firebase redirect result persistence failing cross-origin
- **Changes made (signInWithRedirect switch)**:
  - `src/lib/hooks/useAuth.ts`: `signInWithGoogle()` → `signInWithRedirect`; `getRedirectResult(auth)` in `useEffect` on mount
  - `src/app/auth/page.tsx`: Google button handler simplified; `setIsGoogleLoading(false)` only on error
  - Tom confirmed: popup was NOT the root cause — redirect flow was already showing the same loop
- **Next investigative steps**:
  1. Check Google Cloud Console OAuth 2.0 client → Authorized JavaScript origins + Redirect URIs for `wanderkit.dev.apps.symph.co`
  2. `authDomain` is still `symph-wanderkit.firebaseapp.com` — verify this is correct for DEV
  3. Check if Firebase redirect result is being lost due to cross-origin cookie blocking
  4. Tom to confirm whether issue existed before redirect switch (popup also looped)
- **Status**: Awaiting Tom's deeper debug info (network tab, console errors during redirect loop)

### WanderKit Batch Plan (current state)
| Batch | Tickets | Status |
|-------|---------|--------|
| A | #130 + #134 | ✅ PROD `00034-v2x` + `00035-bsc` |
| B | #129 + #131 | 📅 Next — awaiting Tom "go" after Google Sign-In fixed |
| C | #132 + #133 | 📅 After B |
| D | #103a + #103b | 📅 After C (sequential) |

### WanderKit Key Facts (pinned)
- **PROD**: `wanderkit.symph.co` → Cloud Run `wanderkit-prod`, project `symph-wanderkit`
- **DEV**: `wanderkit.dev.apps.symph.co` (port 3006), `/home/aria/projects/wanderkit`
- **Namespace isolation**: DEV = `dev_*` Firestore collections; PROD = `prod_*`
- **Tom DEV creds**: `tjrgarcia19@gmail.com` / `WanderKitDev2026!`
- **Tom's UID**: `HdZrPfZ0KDRZNdnlok6LiRmrElo1`; Trip: `Sn5kgt6HDRwSKoOrwnY6`
- **Cloud Build**: `gcloud builds submit --config=cloudbuild.yaml --substitutions=SHORT_SHA=$(date +%Y%m%d%H%M) --project=symph-wanderkit`
- **Deploy DEV**: `bash deploy-dev.sh`
- **Workflow rule**: NEVER deploy PROD without Tom's explicit sign-off on DEV; NEVER make changes without Tom saying "go"
- **Backlog**: `/home/aria/products/wanderkit/design/DEVELOPMENT-BACKLOG.md`
- **GTM**: `GTM-T9QB3DM8` | **GA**: `G-4B74BPFW0Z`

### Category Colors (FINAL — PROD `00035-bsc`)
- Flight: sky-500 `#0EA5E9`
- Accommodation: violet-500 `#8B5CF6`
- Transit: brown `#7A5010` / `bg-stone-100`
- Layover: yellow-400 `#FACC15` / `bg-yellow-100`
- Attraction: emerald-500 `#10B981`
- Event: pink-500 `#EC4899`
- Food: orange `#FFA500` / `bg-orange-100`
- Misc: red-600 `#DC2626` / `bg-red-100`

---

## Encore Client Portal — QA Sprint (Feb 24, 2026) #encore #qa

### Session Summary
Completed full UX audit implementation + QA test coverage for the Encore client portal (`encoreclients.com`).

### All Fixes Deployed (encore-00069-rxb, Cloud Run theagency-ai/us-central1)

| Branch | Fix | Revision |
|--------|-----|---------|
| fix/csp-third-party-domains | Google Fonts, HubSpot, FB Pixel, GA in CSP | 00060 |
| fix/csp-hubspot-banner | js-na2.hs-banner.com in script-src + connect-src | 00061 |
| fix/csp-cache-control | no-store, must-revalidate (was s-maxage=31536000) | 00062 |
| fix/ux-logout-banner-tooltips | Logout dialog, banner localStorage, Radix tooltips | 00063 |
| fix/ux-modal-overlay | bg-white modal, bg-black/75 overlay, explicit text colors in dialog.tsx | 00064 |
| fix/ux-stat-cards-approvals-filter-messages-empty | Dashboard stat cards, Approvals filter tabs, Messages empty state | 00065 |
| fix/ux-skeletons-accessibility | Skeleton loading (messages), aria-labels, sr-only unread | 00066 |
| fix/ux-invoice-summary-cards | Invoice summary cards with smart overdue color | 00067 |
| fix/ux-support-stat-cards | Support stat cards with smart SLA/CSAT/tickets color | 00068 |
| fix/ux-reports-stat-cards | Reports stat cards — purple/blue/amber/emerald | 00069 |

### QA Test Coverage File
- **File**: `Encore_QA_Test_Coverage.xlsx`
- **Delivered to**: #encore Discord channel
- **Stats**: 108 test cases, 17 bugs documented, all Pass
- **Tabs**: Summary, Smoke Tests (20), Full Tests × 8 tabs, Security/CSP, Bug Tracker
- **Generated via**: `/tmp/generate_encore_qa.py` (openpyxl, color-coded status cells, freeze panes, auto-filter)

### Key Technical Facts
- **Stat card pattern**: `border-l-4 {accent}` + `h-9 w-9 rounded-lg {iconBg}` pill + `text-2xl font-semibold font-mono-stat` value + `text-sm text-[#3f3f46]` label
- **Dialog hardcoded colors**: `bg-background` resolves dark in prod — must use explicit `bg-white`, `text-[#18181b]`
- **CSP localStorage key**: `encore_welcome_banner_dismissed`
- **Tooltip**: Radix UI unified package `import { Tooltip as TooltipPrimitive } from "radix-ui"` — side="right" for sidebar
- **Skeleton**: `src/components/ui/skeleton.tsx` created; 6 rows mirror thread list shape
- **Smart color logic**: Overdue card → green at $0.00 | Open Tickets → green at 0 | SLA → green ≥95%, amber 80–94% | CSAT → green ≥4.0

---

## Overture Dev Integration — 10:00 AM UTC

### QA Test Script
- **Product**: Overture Dashboards & Analytics v1.4
- **File**: `Overture_QA_Test_Script.xlsx` → `/home/aria/marketing-assets/uploads/`
- **Delivered to**: `#OT: Development` Discord thread (channel ID: `1473964369283907707`)
- **Stats**: 114 test cases — 39 Smoke + 75 Full
- **Tabs**: Part 1 Smoke Tests, Part 2 Full Test Script, Summary
- **Areas covered**: Auth, Onboarding, Dashboard, Settings (save gate), Billing (plan/payment/invoices/quick actions), Alerts, Help Center, Error Handling, Security (IDOR/XSS/privilege escalation), Data Integrity, Boundary/Edge Cases
- **Generated via**: `/tmp/overture_qa_gen.py` (openpyxl, P0/P1/P2 color-coded priority, smoke=blue/full=green row shading)

### Landing Page → App Integration (Zel's Request)
- **Task**: Integrate dev app login/dashboard flow into `overtureinsights.com`
- **Discovery**: Same Next.js codebase already serves both landing page AND app — `/login`, `/dashboard`, `/billing`, etc. all return 200 on prod
- **Key blocker found**: `overtureinsights.com` NOT in Firebase Auth authorized domains
- **Steps completed**:
  1. ✅ Added `overtureinsights.com` + `overture.dev.apps.symph.co` to Firebase Auth authorized domains (symph-overture project)
  2. ✅ Confirmed Google OAuth doesn't need extra redirect URIs (Firebase uses `symph-overture.firebaseapp.com` as auth handler)
  3. ✅ Verified Cloud Run (theagency-ai project) has all `NEXT_PUBLIC_FIREBASE_*` env vars correct
  4. ✅ Updated 7 landing page CTAs from `href="#signup"` → `href="/login"` via worktree `zel-cta-login`
  5. ✅ Deployed revision `overture-00074-jkv` to Cloud Run us-central1

### Key Technical Facts (Overture Prod)
- **Cloud Run service**: `overture` in project `theagency-ai`, region `us-central1`
- **Firebase project**: `symph-overture` (project number: `77215696877`)
- **Auth domain**: `symph-overture.firebaseapp.com`
- **Firebase authorized domains (updated)**: `symph-overture.firebaseapp.com`, `symph-overture.web.app`, `localhost`, `overture-ideas.dev.apps.symph.co`, `overtureinsights.com`, `overture.dev.apps.symph.co`
- **Prod URL**: `https://overtureinsights.com`
- **Dev URL**: `https://overture.dev.apps.symph.co` (port 3080)
- **Latest revision**: `overture-00074-jkv`
- **Existing users in Firebase Auth**: 12 accounts already existed pre-integration
- **Firestore path discrepancy** (still open): `AppContext` reads from `prod_users/{uid}/data/preferences` but onboarding writes to `dev_user_preferences/{uid}` — may cause personalized dashboard greeting not to show post-login

### People
- **Zel (zelvillarosa)**: QA-focused, requested integration. Gave Google account access permission for Firebase/Cloud Run ops.

---

## HOA Solution — Port Update (10:41 UTC)

**URGENT:** Port 3001 is taken by another app. HOA Solution uses **port 3061**.

- Dev URL: https://hoa.dev.apps.symph.co (Caddy already pointing to 3061)
- `caddy-ports.json` already shows `"hoa": 3061` ✅
- Start command:
```bash
PORT=3061
npm run build
lsof -ti :3061 | xargs kill -TERM 2>/dev/null || true
nohup npm run start -- -p 3061 > /home/aria/projects/.aria-logs/hoa.log 2>&1 &
```

---

## Alignly — Landing Page 1:1 Prod Port (Med / med_aduh)

### Goal
Replace dev app root landing page (`src/app/page.tsx`) with faithful 1:1 port of prod `alignlyhq.com`.

### What Was Done
- Fetched prod HTML from `alignlyhq.com` → `/tmp/alignly-prod.html` (52KB, 1541 lines)
- Created worktrees `task-1771926810-839191` + `task-1771930539-1023049`
- Added logout button to sidebar (`src/components/app-layout.tsx`) — merged as commit `7cbd287`
- Replaced `src/app/page.tsx` with verbatim prod CSS injected via `<style dangerouslySetInnerHTML={{ __html: LANDING_CSS }} />` — 1608 lines total
- Deleted `src/app/temp-landing/` directory
- Merged branch `task/task-1771930539-1023049` (commit `2f534f8`) into `master` via fast-forward
- Pushed merged master to `origin` (`github.com:symphco/alignly-app.git`)

### Key Technical Decisions
- **`<style dangerouslySetInnerHTML>` NOT CSS modules** — CSS modules scope `@keyframes` and compound selectors, breaking animations
- **CSS as literal constant** `LANDING_CSS` at line 6, injected via `dangerouslySetInnerHTML={{ __html: LANDING_CSS }}`
- **Waitlist form** POSTs to `/api/waitlist` with `{ email, company: 'Direct Signup', role: 'Unknown', size: '1-10', painPoints: [], source: 'landing' }`
- **Default branch is `master`** (not `main`) — merge scripts hardcode `main`, must use manual merge commands

### Status at Compaction
- Code merged and pushed ✅
- Build + restart + verification still needed:
  1. `npm run build` (allow up to 180s)
  2. Kill port 3060 process, `nohup npm run start -- -p 3060 > /home/aria/projects/.aria-logs/alignly.log 2>&1 &`
  3. `curl -s -o /dev/null -w "%{http_code}" http://localhost:3060/` → expect 200
  4. Notify Med (`med_aduh`, Discord ID `778161610329030676`) in Discord
  5. Clean up worktree: `git worktree remove /home/aria/products/alignly/worktrees/task-1771930539-1023049 --force && git branch -d task/task-1771930539-1023049`

### Key Facts
- **App dir**: `/home/aria/products/alignly/apps/main`
- **Port**: 3060
- **Log**: `/home/aria/projects/.aria-logs/alignly.log`
- **Branch merged**: `task/task-1771930539-1023049` (commit `2f534f8`)
- **Prod palette**: Warm cream `#FEFCF9` — NOT dark
- **No external images**: All avatars are CSS gradient circles (`.avatar-g1` through `.avatar-g4`)
- **Fonts**: Nunito (400,500,600,700) + Inter via Google Fonts
- **Requester**: Med (`med_aduh`, ID `778161610329030676`)
- **SaaS infra**: 4/5 criteria met; Webhooks/Events not yet built


---

## Brandcast Video Generator — Gap Closure Session (loewe, Feb 24)

### Context
loewe raised video generation quality issues in #taste-kitchen. Session ran from ~9:30 AM to 12:40 PM UTC.

### Problems Identified
1. Videos were too abstract (fragment bursts, geometric shapes, no real subjects)
2. No visual consistency across scenes
3. No product uniqueness — all products looked the same
4. Three specific Veo artifacts in v1 video:
   - Floating cursor (hands typing + screen UI visible)
   - Disappearing phone (handheld prop + camera orbit)
   - Face inconsistency (hand-over-face reveal → upper/lower half different age/gender)

### Root Cause
Video generator was running at ~30% of what image generator has:
- Missing: VeoPromptSanitizer (no pre-generation motion failure blocking)
- Missing: FrameDiscriminator (only 1 keyframe attempt, no pre-screen)
- Missing: TemporalCoherenceChecker (no cross-frame consistency check)
- Missing: VideoL4SceneFidelity (frames never checked against scene card description)
- Missing: VideoL5PlatformFit (no safe zone validation)
- Missing: Router↔Sanitizer bridge (revised prompts could re-introduce same failures)
- SceneCardReviewer only wired into Python loop, not Brandcast canvas

### Code Changes Made

#### Build 1 — marketing-qa-reviewer + Brandcast (subagent: video-gen-gap-closure)
- `generator/veo_prompt_sanitizer.py` — blocks OCCLUSION_REVEAL, PROP_PLUS_MOTION, UI_PLUS_HANDS, COMPLEX_TRANSFORM
- `generator/frame_discriminator.py` — 3-variant Gemini generation + L1+L2 pre-screen
- `generator/temporal_coherence_checker.py` — 5-frame temporal scan (object permanence, face consistency, cursor anomaly)
- `VideoGeneratorLoop` — VeoPromptSanitizer wired in before each Veo call
- `src/app/api/copilot/scene-review/route.ts` — SceneCardReviewer endpoint for canvas
- `src/app/api/video/qa/route.ts` — TemporalCoherenceChecker wired in

#### Build 2 — marketing-qa-reviewer + Brandcast (subagent: video-discriminator-l4-l5)
- `generator/video_l4_scene_fidelity.py` — Gemini Vision check: does keyframe match scene card framePrompt?
- `generator/video_l5_platform_fit.py` — Safe zone compliance for 9:16/16:9/1:1 (250px top/bottom for Reels)
- `VideoGeneratorLoop._run_frame_qa()` — now accepts `scene_cards` list, runs L4+L5 per frame
- `VideoGeneratorLoop._apply_critique_revisions()` — Router↔Sanitizer bridge added
- `src/app/api/video/qa/route.ts` — sceneCards + videoPrompt now flow through to all checks; runL4L5VideoChecks() added

#### System Prompt Changes — Brandcast Copilot
- Added Product Visual Worlds per product (real subjects, settings, authentic contexts)
- Visual Treatment Anchor requirement (shared style before any scene cards)
- Concrete framePrompt rules (ban on abstract geometry: "fragments", "streaks", "shards")
- 2026 B2B marketing video format guidance
- `src/lib/data.ts` — videoWorld field added to all products
- `src/lib/prompt-builder.ts` — buildVideoFramePrompt() added (brand injection into Gemini calls)

### Discriminator Architecture (Final State)
```
D1: SceneCardReviewer — arc, color, hook, pacing, motion, AI-tells
D2: FrameDiscriminator — 3-variant Gemini + L1+L2 pre-screen (NEW)
D3: VideoCritiqueRouter — REGENERATE/POST_PROCESS/ESCALATE (existing)
    + VeoPromptSanitizer bridge on every Veo call (NEW)
    + Router↔Sanitizer bridge on retry prompts (NEW)
Temporal: TemporalCoherenceChecker — 5 timestamps, 3 check types (NEW)
L4: VideoL4SceneFidelity — keyframe vs scene card framePrompt (NEW)
L5: VideoL5PlatformFit — safe zone compliance per platform (NEW)
D5: Final stitch delivery
```

### Key Veo Rules Learned (in VeoPromptSanitizer)
- NEVER: hand moves away from face (occlusion reveal → face morph)
- NEVER: handheld prop + camera orbit (object permanence failure)
- NEVER: screen UI visible + hands typing (floating cursor)
- NEVER: objects appear/disappear/change state
- SAFE: slow push-in or pull-back only, no orbit/pan
- SAFE: static subjects, only camera moves

### Video v1 vs v2 (Overture "$100K MRR" caption)
- **v1**: Photorealistic editorial, dramatic performances, all 3 Veo artifacts present
- **v2**: Lo-fi founder cinema — grainy, muted, quiet moments, stillness IS tension
  - Scene 1: Silhouette backlit by blown-out screen (no UI visible)
  - Scene 2: Phone face-down, notification light, no hands, no screen
  - Scene 3: EMPTY CHAIR with Zoom call light (no person = no failure modes)
  - Scene 4: Arms folded, laptop mostly closed, cyan glow only

### v2 Delivery
- File: `overture-100k-v2-full.mp4` in brandcast/videos
- Format: 4 scenes × 8s = 32s, 9:16 vertical, 1080×1920
- All 3 Veo failure modes eliminated at design time

### People Interaction
- loewe (_louveh_) — gave detailed quality feedback on video artifacts, guided aesthetic direction, approved lo-fi founder cinema approach, requested current trending aesthetics
- Communication style: direct, technical, iterative, wants to understand the architecture not just the output

### Aesthetic Direction Learned
- "NOT too perfect or realistic" = lo-fi founder cinema
- Current B2B SaaS trend 2026: raw, grainy, quiet moments, practical lighting, cramped real spaces
- Screen UI in keyframes = Veo death trap (always eliminate or blur to unreadable glow)
- Text on any physical object (books, sticky notes) = Veo garbling risk
- Empty chair / absence / negative space = powerful storytelling without Veo failure risk

---

## WanderKit — Concept J S5 Redo (Feb 24 UTC, ~13:00)

### Context
Tom requested a revised S5 start frame. Original S5 was a walking pose (subject striding forward on boardwalk). After locking all other scenes (S1–S4), Tom requested a standing-still pose: subject with both feet planted, relaxed/peaceful, left hand resting on wooden railing with puka shell bracelet visible.

### What Was Done
- Generated `s5-raw-v3.png` using all 4 approved reference images (S1–S4 raws) for character continuity
- Scene preserved: rice terrace boardwalk, cascading terraces, nipa hut in distance, golden hour sunset lighting
- Subject positioned on LEFT side of frame, linen outfit consistent with S2, hand on railing with bracelet
- Saved to: `/home/aria/products/wanderkit/market/videos/concept-j/s5-frames/s5-raw-v3.png`

### Status at Compaction
- ⏳ Awaiting Tom's review/approval of `s5-raw-v3.png`
- If approved: crop 16:9 (source) + 9:16 (calculate new x offset) → send to Leonardo → assemble Concept J
- If revision: iterate per Tom's feedback

### Key Notes
- v1/v2 S5 were walking pose, horizontally flipped (x=150 crop offset for 9:16)
- v3 is standing-still redo — if approved, replaces v2 as locked frame
- 9:16 crop offset for v3 needs recalculation based on new subject position
- Concept J still blocked on: S5 approval + Leonardo clips for all scenes + music selection

### Concept J Text Cards (All Done)
- TC1 (terracotta), TC2 (terracotta), End card (dark gray #3A3A3A) — both 16:9 and 9:16 PNGs
- Files in `/home/aria/products/wanderkit/market/videos/concept-j/text-cards/`
- All 6 PNGs ready ✅

---

## Marketing Content Review Tab — Fix & Regenerate Feature (Xian, Feb 24 ~13:00 UTC)

### Feature Spec
Xian requested "Fix" button for Warning/Failed assets in Content Review. Full spec saved to:
`/home/aria/.openclaw/workspace/agency/processes/fix-regenerate-spec.md`

### Architecture Decisions
- **Fix = regenerate new image**, never overwrite original
- Uses QA issues from original as fix constraints for new generation
- New asset gets new UUID, `version: N+1`, `previousVersionId`, `fixSource`, `fixIssuesUsed`, `fixTrigger`, `fixTriggeredAt`
- Original gets `fixedBy`, `fixedAt` pointers
- Auto-QA runs on new image immediately after generation
- Both original + fixed version visible in Content Review
- Labels: `Original (Warning)` / `Fixed v1` / `Fixed v2`

### Simplification (Xian correction)
- Xian said: "Let's not put audit trail for who triggered — takes too much process"
- Removed `triggeredBy` from API input + `fixTriggeredBy` from asset record
- Only `fixTriggeredAt` timestamp retained (no user identity)

### Show/Hide Rules for Fix button
- Show: `warning`, `fail` statuses (and not already fixed — no `fixedBy`)
- Hide: pass/approved, pending/scanning, generation failed

### Generation flow
- Primary: nano-banana-pro (Gemini) with original image as `--input-image` reference
- Fallback: OpenAI DALL-E 3 if nano-banana-pro fails
- Fix prompt = product brand context (brand-guidelines.md) + QA issues converted to constraints + platform specs

### Files Created
- `/home/aria/projects/symph-agency/src/app/api/marketing/fix-asset/route.ts` (new)
- `SocialQA.tsx` updated: `handleFix()`, `fixingAssetId` state, Fix button in detail panel, version labels on thumbnails, fix source/fixed-by info in panel

### Asset tracking limitations
- Current system doesn't track *who* requested/generated assets — only *when* (detectedAt) and *where* (watcher auto-detection)
- `sourceChannel: watcher`, `requestId: null` for all existing assets
- This is a planned gap — Workflow Integrity spec has `requestId` + `sourceChannel` fields but not yet populated at generation time
- Clocklane assets: watcher detected, no attribution data — couldn't confirm growth team identity from system

---

## FaceTrace Facebook Posts — Generated Feb 24 ~08:08 UTC

### 4 Posts Generated
All theme: "dangers of seeing your own face without consent" — privacy-first, empowerment tone
All format: 1024×1024 (1:1, Facebook Feed ✅)

| Post | File | Concept | QA |
|------|------|---------|-----|
| 1 | `2026-02-24-08-08-18-facetrace-fb-post1-discovered.png` | Discovery Hook — alarmed woman at phone | ✅ Pass |
| 2 | `2026-02-24-08-08-18-facetrace-fb-post2-unauthorized.png` | Unauthorized Use — split screen before/after | ✅ Pass |
| 3 | `2026-02-24-08-08-18-facetrace-fb-post3-empowerment.png` | Empowerment — confident woman, "Your face. Your rules." | ✅ Pass |
| 4 | `2026-02-24-08-08-18-facetrace-fb-post4-stats.png` | Awareness Stat — "1 in 3 people" infographic | ✅ Pass |

### Brand Applied
- Deep Ocean `#0F4C75` navy background
- Coral Sunset `#FF6B6B` accents + headlines
- Slate Blue `#3282B8` supporting elements
- Inter font, privacy-first empowering tone (not surveillance/fear)
- Brand guidelines source: `/home/aria/products/image-recognition-platform/design/brand-guidelines.md`

### Technical Note
- nano-banana-pro requires `GEMINI_API_KEY` env var (NOT OpenAI key) — confirmed in env
- Must use `uv run` prefix for the Python script
- Generated using `GEMINI_API_KEY="$GEMINI_KEY" uv run "$SCRIPT" ...`

### Saved To
`/home/aria/products/image-recognition-platform/marketing/`

### QA Detail (all platform layer: Facebook Feed 1:1 ✅)
- QA run via Symphony `POST /api/marketing/qa-asset` with `targetPlatform: "facebook"`, `placement: "feed"`
- All 4: dims 1024×1024, status=pass, platform=pass
- Ingest issue: 2 posts (post3/post4) were auto-detected by watcher before manual ingest — skipped as duplicates; post1/post2 freshly ingested

---

## Adulting.Ph Build Session — rff (bunsooo) #2026-02-24 afternoon

### Session summary
Long design/build session with rff on adulting-ph. Lots of UI polish on the Guides page and new features on Dashboard + Profile.

### Guides page (/programs) — changes shipped
- **Hero redesign**: flat bottom, 40px padding-bottom, `md:pt-[80px]` to clear 64px fixed nav
- **Category buttons**: complete color system rework
  - Exact RGB bg values per category (provided by rff): government=rgb(166,185,213), id=rgb(255,213,213), benefits=rgb(255,213,165), tax=rgb(255,222,166), healthcare=rgb(135,233,225), travel=rgb(210,183,234), finance=rgb(194,255,235), housing=rgb(255,241,232)
  - `All` = solid dark blue (#3B4FC8), white text
  - Opacity (0.7→1.0) on PARENT WRAPPER div, NOT on bg/text/icon colors
  - Active: 1.5px solid white outline
  - `categoryConfig` uses pure hex colors (not rgba tricks)
- **Category row**: `min-width: 100px; min-height: 100px` on boxes
- **Custom scrollbar**: 4px height, #133C84, 50px radius, 12px gap (pb-[16px] on container)
- **Desktop alignment**: category row moved outside `max-w-2xl`; left offset = `max(20px, calc((100vw - 42rem) / 2))` — aligns first box with Guides title on all viewports
- **Drag to scroll**: mouse drag implemented via useRef + mousedown/mousemove/mouseup
  - `didDrag` flag (4px threshold) prevents accidental category selection after drag
  - Cursor stays default (not grab) — rff + I agreed grab cursor on empty space looks broken

### Dashboard (/dashboard) — changes shipped
- **Recommendations**: max 4, sorted by CATEGORY_PRIORITY (id→gov→health→benefits→finance→tax→housing→travel), excludes started AND completed guides
- **Avatar circle**: top-right of header (44px), links to /profile, shows photo or initials with consistent color per user

### Profile (/profile) — full redesign
- Hero: blue gradient, avatar 110px absolutely positioned bottom-left, sticks out 40px below hero
- Name/email: right of avatar, smartName logic (≤14 chars full name, else first name only)
- Photo upload: Firebase Storage (gs://symph-adulting-ph-uploads, us-central1, public)
  - Server-side via `/api/profile/photo` using firebase-admin + applicationDefault()
  - Updates auth.currentUser.photoURL on success
- Red logout button (red text + red border)
- **Bug fixed**: `auth.currentUser` was null at mount → switched to `onAuthStateChanged`

### Key learnings (also in error-log.md)
1. **Opacity on wrapper, not colors** — when UI elements should fade at 70%, apply opacity to the PARENT CONTAINER. Never reduce bg/text/icon colors via rgba(). [rff correction]
2. **Light bg colors = proper lighter RGB, not rgba tricks** — use exact design-specified RGB values for light backgrounds; rgba() shifts unpredictably on different bg contexts. [rff correction]
3. **Tailwind JIT purges dynamic class strings** — class names built with template literals (e.g., `${categoryCardColors[id]?.active}`) don't survive Tailwind's build-time purge. Use inline styles for runtime-computed values.
4. **auth.currentUser can be null at mount** — always use `onAuthStateChanged` for auth data in profile/settings pages. `auth.currentUser` only works if auth was already initialized.

### Infrastructure
- Firebase Storage API enabled on symph-adulting-ph project
- GCS bucket created: gs://symph-adulting-ph-uploads (us-central1)
- CORS configured for adulting-ph.dev.apps.symph.co + localhost:3024
- Storage rules: 5MB max, image/* only, server-side validation

### People
- **rff (bunsooo)**: Very design-precise, gives exact RGB codes. Frustrates when I use opacity hacks instead of proper colors. Prefers discussing options before building (asked about avatar, discussed name truncation). Good eye for spacing, alignment, responsiveness across viewports.


---

## CPU Spike Investigation & app-manager v4.0 — Jim (jim.paano) ~13:00 UTC

### Issue
Server CPU was spiking due to PM2 crash loops. Worst offenders:
- `symphos-dev` — 153,558 restarts
- `cardpop-prod` — 269,913 restarts
- `safenow`, `tiangge`, `wanderkit`, `smashslot`, `overture-ideas`, `remotion-chat` — 5,000–7,500 each

Root cause: **EADDRINUSE** — PM2 had hardcoded ports, but `app-manager` or subagent skills had already started raw `nohup` processes on those same ports. PM2 kept crashing (port taken) and restarting forever.

### Three Conflicting Systems Discovered
1. **PM2 (legacy)**: hardcoded ports in entries, no awareness of `caddy-ports.json`
2. **Symphony `/api/apps/[slug]/start`**: reads `.allocated` key in `caddy-ports.json`, uses `spawn()` — NOT PM2
3. **Subagents/skills** (`web-app-builder`, `landing-page-builder`): raw `npm run start -- -p ${PORT} &` — no tracking

Also: `caddy-ports.json` had TWO keys — `.allocated` (Symphony) and `.assignments` (app-manager) — they never saw each other's data.

### Fix Applied

**Step 1 — Stop the bleeding**: `pm2 delete` all 23 crash-looping dev app entries. CPU immediately normalized.

**Step 2 — app-manager v4.0** (`/home/aria/bin/app-manager`):
- Replaced `nohup npm start &` → `pm2 start npm --name "$slug" --stop-exit-codes 98`
- `--stop-exit-codes 98` = EADDRINUSE exit code — PM2 stops restarting on port conflict (CRITICAL FIX)
- `get_assigned_port()` now checks `.allocated` first, falls back to `.assignments`
- `assign_port()` now writes to `.allocated` only (single source of truth)
- `find_next_port()` checks BOTH keys to avoid conflicts

**Step 3 — Symphony start route** (`/api/apps/[slug]/start/route.ts`):
- New code path: calls `execSync('/home/aria/bin/app-manager start ${slug}')` — returns immediately
- Legacy 200-line spawn/PM2 logic preserved but unreachable (wrapped in dead code block)

**Step 4 — Skill docs updated**:
- `web-app-builder/SKILL.md`: `npm run start -- -p ${PORT} &` → `/home/aria/bin/app-manager start ${SLUG}`
- `landing-page-builder/SKILL.md`: same change

**Step 5 — Symphony deployed**: `deploy.sh` succeeded, revision `809d3b9`, HTTP 200 ✅

### Architecture After Fix
```
Symphony UI button     Subagent skill       Direct CLI
       ↓                    ↓                   ↓
       └──────────→ app-manager ←──────────────┘
                         ↓
              PM2 with --stop-exit-codes 98
```

### Key Learnings
- Always add `--stop-exit-codes 98` to PM2 entries for dev apps — prevents infinite EADDRINUSE loops
- `caddy-ports.json` should have ONE key (`.allocated`) — two keys created coordination hell
- app-manager should be the single entry point; never start apps via raw shell in skills

### Backup
- Old app-manager saved at `/home/aria/bin/app-manager.bak`


---

## WanderKit Batch D — Dev Session (2:12 PM UTC)

### Bugs logged to backlog (#136, #137, #138) — intern-reported
- **#136** Packing Tab: Planned Wears field can't be cleared to type freely; needs +/− buttons
- **#137** Ledger Tab: "Add [Category] expense" button doesn't pre-select that category in modal (defaults to Attraction)
- **#138** Overview Tab: Weather/SunArc broken for trips with unsupported destination cities

### Batch D work completed (DEV only, PROD pending Tom sign-off)

**#138 — City coverage + graceful fallback**
- Root cause confirmed: `allCities` filters to only CITY_COORDS-matching cities; Siargao wasn't in list → `allCities=[]` → both widgets fail silently
- Added ~80+ vacation destinations to `WORLD_CLOCK_CITIES` (Siargao, Boracay, El Nido, Palawan, Coron, Bohol, Davao, Iloilo, Dumaguete, Tagaytay + Indonesia, Thailand, Vietnam, Japan, Korea, India, Malaysia, Maldives, Europe vacation spots, Caribbean, Pacific Islands, US vacation spots, etc.)
- Added graceful fallback when `allCities.length === 0`: shows "Weather unavailable for [city]" and "Sunrise/sunset unavailable for [city]" instead of infinite spinner / `—` times
- Files: `src/lib/constants/cities.ts`, `src/components/trip/OverviewTab.tsx`

**#135 — Donut chart + widget design exploration (NOT shipped to PROD)**
- Converted DonutChart from Recharts back to pure SVG — eliminates Recharts internal margin centering issues
- `strokeLinecap="butt"` for clean flush segment joins (no overlap, no gap)
- Size now accepts prop (default 120), center text scales with size
- Multiple layout redesigns explored with Tom (large donut hero, 2-col categories, side-by-side blocks with stats/categories in separate cards) — none were approved
- Tom reverted to original layout: small donut top-left + stats right + 2-col category grid below
- **What stayed**: All 8 categories always shown (zeros included, uniform styling), `items-start` on parent grid (eliminates height-stretching whitespace), pure SVG donut at 120px
- **#135 still open** — design direction not finalized

### Deploy script fix — `deploy-dev.sh`
- Root cause: `pm2 delete` removes PM2 tracking but leaves Node.js process alive on port 3006
- New process on same port → EADDRINUSE → errored state → browser gets HTML 404 for JS/CSS chunks
- Fixed: added `fuser -k $PORT/tcp` before start, and changed from `pm2 restart` to stop+delete+start pattern

### Batch plan updated
- Batch D: #135 + #138 (Overview Tab) — #138 ready for PROD, #135 design TBD
- Batch E: #136 (Packing) + #137 (Ledger)
- Batch F: #103a (Mobile Audit) + #103b (Full PWA) — largest scope, sequential

### PROD status
- Latest PROD: `wanderkit-prod-00038-xp6` (Batch C)
- DEV has Batch D changes built and running — awaiting Tom sign-off before PROD deploy

---

## Worktree CPU Cleanup — Jim Paano (~2:41 PM UTC)

### Context
Jim (jim.paano) approved cleanup of orphan worktree processes consuming CPU on the server.

### Identified Resource
- **Task ID**: `copilot-task-1771906076636-jim.paan`
- **Dev server port**: 3203
- **Dev URL**: `https://ai-teacher-assistant-wt076636.dev.apps.symph.co`
- **Product**: `ai-teacher-assistant`
- **PIDs flagged earlier**: 2990838, 3284540 (zombie `next dev` processes)

### Action
Jim explicitly approved cleanup. Cleanup was initiated at session compaction time.

### Cleanup Steps (to complete in next session if not done)
1. Kill the worktree dev server: `lsof -ti :3203 | xargs kill -TERM`
2. Remove Caddy entry for the worktree subdomain
3. Clean up worktree directory under `/home/aria/products/ai-teacher-assistant/worktrees/`
4. Remove session entry from copilot-worktree sessions store
5. Kill zombie PIDs 2990838 and 3284540 if still alive

---

## WanderKit — Donut Chart UI Feedback (Tom, ~2:28 PM UTC)

### Context
Tom sent a screenshot of the Overview Tab expenses widget from DEV. Three issues identified:

1. **Donut needs more visual emphasis** — should be the focal element of the card
2. **Category cards are too wide** — need to be narrower
3. **Bottom whitespace** — excess space below the last category card row

### Screenshot received
- File: `/home/aria/.openclaw/media/inbound/14b6d951-8e9a-4ea8-896c-92fd64960ca7.png`
- Source: Tom (`tomgarcia19`, Discord ID `1247849456058236980`) in #wk-development (1472570510977007688)

### Status at compaction
- 🔄 Awaiting response/fix implementation
- Need to: review screenshot → propose/implement fixes → send revised design or annotated suggestions

### WanderKit Batch State (current)
| Batch | Tickets | Status |
|-------|---------|--------|
| A | #130 + #134 | ✅ PROD `00034-v2x` + `00035-bsc` |
| B | #129 + #131 | ✅ PROD `00036-74p` + `00037-mln` |
| C | #132 + #133 | ✅ PROD `00038-xp6` (confirmed by Tom) |
| D | #135 + #138 | 🔄 DEV — awaiting Tom sign-off |
| E | #136 + #137 | 📅 Queued |
| F | #103a + #103b | 📅 After E (sequential, largest scope) |

### Key WanderKit Rules (pinned)
- NEVER make changes without Tom's explicit "go"
- NEVER deploy PROD without Tom's DEV sign-off
- Workflow: DEV → Tom verify → PROD deploy
- All WanderKit dev talk: #wk-development (1472570510977007688)
- Backlog: `/home/aria/products/wanderkit/design/DEVELOPMENT-BACKLOG.md`
- Deploy PROD: `gcloud builds submit --config=cloudbuild.yaml --substitutions=SHORT_SHA=$(date +%Y%m%d%H%M) --project=symph-wanderkit`
- Deploy DEV: `bash deploy-dev.sh` in `/home/aria/projects/wanderkit`

---

## Alignly — Prod Deploy Attempt (Med / med_aduh, ~2:40 PM UTC)

### Session Summary
Full landing page + auth + legal polish for Alignly, then attempted Cloud Run prod deploy.

### All Changes Committed (master branch, HEAD: `1bd8d93`)
- `src/app/page.tsx` — 1:1 port of prod landing (no CSS modules, uses `<style dangerouslySetInnerHTML>`)
- `src/app/terms/page.tsx` + `src/app/privacy/page.tsx` — legal pages
- `src/middleware.ts` — edge middleware, protects routes, whitelists public pages
- `src/app/auth-context.tsx` — cookie management (`alignly_org`), auto-clear stale localStorage
- Fixed "Welcome back, New!" greeting bug — uses `employee.firstName` from Firestore
- Updated all brand SVGs to 3-circles Alignly mark (#059669)
- `next.config.ts` — `output: 'standalone'` added
- `deploy-prod.sh` — reads `.env.prod`, handles `NEXT_PUBLIC_*` as `--set-build-env-vars`
- `.env.prod` — at repo root, NOT committed to git

### Firebase Authorized Domains
`alignlyhq.com` added to `symph-alignly` project authorized domains.

### Cloud Run Deploy — BLOCKED
- `gcloud run deploy alignly --source . --project theagency-ai --region us-central1`
- Sources upload successfully but Cloud Build container build step fails silently
- No failing build appears in `gcloud builds list --project=theagency-ai`
- Root cause unknown — build may be going to wrong project or build logs inaccessible

### Key Config
- App path: `/home/aria/products/alignly/apps/main`
- Port: 3060 → `alignly.dev.apps.symph.co`
- Firebase project: `symph-alignly`
- Cloud Run service: `alignly`, project: `theagency-ai`, region: `us-central1`
- Remote: `github.com:symphco/alignly-app.git`, branch: `master`
- Middleware `TOP_LEVEL_ROUTES`: `''`, `login`, `signup`, `invite`, `terms`, `privacy`, `api`, `_next`

### Next Steps (for next session)
1. Try `gcloud builds list --project=symph-alignly` — build may be routing to wrong project
2. Run deploy with `--verbosity=debug` to capture actual build ID at submission
3. Try local Docker build → push to GCR → deploy with `--image` flag
4. Alternative: `cloudbuild.yaml` with explicit build steps + `--build-arg` for NEXT_PUBLIC vars

---

## Worktree Copilot — Bug Fixes Session (Jim Paano, ~3:15–4:00 PM UTC)

### Changes Deployed to symph-agency

#### 1. Discard Dialog — Proper Modal (commit ~13:50 UTC)
- **Before**: Inline "Sure? Yes | Cancel" buttons in status bar
- **After**: Proper `Dialog` component with title "Discard changes?", description, Cancel + "Discard changes" buttons
- **File**: `src/components/worktree-copilot/WorktreeStatusBar.tsx`
- Added `isDiscarding` state → spinner on button, "Discarding…" text, both buttons disabled during API call
- `onOpenChange` blocked while discarding (can't dismiss mid-call)
- `Loader2` from lucide-react used for spinner icon

#### 2. 500 Error Loop — Stale Caddy Entries (commit ~14:50 UTC)
- **Root cause**: Worktree sessions cleaned from in-memory store on PM2 restart, but Caddy entries persisted → dead URLs returning 500 in preview iframe → Next.js router retry loop (deep `a5/a6` callstack)
- **Fix 1** (instrumentation.ts): Added `sweepOrphanedWorktreeCaddyEntries()` on server startup
  - **CRITICAL**: Must check if port is listening before removing — early version removed ALL wt-* entries including live ones
  - Only removes entries where `ss -tlnH "sport = :${port}"` returns empty (nothing listening)
  - Keeps entries for live dev servers
- **Fix 2** (sessions.ts): Added `restoreCaddyEntryIfNeeded()` called during session restore from disk
  - If a session's dev server survived restart (nohup), re-adds Caddy entry if missing
  - Uses `caddy-manager.sh add <subdomain> <port> worktree`

#### 3. PM2 Migration for Worktree Dev Servers (commit ~15:17 UTC)
- **Before**: `startDevServerInWorktree` used `nohup npx next dev --webpack -p ${port} > log 2>&1 &` — raw orphaned process
- **After**: Uses `app-manager start-ephemeral <id> <path>` — PM2-supervised
- **app-manager** updated:
  - New `cmd_start_ephemeral()` — allocates/reuses ephemeral port, derives PM2 name (`wt-<short-ts>-<user>`), starts via PM2, stores pm2Name in caddy-ports.json ephemeral entry
  - Kills stale process on port before binding (`get_pid_for_port` → TERM → kill -9)
  - `free-ephemeral` now stops PM2 process THEN kills by PID (cleanup both)
  - Dispatch: `start-ephemeral) cmd_start_ephemeral "$2" "$3"` added
- **mode.ts** `startDevServerInWorktree` now accepts `worktreeTaskId` param, calls `app-manager start-ephemeral`
- **session/route.ts**: passes `worktreeTaskId` to `startDevServerInWorktree`

#### 4. EADDRINUSE Fix (manual fix ~15:40 UTC)
- Old nohup process (PID 3223614) was holding port 3201 after PM2 migration
- PM2 process `wt-229749-jim.paan` crashed 9 times with `EADDRINUSE`
- Fix: killed PID 3223614, `pm2 restart wt-229749-jim.paan` → `✓ Ready in 1293ms`
- `start-ephemeral` now proactively kills stale port holders before binding (permanent fix)

### Key Files Changed
- `src/components/worktree-copilot/WorktreeStatusBar.tsx`
- `src/instrumentation.ts` — startup orphan sweep
- `src/lib/copilot-worktree/sessions.ts` — Caddy restore on session load
- `src/lib/copilot/mode.ts` — PM2-based dev server start
- `src/app/api/copilot-worktree/session/route.ts` — passes worktreeTaskId
- `/home/aria/bin/app-manager` — start-ephemeral command + free-ephemeral PM2 cleanup

### Architecture (Post-Fix)
- Worktree dev servers = PM2 processes named `wt-<short-ts>-<user>` (visible in `pm2 list`)
- Independent from Symphony's PM2 lifecycle — Symphony restart doesn't kill worktree servers
- Session persistence in `.worktree-sessions.json` — Caddy entries restored on session load
- Startup sweep: dead wt-* Caddy entries removed, live ones kept
- Port cleanup: stale processes killed before new bind

### Lesson Learned
- When migrating from nohup → PM2, old nohup processes survive and block the new PM2 ones
- `start-ephemeral` kills port holders first, making migration safe going forward
- My initial "sweep all wt-* entries on startup" was too aggressive — broke Jim's live session every deploy


---

## Session Compaction — 16:28 UTC

Pre-compaction flush. No new product/dev work since 16:00 UTC flush. All session content already persisted above.

**Status snapshot (16:28 UTC):**
- Worktree PM2 migration: ✅ Complete and stable
- Jim's session (`wt-229749-jim.paan`): ✅ Running on port 3201
- Symphony app: ✅ Live at symphony.symph.co
- All changes deployed via `./deploy.sh`

## TIL (Today I Learned)
- **Subagent timeout ≠ failure** — Study Buddy build plan subagent timed out but ALL work was already done (57KB plan, HTML, workflow.json at plan_ready). Timeout hit during cleanup only. Always check if artifacts exist before respawning.
- **`kill` vs `kill -9`** — Orphan Claude PTY sessions (johndorf-core, 17h+ each) ignored SIGTERM, required SIGKILL to free ~940MB RAM. Default `kill` is not enough for rogue PTY processes.
- **Caddy port conflicts compound** — Port 3001 had 5 slugs assigned by heartbeat time; infra validation caught it. Fix: always check caddy-ports.json before allocating, and run validate-infra.sh regularly.
- **Phantom allocations** — Worktree ports (ai-teacher-assistant-wt741801) lingered in caddy-ports.json after cleanup; worktree watchdog handles sessions but not stale port entries. Added manual cleanup today.
- **OpenClaw config: thinkingDefault vs reasoning visibility** — Raven wanted to disable "reasoning" showing up in chats. `thinkingDefault` controls whether reasoning happens; `/reasoning` controls visibility. The right field is `verboseDefault` for suppressing reasoning blocks from replies.
- **Browser tool down** — Chrome CDP failing to start on port 18800 for openclaw profile; flagged to Raven. Affects design research, QA screenshots, competitor analysis.
- **Wingman full build complete** — 46 source files, Firebase symph-wingman, port 3011, wingman.dev.apps.symph.co. Dark-mode AI dating platform. First product with full design-system-prompt.md → build pipeline.
- **Study Buddy + Wingman full design generation** — Both products at 100% design coverage. Study Buddy: 19 P0 screens, Focus Indigo #4F46E5. Wingman: 22 screens, Midnight Indigo + Rose Coral, dark-mode-first.
- **Raven's OpenClaw slides built** — 8-slide deck at raven-slides.dev.apps.symph.co. Fraunces + Caveat + Symph Blue #29A9E0. Includes security hardening angle per his request.

## ReadStreak E2E Health Check — 7:00 PM UTC (3AM PHT)

**All checks passed ✅**

| Check | Result |
|-------|--------|
| Homepage (/) | ✅ 200 |
| Dashboard (/dashboard) | ✅ 200 |
| Library (/library) | ✅ 200 |
| Friends (/friends) | ✅ 200 |
| API /api/healthcheck | ✅ 200 — `{"status":"ok","app":"ReadStreak","version":"1.0.0"}` |
| API /api/stats | ✅ 401 (auth required — expected) |
| API /api/books/search | ✅ 400 (params required — expected) |

**Process:** PM2 `readstreak-dev` (PID 3455196) — running from worktree `redesign-cozy-v1` on port 3008
**Domain:** readstreak.dev.apps.symph.co

