Change Log Feed
212 entries · 11 changelog files scanned
The Apr 24 continuation documented how to debug and harden that system when a real production path still failed despite the roadmap being in place.
New `public.notification_fanout_warnings` audit table: `(id, source_table, source_id, source_kind, expected_recipient_count, actual_notification_count, organization_id, reason, created_at)` with indexes on `(source_table, source_id)`, `(source_kind, created_at DESC)`, and partial `(organization_id, created_at DESC)`. Platform-admin-only SELECT via existing `is_platform_admin(auth.uid())` helper — INSERTs only happen through the SECURITY DEFINER audit helper which bypasses RLS.
`public.owner_admin_inbox_items` — unions `org_connect_request` + `registration_submission`. Role-scoped via `organization_users.role IN ('owner','admin')` matched against `auth.uid()`.
`session_rsvp` — `AFTER UPDATE OF status` trigger on `session_invites`, fires on `pending → accepted/declined`; notifies session coach + org owner/admin via `training_session_instances.coach_user_id` and `organization_id`.
**Indirect Patrick impact.** wc-admin migration 051 (`051_lead_magnet_drip_sends.sql`) includes a targeted `UPDATE client_welcome_drip_sends SET status='pending_cron' WHERE client_id='9d60c220-a73e-4bc6-b238-ee1df6420fc5' AND status='failed' AND step IN (5,6)`. Steps 5 (scheduled ~2026-05-15) and 6 (~2026-05-31) were originally queued by the orchestrator before the 7-day Resend cutoff was hardened; Resend silently rejected them. They are now `pending_cron` — the wc-admin cron dispatcher will fire them at their original `scheduled_for` dates.
`bun run test` (with `.env.local` moved aside to simulate CI) → **38 files / 458 tests passed**.
`bun run build` clean after every commit.
Required migration-history repair: stale remote history entry `20260703000001` was marked reverted before applying `20260702200004`.
`bun run build` clean (Vite 8 + PWA injectManifest + OG prerender all green).
`npm run build` clean (Vite 8 + PWA injectManifest + OG prerender all green).
so AOS prompt status doesn't get duplicated. Today that surfaces 23 cards from `AOS/CC_Prompts/README.md`.
Chrome-MCP automated sweep (prior Chrome-MCP session; pre-fix state)
`ADD COLUMN kit_source TEXT` on `public.apa_drip_emails`
Full Claude Code prompt written and committed for three auto-generated branded share graphics: Final Score Card, Milestone Moments, and Post-Game Recap Card. File: `AOS/CC_Prompts/Post_Game_Share_Cards_CC_Prompt.md`.
`~/Desktop/Russell Brunson Newsletters/Funnel University Issue 3_ Digital Marketer's Continuity Funnel _ REO Rockstar's VSL Funnel _ Marketing Secrets.html`
**Annual prepay $470/yr** added alongside the existing $47/mo (Skool dual-interval supported natively). Both billing intervals live on the same paywall.
**87 app pages** with `PageHelpBanner` now render real content instead of draft stubs.
Bigger tap targets, landscape orientation support, compact baseball stats layout. CC prompt spec: `AOS/CC_Prompts/Scoreboard_UI_Overhaul_Combined.md`.
new pure (Deno/Node-agnostic) module. Exports `CRITICAL_NOTIFICATION_TYPES` (billing / account / auth / security + legacy `invoice_*` events), `isCriticalNotificationType` (prefix match on `billing_*` / `account_*` / `critical_security_*`), `shouldDeliverForOrgServer`, `shouldDeliverAcrossOrgs`, and `applyPerOrgChannelGate` (preserves in-app, suppresses outbound channels on muted / deny).
`id / user_id / org_id / relationship_type (active_parent | past_parent | fan | grandparent) / pinned / muted / notification_prefs JSONB / auto_created / joined_at / left_at / created_at / updated_at`. Unique `(user_id, org_id)`. RLS: user read/update/delete own; org owner+admin read rows in their org.
**`ParentOrgOverviewModal`** — read-only sheet surfacing coaches of athlete's team(s) + next 3 tournaments + next 3 multi-team events. Not a full org dashboard.
Migration `supabase/migrations/20260628000002_training_articles_parent_org_phase_3_3.sql`.
Full competitive analysis of FlagPoint (flag football niche platform) added to `shared/Competitor_Intel/Competitor_Intel.md`. Threat level: **VERY LOW**. Key takeaways: validates AOS scoreboard strategy; FlagPoint's frictionless free tools and player profile features are worth monitoring.
`ReportProblemModal` usage removed from Layout (modal file untouched)
Full `.com` → `.co` email scrub. Cloudflare worker deployment. OG preview testing.
`e7b09e6` — `[2026-05-06] Claude Code: fix black screen — close sidebar on route change` — `AppShell` (`src/components/site/app-shell.tsx`) is a Client Component holding `sidebarOpen` in `useState`, but had no `usePathname()` watcher. After a Server Action `redirect()` to a new route, `sidebarOpen` stayed `true` and the `bg-black/60 lg:hidden` overlay (line 36) sat over the new page on mobile — Patrick saw a black screen until tapping anywhere. Fix: `useEffect(() => { setSidebarOpen(false) }, [pathname])` with `usePathname()` from `next/navigation`. Closes the drawer on every navigation. 1 file, +7 / -1. `pnpm build` PASS. Direct push to `origin/main`, no PR. **`local HEAD == origin/main` verified** at `e7b09e6` post-push. Daily-log entry: see `Patrick_Daily_Log.md` 2026-05-06 § "Pre-call fixes for tomorrow's Patrick Zoom" — Fix 1. CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, clean imports.
no source-code fix needed)
`d709fb6` — `[2026-05-07] Claude Code — estimate PDF cleanup pass: bake waste into Total, kill LINE prefix, single bottom Total on customer, page-break rules, signature block redesign` — Visual cleanup of the customer-facing estimate PDF based on Chase's review of the four-fix output (`882f637` → `485e1e1`). Five fixes in `src/modules/estimates/pdf.ts` + one footer-overlap bonus + a dev tool to render before/after samples. (1) Customer line-items table dropped from 8 columns (DESC / QTY / UNIT / UNIT PRICE / WASTE % / LINE SUBTOTAL / LINE WASTE / LINE TOTAL) to 6 (DESC / QTY / UNIT / UNIT PRICE / WASTE % / TOTAL) — TOTAL is now the waste-inflated line total. Internal Review keeps the Subtotal / Waste / Total breakdown next to per-line internal cost & margin (operator-relevant). (2) Stripped "Line" prefix from headers (`Line subtotal` → `Subtotal`, `Line waste` → `Waste`, `Line total` → `Total`). (3) Customer bottom totals card collapsed to a single `Total $Y` row — Subtotal / Waste lines were noise once waste is baked in per-line. Internal Review keeps the full breakdown (Subtotal / Waste / Total / Internal cost / Margin). (4) Notes / scope blocks gained `page-break-inside: avoid` so the customer-notes paragraph (added in `2e7d9fe`) no longer orphan-splits across pages. (5) Signature block redesigned: two-column with bold uppercase brand-color label, customer name (or blank for rep side), thick signature underline + SIGNATURE caption, then a Print Name / Date row beneath. `page-break-inside: avoid` on the block so the rep side never orphans onto a new page alone. **Bonus fix:** the pre-existing `position: fixed; bottom: 0.3in` footer was overlapping the bottom row of the line-items table on page 1 (visible in the baseline `~/Desktop/estimate-before.pdf`). Chromium's PDF print pipeline doesn't reliably keep position-fixed elements clear of flowed content. Converted to a normal end-of-document block — appears on the last page only. Contact info still at the top of every page in the letterhead. **Dev tool:** new `scripts/render-estimate-sample.ts` renders a fixture estimate (10 line items, Eric & Hannah Chen / 412 Maplewood Lane / FPH-2026-0118) against the static `PATRICK_BRAND` baseline so future PDF iterations produce before/after samples without a Supabase round-trip. Usage: `pnpm exec tsx scripts/render-estimate-sample.ts <out-path> [customer|internal]`. **Decisions:** P-DEC-39 — chose Option A for customer bottom totals (single Total) over Option B (Subtotal + Total) since subtotal-without-waste is meaningless once waste is baked in. P-DEC-40 — footer dropped from every-page to last-page-only (Chromium's PDF print pipeline doesn't reliably keep `position: fixed` clear of flowed content; letterhead carries contact info on every page). 2 files, +253 / -27. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `d709fb6` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Estimate PDF cleanup pass". CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, no client-specific literals in shared modules (all driven by `BrandResolver`), no provider-SDK leak. BrandResolver propagation verified — sample script constructs a `ResolvedBrand` from `PATRICK_BRAND` and `brandCopy()` resolves through the same code path the route handler uses.
`cddf686` — `[2026-05-05] Claude Code — Lane 3 Automation Engine: sequence templates, runs, outbox fanout, job-page picker (Patrick Loom 2026-05-05 02:38, 02:46, 04:57, 17:30)` — Patrick: post-milestone "Begin automations? Y/N" prompt + sequence picker (Loom 02:38, 02:46), default damage-report follow-up cadence editable BEFORE start (Loom 04:57), and a Settings → Automations editor where the owner can curate every template (Loom 17:30). Built on top of the existing outbox cron — zero new cron, zero new provider integration. **Migration `0047_sequence_automations.sql` (NEW, 367 lines)** adds four tables and seeds six default templates per existing org. Tables: (1) `sequence_templates(id, org_id, template_key, name, description, trigger_milestone, is_active, is_system, created_by, created_at, updated_at, UNIQUE(org_id, template_key))` — `trigger_milestone` is a CHECK-constrained closed set: `inspection_scheduled` / `build_day_scheduled` / `adjuster_appointment` / `proposal_sent` / `proposal_followup_due` / `damage_report_sent` / `custom`. Owner/manager-only writes via `rep_is_owner_or_manager()`. Soft-delete via `is_active=false`. DELETE policy explicitly excludes `is_system=true` rows. (2) `sequence_template_steps(id, org_id, template_id, step_order, delay_seconds, channel, subject, body, is_active, …, UNIQUE(template_id, step_order))` — `delay_seconds` is INT (signed), accepts negative offsets per the v3.3 patch. `channel` CHECK ∈ email/sms/in_app/task. (3) `sequence_runs(id, org_id, job_id, template_id, template_snapshot JSONB, trigger_milestone, scheduled_at, started_by, status, cancelled_at, cancelled_by, cancel_reason, …)` — status CHECK ∈ active/completed/cancelled. `template_snapshot` is the canonical at-start-time copy of the template + steps so subsequent template edits do not retroactively change in-flight runs. No DELETE policy (durable history). (4) `sequence_run_steps(id, org_id, run_id, step_order, delay_seconds, channel, subject, body, fire_at, outbox_event_id, status, sent_at, cancelled_at, failure_reason, …, UNIQUE(run_id, step_order))` — status CHECK ∈ scheduled/sent/cancelled/failed. `outbox_event_id` REFERENCES `integration_outbox_events(id) ON DELETE SET NULL`. **Seed data per existing org**: six is_system templates with editable step rows: Damage Report Follow-up (`+2d` SMS / `+5d` email / `+12d` SMS / `+19d` email — Patrick verbatim 2d/5d/12d/19d gap → absolute offset chain), Inspection Scheduled (`-1d` SMS / `-1h` SMS / `+1d` email follow-up), Build Day Scheduled (`-3d` email / `-1d` SMS / `-1h` SMS), Adjuster Appointment (`-1d` email / `-1h` SMS), Proposal Sent (`+2d` SMS / `+5d` email / `+9d` email), Proposal Follow-up (`+0` SMS immediate / `+3d` email). All bodies use merge fields: `{{customer_first_name}}`, `{{customer_full_name}}`, `{{rep_first_name}}`, `{{event_date}}`, `{{event_time}}`. Idempotent seed: `ON CONFLICT (org_id, template_key) DO UPDATE SET name = EXCLUDED.name`, step inserts `ON CONFLICT (template_id, step_order) DO NOTHING`. **Sequence runner (`src/modules/automations/sequences.ts`, 320 lines)** exports `startSequenceRun(client, ctx, input)` and `cancelSequenceRun(client, ctx, runId, reason)`. Start: validates input via Zod (`StartSequenceInputSchema`); resolves homeowner contact via jobs.customer_id → customers.primary_contact_id → contacts(email/phone/full_name); resolves sender rep full_name from `reps`; computes merge context (`firstNameOf` strips first whitespace-delimited word); inserts run; for each step, applies merge, computes `fire_at = anchorMs + delay_seconds * 1000`, inserts `integration_outbox_events` with `event_type = 'sequence:{milestone}:step_{order}'`, `provider = PROVIDER_BY_CHANNEL[channel]` (resend/twilio/internal), `payload.source = 'sequence_run'`, `payload.sequence_run_id`, `payload.sequence_step_order`, `payload.to` resolved from contact, `payload.body` post-merge, `payload.html = body.replace(/\n/g, '<br/>')` for email; inserts `sequence_run_steps` with the same snapshot + outbox link; on any failure, marks the run cancelled with reason `fanout_failed` and bails. Cancel: SELECT pending run_steps + outbox ids; UPDATE outbox WHERE status='pending' SET status='dead_letter', last_error='sequence_cancelled:{reason}'; UPDATE run_steps SET status='cancelled', cancelled_at, failure_reason; UPDATE run SET status='cancelled', cancelled_at, cancelled_by, cancel_reason. Idempotency keys: `seq:{run_id}:{step_order}` per step — Twilio + Resend dedupe on this key. **Cron post-hook**: `src/app/api/cron/outbox/route.ts` gains `withSequenceHook(name, inner)` HOC that wraps each handler. Inspects `event.payload.source === 'sequence_run'` and on success updates the matching `sequence_run_steps` row to status='sent', sent_at=now() (or status='failed' on permanent failure). Mounted via `handlers: { twilio: withSequenceHook('twilio', twilioHandler), resend: withSequenceHook('resend', resendHandler), … }`. **API routes** (all org-scoped via RLS, owner/manager edit): `GET/POST /api/automations/templates` (`?milestone=` + `?active=1` filters; POST creates custom templates with auto-derived `template_key` from name); `GET/PATCH/DELETE /api/automations/templates/[id]` (DELETE rejects is_system templates with HTTP 400); `POST /api/automations/templates/[id]/steps`; `PATCH/DELETE /api/automations/templates/[id]/steps/[stepId]`; `GET /api/automations/sequences?job_id=` (returns runs + nested run_steps via Postgres join); `POST /api/automations/sequences` (calls `startSequenceRun`); `POST /api/automations/sequences/[id]/cancel`. All routes Zod-validate input + log warnings on errors. **UI — Settings → Automations tab** (`src/app/(app)/settings/automations/`): new fourth tab in nav order (Organization / Profile / Branding / Lists / **Automations** / Notifications / Integrations / Team). Lists every template in `is_system DESC, name ASC` order. Each row shows name + Default badge + Disabled badge if `!is_active` + trigger label + step count. Click expands an inline step editor: per-step delay sign + value + unit (min/hour/day) + channel + subject (email only) + body (textarea). Save/cancel + delete-step buttons. "Add step" appends with reasonable defaults. "New template" launcher creates with auto-derived template_key + milestone dropdown + description. Owner/manager only — non-edit roles see read-only rows. **UI — Job → Workflow tab `JobAutomationsPanel`** (`src/app/(app)/jobs/[jobId]/automations-panel.tsx`, 488 lines): renders above the workflow checklist on `/jobs/[id]?tab=workflow`. Header: "Automations for {customer_name}" + "Start automation" launcher button. Lists active runs with template name + milestone label + sent/total step counts + per-step chips colored by status (sent=emerald, cancelled=muted+strikethrough, failed=accent, scheduled=neutral). Each chip shows channel icon + delay label. Cancel button per run (confirms before flipping). Auto-prompt banner: reads `?auto_prompt_milestone=` from URL; if present, surfaces "Begin automations for {milestone}? [Pick sequence →] [Skip]" and auto-opens the picker pre-filtered to the matching milestone. **`SequencePickerDialog`**: milestone dropdown + template dropdown (filtered by milestone, `is_active=true` only) + datetime-local anchor + step list with per-step inline editor (delay sign/days, channel, subject, body, remove). "Add step" + "Start sequence" buttons. Anchored on `scheduled_at`; each step's `delay_seconds` derives from sign×days. Snapshot includes name, capturedAt, steps (so the run carries everything for forensic lookup). **Damage report + estimate send hooks** (`src/app/(app)/jobs/[jobId]/critical-step-actions.tsx`): after `runMockActionAction` returns OK for `send_damage_report_quick` or `send_estimate_quick`, sets `?tab=workflow&auto_prompt_milestone=damage_report_sent|proposal_sent` via `router.replace` + `router.refresh`. The `JobAutomationsPanel` then auto-opens the picker. The other three milestones (inspection_scheduled, build_day_scheduled, adjuster_appointment) launch via the manual "Start automation" button — calendar-event-create hook is a future extension. **Files**: `supabase/migrations/0047_sequence_automations.sql` (NEW, 367 lines); `src/modules/automations/sequences.ts` (NEW, 320 lines); `src/app/api/automations/templates/route.ts` (NEW, GET+POST); `src/app/api/automations/templates/[id]/route.ts` (NEW, GET+PATCH+DELETE); `src/app/api/automations/templates/[id]/steps/route.ts` (NEW, POST); `src/app/api/automations/templates/[id]/steps/[stepId]/route.ts` (NEW, PATCH+DELETE); `src/app/api/automations/sequences/route.ts` (NEW, GET+POST); `src/app/api/automations/sequences/[id]/cancel/route.ts` (NEW, POST); `src/app/(app)/settings/automations/types.ts` (NEW, shared types + formatDelay helper); `src/app/(app)/settings/automations/automations-panel.tsx` (NEW, 567 lines, Settings UI); `src/app/(app)/jobs/[jobId]/automations-panel.tsx` (NEW, 488 lines, Job page UI); `src/app/(app)/settings/page.tsx` (Automations tab content + supabase queries when tab=automations); `src/app/(app)/settings/settings-tabs.ts` (added 'automations' to tab key union); `src/app/(app)/jobs/[jobId]/page.tsx` (renders `JobAutomationsPanel` above WorkflowChecklist on workflow tab); `src/app/(app)/jobs/[jobId]/critical-step-actions.tsx` (auto-prompt URL hook on send_damage_report_quick + send_estimate_quick); `src/app/api/cron/outbox/route.ts` (withSequenceHook + sequence_run_steps post-hook). **16 files, +2960 / -19.** `pnpm build` PASS (Next 16.2.4 turbopack — Compiled in 13.8s, TypeScript 16.1s, all 90+ routes including 7 new `/api/automations/...` routes). Migration `0047` applied to FPHI Supabase remote `ujbgksplpyqqektjfqak` via `supabase db push`. Direct push to `origin/main`, no PR. **API costs (per `feedback_advise_on_api_costs_first`)**: Resend $0.0004/email (100/day free), Twilio $0.0079/SMS US — at 100 active sequences/mo with 50% email + 50% SMS mix, total ~$1.68/mo. Watch when scaling past 1000 active sequences/mo.
`aba06f9` — `[2026-05-08] Claude Code — Damage report cover notes drop-cap fix` — Quick fix to the Phase 1 damage-report PDF redesign (`1eb73c8`, 2026-05-07). The "From the desk of …" cover-notes block was rendering its first letter as a 38pt Fraunces ornate drop-cap (`.letter .letter-body p:first-child::first-letter`), and the floated giant capital sat directly on top of the rest of the paragraph — Patrick's preview text "Test to see if it shows up on preview" came out as a giant **T** overlapping "est to see if it shows up on preview" and was unreadable. Drop-caps are an editorial device for long-form prose, not for an inspector's 1–3-sentence intro paragraph. Fix: dropped the seven-line `::first-letter` rule from `src/modules/damage-reports/pdf.ts`. Cover notes now flow as a normal 11pt / 1.7 line-height paragraph. Scope was limited to the cover-notes block only; the Definitions section (`.def-term`/`h3`) was never affected. Verification: `pnpm build` PASS, `pnpm tsx scripts/render-damage-report-sample.ts ~/Desktop/cover-notes-fix-after.pdf` rendered cleanly. 1 file, +0 / -7. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `aba06f93fefe28f1385acd98fc69b7baaf6da1d5` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-08 § "Damage report cover notes drop-cap fix". Brain commit: `dc46219`. CLAUDE.md compliance: pure CSS deletion, no `any`, no `console.log`, no silent catches, no schema change.
`a9fdc73` — `[2026-05-08] Claude Code — Inspections module ported from TVE Ops: 26-checkpoint catalog + per-job records + per-checkpoint photos` — Patrick's 2026-05-08 email reply (logged in `Patrick_Daily_Log.md`) requested Photo Checklists per workflow stage; this Phase 1 ship is the underlying surface, with Phase 2.1 (per-stage routing) deferred to the Phase 2 plan. Migration `0056_inspections.sql` (applied to remote `ujbgksplpyqqektjfqak` via Supabase MCP `apply_migration`) ships three tables: `inspection_checkpoints` (per-org catalog of named photo checkpoints; 26 seeded — TVE Ops's reference 25 plus **Driveway as the 26th per Chase's 2026-05-08 review** for post-build dumpster damage / nail magnet sweep verification), `inspections` (per-job records — `org_id`, `job_id`, `inspector_rep_id`, `started_at`, `completed_at`, `notes_md`), `inspection_photos` (per-checkpoint photos — `org_id`, `inspection_id`, `checkpoint_id` nullable for general photos, `storage_path`, `caption`, `taken_at`, `uploaded_by_rep_id`). RLS is generic `org_id`-scoped throughout — read for reps in-org, writes restricted to inspector + manager roles. Storage bucket reuses `contractor-documents` with path prefix `<org_id>/inspections/<inspection_id>/<uuid>.<ext>`. MIME allowlist + 10MB cap match the existing photo-upload pattern. New modules: `src/modules/inspections/` (schema types, server actions, helpers — `createInspection`, `recordCheckpointPhoto`, `markInspectionComplete`); `src/app/(app)/inspections/` (top-level Inspections list + per-inspection detail / capture panel); `src/app/api/inspections/` (photo upload route handler — signed-URL flow + RLS-aware insert). UI surfaces: top-level Inspections list at `/inspections` filterable by job + inspector + status; per-job inspection panel renders the full 26-checkpoint list as a grid with tap-to-capture; General Photos section below the checkpoint grid; Settings → Inspection panel scaffold (admin view) currently lists the 26 catalog rows read-only — the per-org checkpoint editor is Phase 2.2 in `Patrick_Inspections_Phase2_Plan.md`. Deferred to Phase 2: per-stage workflow checklists (2.1), per-org catalog UI with sample-image upload (2.2), Inspection PDF export (2.3), EXIF / GPS extraction (2.4). AI image-similarity check intentionally **OUT** per Chase 2026-05-08 (sample-image upload in Phase 2.2 replaces it). `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `a9fdc73` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-08 § "Photo Checklists / Inspections module shipped (FPHI `a9fdc73`)". Decision-log entry: P-DEC-51 (Inspections ported with 26 checkpoints; Driveway added as 26th; AI similarity OUT). CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary, schema migration committed alongside code, multi-tenant `org_id` scoping on every table + every RLS policy.
`94fcda4` — `[2026-05-08] Claude Code — Lane 4 Snippets engine: 19 Patrick verbatim snippets + admin-only Settings → Snippets CRUD` — Migration `0056_damage_snippets_patrick_library.sql` (renumbered from 0051 mid-flight when the parallel Color Library / Pricing branch claimed 0051–0055; remote bookkeeping treats them as separate timestamped versions). Drops the closed CHECK on `damage_snippets.category` and makes the column nullable — Patrick's 2026-05-08 library is a flat alphabetical bank, not a taxonomy. Spec §12.2 #9 says CHECK is for locked domains only. Tightens RLS writes from `rep_is_office_or_above()` to `rep_is_owner_or_manager()` (P-DEC-148: field reps churn frequently; admins gatekeep the curated library). Seeds Patrick's 19 verbatim snippets (Nail Pop, Lichen, Wind Damage, State Farm Definition of Hail Damage, Gutter Apron, Flashlight, Imperial Cut Shingle, Velux, Chalk, Spatter, Bruise, Caulk, Detach and Reset Soffit, Custom Bent Fascia, Power Fan – Replace, Power Fan – Detach and Reset, Step Flashing, Starter, Blisters, 10X Jewelers Loupe) with `category=NULL`. New tab `Settings → Snippets` (admin writes only): list sorted by title with type-ahead search filter + show-hidden toggle, soft-delete via `is_active`, inline edit, bulk-import textarea accepting "Title : Description" pairs (idempotent vs existing keys + labels — returns inserted/skipped counts). 5 files, +771 / -0. `pnpm build` PASS. Direct push to `origin/main` (after rebase onto parallel `b286471` brain-log commits + the Color Library / Pricing branch — settings-tabs.ts and settings/page.tsx merge conflicts resolved by stitching both new tabs into the closed-set type + tab order). **`local HEAD == origin/main` verified** at `94fcda494d357577de4577ff86aaf58a6692131c` post-push. Migration applied to remote `ujbgksplpyqqektjfqak` via Supabase MCP `apply_migration`. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-08 § "Lane 4 — Snippets engine + Conclusion templates shipped" (1st commit). Decision-log entries: P-DEC-148 (admin-only writes), P-DEC-150 (alphabetized, not categorized). CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary, schema migration committed alongside code, RLS write tightening is a deliberate scope reduction (office reps lose write — read still fine).
`92dc1d6` — `[2026-05-07] Claude Code — estimate builder: Production Notes field + 0054 schema` — Schema migration `0054_estimate_production_notes.sql` (originally authored as 0053 then renumbered when concurrent pricing-catalog work landed `0053_pricing_catalog_tier_groups.sql` while this branch was in flight; remote already applied the SQL under the `0053` migration name so the rename only affects the working tree). Adds `estimates.production_notes TEXT` (nullable) — operator-only notes that render ONLY on the Production PDF view, distinct from `internal_notes` which is office-only. Adds `brand_settings.production_manager_email TEXT` (nullable) — org-level default recipient for the upcoming Send-to-PM button. Extends `estimates.pdf_view_mode` CHECK to allow `'production'` as a fifth value alongside `itemized_priced` / `itemized_only` / `simple` / `internal_review`. Builder surfaces a third notes textarea below the existing Notes-to-Customer + Internal-Notes pair, sky-tinted, with copy that clarifies the audience: "Logistics for the crew. Where to put the dumpster, parking restrictions, do-not-touch areas, access notes, anything the production manager + crew needs to know." Internal-notes hint copy clarified to "office / back-office only" (vs the previous ambiguous "operator-only"). Saves through the existing `saveEstimateAction` — `SaveEstimateSchema` gained `productionNotes` (z.string().max(4000).nullable.optional). Migration applied to remote `ujbgksplpyqqektjfqak` via Supabase MCP `apply_migration`. 4 files, +78 / -3. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `92dc1d65fed1c15158646bffa877d80e8a6a158c` post-push (after rebase onto the parallel `4c296fb` pricing-catalog commit). Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Production Manager view shipped" (1st bullet). Decision-log entry: P-DEC-2026-05-07-08 (separate `production_notes` column vs reusing `internal_notes` — audience-split rationale). CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary, schema migration committed alongside code.
`9127856` — `[2026-05-05] Claude Code — Add canvasser + site_supervisor roles (Loom #2 04:57, 05:00)` — Patrick (Loom #2): two new rep roles. **Migration `0048_canvasser_and_site_supervisor_roles.sql`** drops the old `reps_role_check` CHECK constraint and recreates it with two extra roles: `canvasser` (knock + lead only — sales subset) and `site_supervisor` (read-only on assigned jobs — production subset). Adds three `SECURITY DEFINER` helpers — `rep_can_log_knocks()` (owner/manager/office/sales/canvasser), `rep_can_create_leads()` (same set), `rep_is_field_only()` (canvasser/site_supervisor/sub) — each with the mandatory `is_active = true` filter per CLAUDE.md §4.2. Existing `rep_is_owner_or_manager()` and `rep_is_office_or_above()` left unchanged: a canvasser is NOT office-or-above, neither is a site_supervisor. **Team UI**: `REP_ROLES` enum in `src/app/(app)/team/actions.ts` extended with the two new role values; `create-rep-form.tsx` Role `<Select>` gains "Canvasser (knocks + leads only)" and "Site supervisor (materials runner)" options; `team/page.tsx` table renders pretty role labels via a `ROLE_LABELS` map. **`current-rep.ts`** type union extended to match the DB CHECK. 4 files, +83 / -1. `pnpm build` PASS.
`882f637` — `[2026-05-07] Claude Code: estimate builder — drop redundant Create form, "+ New estimate" creates draft + redirects` — The intermediate "Create estimate" page asked for Title / Default waste % / Intro before sending the user to the builder, but those exact fields were already inline-editable on the builder. Net effect: an extra click that produced no information the builder couldn't already capture. Collapsed to a single Server Action: clicking `+ New estimate` on the estimates list inserts a draft (`title="Estimate"`, `default_waste_pct=10`, `intro=null`, `expiration_date=created_at+30d` to match the "valid for 30 days" terms language) and redirects straight to `/jobs/<jobId>/estimates/<estimateId>`. Title + waste % + intro stay inline-editable on the builder. `createEstimateAction` return type changed `Promise<ActionResult>` → `Promise<void>` so it can be wired directly to `<form action={...}>`; on failure it throws (Server Action error surfaces to the user) instead of returning a result object the form had no way to display. Removed: `src/app/(app)/jobs/[jobId]/estimates/create-estimate-form.tsx` (no remaining callers). 3 files, +38 / -103. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `882f637` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Estimate builder improvements — close TVE OS parity gap" fix 1. CLAUDE.md compliance: no `any`, no `console.log`, no silent catches.
`805fefe` — `[2026-05-05] Claude Code — Damage report finalize returns to workflow + top-left back button` — Patrick (Loom 06:29 + 09:22). `ReportEditorV2` accepts `finalizeReturnTo`; `transitionDamageReportStatusAction` success on `finalized` → `router.push('/jobs/<id>?tab=workflow')` so finishing a report drops the rep on the originating workflow tab instead of stranding them on the editor (or pushing them to All Jobs). Editor page top now shows `← Back to workflow` (primary) + `All reports` (secondary). 2 files, +28 / -2.
`7ae1278` — `[2026-05-07] Claude Code — Send Production PDF to PM + org default email field` — Phase 1 deliverable for the third bullet of Chase's Production Manager view spec. The "Send to PM" button (sky-700 fill) sits next to the existing "Production PDF" download button on the estimate header. When `brand_settings.production_manager_email` is set, the button fires straight to that recipient with no prompt; otherwise it inline-expands an email input that defaults to the saved org default. The Resend adapter is still a Phase 4 stub (mock throws) so the action writes the durable execution-record + outbox row pair per the CLAUDE.md mock-adapter contract: `workflow_action_executions` row with `action_key=send_estimate_production_pdf_stub`, `result.simulated=true`, `input` payload includes the recipient + production-PDF URL + first-200-char production-notes preview + actor_rep_id. `integration_outbox_events` row with `provider='mock'`, `status='sent'`, `provider_response={"simulated":true}`, `event_type='estimate.production_pdf.send'` — Phase 2's real Resend adapter promotes this row type to actual sends. UI surfaces an amber "Simulated send to {recipient}" toast so Patrick never sees a silent no-op. Settings → Organization gains a "Production manager email" field under a "Production" fieldset (between Address and License); read-only view renders it as a Row beside the existing brand values. New helper `resolveProductionManagerEmail()` in `src/lib/branding/resolver.ts` so callers don't learn brand_settings column names directly. 7 files, +377 / -1. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `7ae12786abc68411fb0b826a775e9b1b670633dc` via `git ls-remote origin main`. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Production Manager view shipped" (3rd bullet). CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary, mock adapter row clearly marked, idempotency keys on both action + outbox writes.
`6960cb8` — `[2026-05-08] Claude Code — Per-inspector Conclusion templates: schema + per-report templating UI + Profile / /team editors + PDF wire-up` — Lane 4 of the Damage Report Smart Doc work: each inspector signs reports with their own Conclusion template + credentials block. Migration `0057_conclusion_templates.sql` (applied to remote `ujbgksplpyqqektjfqak` via Supabase MCP `apply_migration`) adds `conclusion_template_md` + `inspector_credentials_md` (both nullable) to `reps` and `inspector_rep_id` (FK reps) + `conclusion_variables` (JSONB) + `conclusion_overridden` (TEXT) to `damage_reports`. Patrick Page seeded verbatim from his 2026-05-08 templates (HAAG Certified credentials block + the four-placeholder template). Alan Weene seed is conditional on a rep row being present; FPHI Supabase has no Alan rep yet (only Patrick Page + Chase Whited), so the migration logs a NOTICE and skips — when Patrick onboards Alan via `/team`, the next migration apply (or in-app Settings → Profile / `/team` edit) backfills automatically. New module `src/modules/damage-reports/conclusion.ts` defines the closed placeholder set (`roof_age_estimate`, `prior_condition`, `date_of_loss`, `scope_recommendation`) + `mergeConclusionTemplate()` (renders unfilled variables as `[ Variable Label ]` so reviewers spot misses). Damage report editor gains a Conclusion section: inspector picker (sources from active reps with template set), live inputs grid for whichever placeholders the chosen template uses, live preview of merged conclusion + credentials, and an Override textarea as escape hatch. `saveDamageReportConclusionAction` validates inspector ∈ org. Settings → Your profile lets each rep edit their own template + credentials (extends existing `saveProfileAction` with column-allow-list). `/team` adds an "Inspector template" column with a Dialog editor — owner/manager edits anyone via `updateRepInspectorTemplateAction` (service role + in-org guard). PDF wire-up: `DamageReportData` gains a `conclusion` block; new `conclusionBlockHtml()` renders body as paragraph-broken text + a credentials sub-block above the signature card; PDF route loads the inspector rep, merges `{{vars}}`, falls back to generic boilerplate if none set, and `conclusion_overridden` (when non-null) wins as-is. 13 files, +918 / -19. `pnpm build` PASS. Direct push to `origin/main` (after rebase onto parallel `280afe0` Inspections brain logs). **`local HEAD == origin/main` verified** at `6960cb8617bc88095ccdc039eb5e4a175f2ad432` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-08 § "Lane 4 — Snippets engine + Conclusion templates shipped" (3rd commit). Decision-log entries: P-DEC-151 (per-inspector templates with placeholders), P-DEC-152 (Alan Weene conditional seed). CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary, schema migration committed alongside code, no client-specific literals (templates live as data, not as hard-coded TSX).
`5e204ce` — `[2026-05-05] Claude Code — Settings: editable Organization tab + editable Profile tab (audit fix for items the prior task left readonly)` — Chase opened the live Settings page after Lane 1's "editability" claims and found Organization + Profile tabs were NOT actually editable. Audit revealed: Branding (item 6c, SHA `2c49250`) IS wired and persists; Survival Guide (Phase 7.1, SHA `b4a3xxx` from 2026-05-04) IS wired and persists; Lists (Lane 2) and Automations (Lane 3) work; **Organization tab and Profile tab were both literal `<Row label=... value=... />` displays with no form, no `onSubmit`, no DB write path.** This commit ships both tabs.
`584893e` — `[2026-05-08] Claude Code — Lane 4 follow-up: third inspector Amanda Janse-Vreeling + new {{event_type}} placeholder` — Chase shared one of Patrick's existing Company Cam damage-report PDFs in the post-ship review and surfaced a third inspector: Amanda Janse-Vreeling (Public Adjuster — IL + WI Public Adjuster licenses + HAAG-certified inspector + Xactimate L2 + Public Adjuster Boot Camp Feb 2022). Her template structurally differs from Alan/Patrick's: leads with the opinion paragraph, then age + condition, then closer. Introduces a fifth placeholder `{{event_type}}` (e.g., "hail storm") that Alan + Patrick's templates hardcode as "Hail impacts". Confirmed the existing UI already supports different orderings per inspector by design — each template is stored as raw markdown on `reps.conclusion_template_md`, the PDF builder splits on blank lines preserving input paragraph order, and `extractTemplateVariables()` walks only the placeholders actually present in the chosen template (so Amanda's editor shows 5 inputs, Alan/Patrick's shows 4). Changes: `src/modules/damage-reports/conclusion.ts` (added `event_type` to `CONCLUSION_VARIABLES` first in the array since Amanda's template leads with it, plus labels + placeholders); `src/app/(app)/jobs/[jobId]/reports/actions.ts` (extended `SaveConclusionSchema.conclusionVariables` with `event_type` max 200); `supabase/migrations/0058_amanda_conclusion_template.sql` (same conditional-seed pattern as 0057 — looks up Amanda by name/email, logs NOTICE if missing, updates if found). Migration applied to remote `ujbgksplpyqqektjfqak`. Amanda has no rep row in FPHI Supabase as of 2026-05-08 (verified via `SELECT FROM reps WHERE full_name ILIKE 'Amanda%'` → empty); migration logged NOTICE + skipped. Patrick's manual to-do: create Amanda's rep via `/team` (role suggestion: `manager` — she's a Public Adjuster, not strictly inspector/sales). Backfill auto-runs on next migration apply or via in-app Profile / `/team` edit. 3 files, +82 / -0. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `584893e93ef3ba5b45d8de20d96e8dce96a75c88` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-08 § "Lane 4 follow-up — third inspector Amanda Janse-Vreeling…". CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary, schema migration committed alongside code.
`485e1e1` — `[2026-05-07] Claude Code: estimate builder — Expiration Date picker, persist + display on customer PDF` — `estimates.expiration_date` already existed in the schema (added in 0029) but had no editor UI. Added an `<input type="date">` to the builder header next to Title / Default waste % / Status. New estimates default to `created_at + 30 days` (set in `882f637`'s `createEstimateAction`); the picker is editable thereafter. `SaveEstimateSchema` gained an `expirationDate` regex field; save flow updates `estimates.expiration_date` alongside the other header fields. PDF: no change needed — `32cb8b3` already wired `data.expirationDate` through the route + renderer to print "Estimate expires: <Long date>" near the totals card on every viewMode. Date is parsed in local time so DATE round-trips don't surface an off-by-one bug east of UTC. 3 files, +33 / -1. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `485e1e1` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Estimate builder improvements — close TVE OS parity gap" fix 4. CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary.
`341a6e8` — `[2026-05-05] Claude Code — Port residences count to KnockMap sidebar (Overpass gap fix)` — Migration 0044 added `knock_polygons.estimated_house_count + house_count_fetched_at` columns and the Overpass refresh route was wired in `8da9356`, but the sidebar never showed the count. Root cause: four layers all missed the new columns: (1) `KnockPolygon` TypeScript type; (2) `PolygonRpcRow` + `rowToPolygon()` in `/api/knocks/polygons/route.ts`; (3) `KnockSidebar.tsx` UI; (4) `KnockMap.tsx` refresh handler. All four fixed in one commit. `KnockSidebar.tsx` territory `<li>` restructured to `flex flex-col` — name/knocks row on top, house-count row beneath with `Home` icon + `~N residences` display (null = "No count yet" italic; in-progress = "Counting residences…" italic + spinning `RefreshCw`). `KnockMap.tsx` adds `refreshingPolygonId` state + `handleRefreshPolygonHouseCount(id)` function (POSTs to existing refresh endpoint, updates polygon state) + auto-fires on polygon create. Side fix: installed `@googlemaps/markerclusterer` (was imported by `KnockMap.tsx` but absent from `package.json` — pre-existing build failure on clean install). `pnpm build` PASS. Pushed to `origin/main`.
`32e7b0e` — `[2026-05-07] Claude Code — Production PDF view + view-mode toggle button` — Adds a fifth `pdf_view_mode='production'` that renders a crew-facing materials sheet — qty / unit / description columns only (no prices, no waste %, no totals, no margin, no internal cost), customer phone/email so the PM can call, the selected tier (Good/Better/Best with manufacturer + product name), the production_notes field rendered in a brand-accent callout (8px left border, soft amber background, ⚠ icon, "Production Notes — read before starting" header), and a crew close-out signoff block (Materials verified / Customer walkthrough / Photos captured / Site cleaned & magnet-swept checklist + print-and-sign Crew Lead line + Date field). Estimate page header gets a dedicated "Production PDF" button next to "Generate PDF" — hits the route with `?view=production` so it always renders the production view without flipping the persisted toggle state. Tier-cards PDF view-mode toggle gains a "Production" pill (sky-700 tone, distinct from the amber Internal Review pill). `setPdfViewModeAction` Zod enum extended. PDF route reads `?view=` query-param and (when valid) overrides the persisted pdf_view_mode just for that request. Sample renderer script accepts a 'production' variant for local visual review (renders 251 KB sample PDF). Brand resolved via existing BrandResolver pattern — Patrick's customizations propagate to the Production sheet automatically. New `buildProductionHtml` function in `src/modules/estimates/pdf.ts` keeps the existing customer/internal layout untouched. 6 files, +555 / -31. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `32e7b0ed67111afa6b3a27ca808ce7929d7fcb56` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Production Manager view shipped" (2nd bullet). CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, no client-specific literals (BrandResolver-driven). Rebase note: rebased after `5d4fcc6` + `274b32b` from the parallel Tier auto-fill task — clean merge, both branches added complementary changes to the same files.
`32cb8b3` — `[2026-05-07] Claude Code: estimate builder — Internal Cost + Margin columns + Internal Review PDF view` — Patrick's old builder showed customer-facing columns only (qty / unit price / waste / subtotal / waste $ / total). To match TVE OS he needs to track per-line material cost ("Int. cost") + the resulting margin, and have a way to print an operator-only summary that includes them. Customer PDFs must continue to hide both. Schema: `0052_estimate_line_items_internal_cost.sql` (applied to remote `ujbgksplpyqqektjfqak`) added `estimate_line_items.internal_unit_cost NUMERIC NOT NULL DEFAULT 0` and extended the `estimates.pdf_view_mode` CHECK to allow `'internal_review'` (alongside `'itemized_priced'` / `'itemized_only'` / `'simple'`). Totals helpers (`src/modules/estimates/totals.ts`): `LineInput` gained optional `internalUnitCost`; `LineTotals` + `EstimateTotals` now expose `lineInternalCost` / `lineMargin` / `lineMarginPct` + the matching estimate-level aggregates. Margin computed against waste-inflated cost (`internal_unit_cost × qty × (1 + waste/100)`) so it reflects what Patrick actually pays the supplier when waste is factored in. Builder UI (`estimate-editor.tsx`) gained two amber-tinted editable columns (INT. COST, MARGIN with $ + %) plus a margin block in the sticky totals ribbon — red when negative, emerald when positive. Tier cards (`tier-cards.tsx`) PDF render-mode toggle gained a fourth amber "Internal Review" option with a "never shared with customer" tooltip. PDF renderer (`src/modules/estimates/pdf.ts`): `EstimatePdfData` gained `viewMode` + `customerNotes` + `internalNotes` + `expirationDate`. When `viewMode === 'internal_review'` the PDF surfaces the int. cost + margin columns inline, adds Internal cost / Margin rows to the totals card, prints a top-of-page "DO NOT share with customer" banner, and appends the internal-notes block. Customer-facing modes stay locked down — int. cost / margin / internal notes never render. Customer PDFs gain a Notes block (later repointed to `intro` in `2e7d9fe`) and an "Estimate expires: <date>" line near totals. Server action: `LineItemSchema` gained `internalUnitCost` (z.number().min(0).default(0)); `SetPdfModeSchema` enum extended; save flow persists `internal_unit_cost` to `estimate_line_items`. **Decision (P-DEC-021):** added a fourth `pdf_view_mode` value, `internal_review`, rather than re-using a customer mode + a separate URL flag. The mode is already a CHECK constraint on `estimates.pdf_view_mode` and the toggle is one of three things Patrick already picks per estimate; one mode = one persisted preference, no extra state to remember. 8 files, +335 / -31. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `32cb8b3` post-push (after rebase onto the parallel `render_failed` hotfix `3c2f173`; no conflicts — different modules). Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Estimate builder improvements — close TVE OS parity gap" fix 2. CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary, schema migration committed alongside code.
`313fc88` — `[2026-05-05] Claude Code — Lane 5 Color Database: searchable visual library of past job photos tagged by manufacturer + color (Loom #2 00:00-00:57)` — Patrick's Loom #2 ask: customer says "I want to see what Owens Corning Black Sable looks like on a roof" → he searches a library and pulls 6-8 real photos from past Fresh Page jobs. Real product moat in a saturated market — visual proof for color-curious customers.
`2e7d9fe` — `[2026-05-07] Claude Code: estimate builder — split customer notes vs internal notes` — Renamed the existing "Intro / scope summary" textarea label to "Notes to Customer (appears on PDF)" — column stays as `estimates.intro` for backwards compat (no migration). Added a second textarea below it, "Internal Notes (never on PDF)", amber-tinted, persisted to the existing-but-previously-unused `estimates.internal_notes` column (added in 0029). Internal notes only render on `internal_review` PDFs. The customer-notes block in the PDF (added in `32cb8b3`) now reads from `data.intro` instead of the unused 5.10-stub `customer_notes` column. Removed the lede paragraph at the top of the customer PDF — same content was rendering twice once we have a dedicated bottom Notes block. `SaveEstimateSchema` gained `internalNotes` (z.string().max(4000).nullable.optional). `EstimatePdfData` lost `customerNotes`; `intro` is now explicitly the customer-facing notes per its doc-comment. `TierCards` lost the now-unused `customerNotes` prop. **Decision (P-DEC-022):** kept `intro` as the customer-notes column instead of migrating to `customer_notes` (the unused 5.10 stub). Renaming would have rippled into the seed validator + the snapshot mirror in the workflow tables for no user-visible benefit. The 5.10 `customer_notes` column remains a no-op for now — left in place rather than DROP'd to avoid churn on a column Phase 2's SmartDocs pipeline may still claim. 6 files, +39 / -13. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `2e7d9fe` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Estimate builder improvements — close TVE OS parity gap" fix 3. CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, Zod at every boundary.
`2d9e8d5` — `[2026-05-05] Claude Code — Settings → Branding logo upload: fix nested-form bug, all 4 e2e surfaces now PASS` — Resolves the FAIL surfaced yesterday by `1d76571`. `LogoUploader` had its own `<form action={uploadAction}>` nested inside the outer `BrandingForm` `<form>`; React 19 disallows nested forms — submission threw and never wrote to Storage/DB. Refactor (Option B): removed the inner `<form>`, drove the upload via `useTransition` with a `type="button"` Upload button whose `onClick` reads the selected file from a ref, builds `FormData('logo', file)`, and calls `uploadLogoAction(undefined, formData)` inside `startTransition`. File input has no `name` attribute (so the parent BrandingForm submit never includes a stray 4 MB file); spec selects via `data-testid="logo-file-input"`. After a successful upload, the ref clears the file input. Picked Option B over Option A (sibling form outside BrandingForm) because the logo upload is logically nested inside the Branding fieldset and lifting it out would have either split the visual layout or required restructuring `src/app/(app)/settings/page.tsx` for no UX benefit (decision: `P-DEC-36`). Spec changes: removed both `await page.waitForTimeout(1500)` waits and the `{ waitUntil: 'networkidle' }` Branding-tab navigation option (the post-hydration regen the workaround was masking is gone). Logo file selector switched from `input[type="file"][name="logo"]` to `input[data-testid="logo-file-input"]`. `pnpm exec playwright test tests/e2e/settings-editability.spec.ts --project=chromium` → **4 passed (28.8s)** — Organization round-trip + propagation, Profile round-trip, Branding colors + alt-text round-trip, Branding logo upload round-trip + propagation. Logo upload PASS proof: file uploaded to `brand-assets` bucket, `brand_settings.logo_path` matches `^${orgId}/logo-\d+\.svg$`, `/dashboard` sidebar `<img>` `src` contains both `brand-assets` and the org ID. Throwaway-org cleanup verified post-run: 0 leftover orgs, 0 auth users, 0 brand-assets folders. Patrick's real org/user **NEVER** touched. Triage of similar nested-form patterns: only files with multiple `<form>` tags are `settings/profile/profile-form.tsx` (three sibling forms — safe) and `settings/lists/list-editor.tsx` (two mutually-exclusive forms — safe). No other nested-form bugs found. `pnpm build` PASS (✓ Compiled successfully in 9.9s, all 35 routes registered). 2 files, +38 / -14. Direct push to `origin/main`, no PR. **`local HEAD == origin/main` verified** at `2d9e8d5` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-06 § "Branding logo upload fix". CLAUDE.md compliance: no `any`, no `console.log` in app code, no silent catches.
`2d08b21` — `[2026-05-05] Claude Code — Editable referral sources + carrier dropdown (Lane 2 items 9–10)` — Patrick (Loom 00:00 + 00:54): lead source and carrier should be per-org editable lists Patrick can curate without code changes. **Migration `0045_editable_lists_and_dept.sql`** adds three things in one file. (1) `referral_sources(id, org_id, label, display_order, is_active, created_at, updated_at, UNIQUE(org_id, label))` — owner/manager-only INSERT/UPDATE policies (`rep_is_owner_or_manager()` predicate), org-member SELECT, no DELETE policy (soft-delete via `is_active=false`). Seeded per existing org with Door knock / Referral / Storm response / Web form / Phone-in / Other. (2) `insurance_carriers(id, org_id, name, display_order, is_active, …)` same shape, seeded with top-12 US carriers (State Farm / Allstate / USAA / Liberty Mutual / Farmers / Progressive / Nationwide / Travelers / American Family / Auto-Owners / Erie / Chubb). (3) `jobs.lead_source TEXT` column + `jobs_org_lead_source_idx` — the lead intake form had been collecting `job.source` in form data and discarding it; now the column exists and the action persists it. Stored as the label string (not a FK) so soft-deleted referral_sources entries don't orphan historical jobs. Both new tables follow the existing `quick_links` / `survival_guide` RLS pattern with an explicit `service_role` ALL policy for audit clarity. **Migration `ON CONFLICT (org_id, label) DO NOTHING`** on both seed inserts so re-running the migration against a partially-seeded environment is idempotent. **UI**: new Settings → Lists tab (`/settings?tab=lists`) with two `ListEditor` cards. Each card lets owner/manager add / rename / reorder / hide-from-picker; hidden rows show greyed in the editor with a "hidden" chip but disappear from the public picker. Other roles see read-only with an "ask an owner" hint. The new tab slots third in the nav order (Organization / Profile / Branding / **Lists** / Notifications / Integrations / Team) so Patrick finds it without scrolling. **Lead intake form** (`/jobs/new`) now reads active `referral_sources` + `insurance_carriers` from the org and renders dynamic dropdowns. Carrier picker has a `+ Add custom` toggle that swaps to a free-text `<Input>` for regional carriers (e.g. Cincinnati Insurance) and a `Pick from list` toggle to switch back. Default selection: first active referral source (sorted by display_order then label) and the empty option for carriers (forces an explicit choice). **Zod schema** (`src/lib/zod-schemas/lead-intake.ts`): `job.source` widened from a closed enum to `z.string().trim().min(1).max(80)` — the form posts a label that may have changed between page-load and submit, so we accept any non-empty string ≤80 chars. **Files**: `supabase/migrations/0045_editable_lists_and_dept.sql` (NEW, 156 lines), `src/app/(app)/settings/lists/actions.ts` (NEW, 6 server actions × 2 lists, owner/manager gate, soft-delete via toggle), `src/app/(app)/settings/lists/list-editor.tsx` (NEW, the inline edit UI used by both list cards), `src/app/(app)/settings/page.tsx` (Lists tab content + supabase queries when tab=lists), `src/app/(app)/settings/settings-tabs.ts` (added `'lists'` to the tab key union), `src/lib/zod-schemas/lead-intake.ts` (source enum → free-text), `src/app/(app)/jobs/new/page.tsx` (parallel fetch of reps + referral_sources + insurance_carriers; pass to form), `src/app/(app)/jobs/new/lead-intake-form.tsx` (dynamic referral picker + carrier list/custom toggle), `src/app/(app)/jobs/new/actions.ts` (default `'Door knock'` instead of `'door_knock'`; persists `lead_source` to jobs row). **9 files, +912 / -36.** `pnpm build` PASS (Next 16.2.4 turbopack — Compiled in 12.2s, TypeScript 17.0s, all 80+ routes including `/settings`, `/jobs/new`, `/team`, `/calendar`).
`2711225` — `[2026-05-08] Claude Code — Damage report editor: snippet picker (Cmd+/) + per-textarea trigger button on Cover Notes / Damage Summary` — Wires Patrick's 19 snippets into the damage report editor. New shared component `src/components/site/snippet-picker.tsx` (Dialog + button) — title is search-only, only the description text inserts into the active textarea at the caret. Active on Cover Notes + Damage Summary fields. `Cmd+/` (Mac) / `Ctrl+/` (other) shortcut detects which textarea has focus and routes the insert there; "Insert snippet" trigger button below each textarea covers the non-keyboard path. Insertion respects existing caret + selection — replaces selection, otherwise appends with a leading space, and lands the caret at the end of the inserted text next frame. Server-side fetch of `is_active=true` snippets ordered by label in `page.tsx`; empty library = picker shortcut + buttons stay hidden. 3 files, +315 / -25. `pnpm build` PASS. Direct push to `origin/main`. **`local HEAD == origin/main` verified** at `27112250af2cf4de857581c9713e75eed0f7be8d` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-08 § "Lane 4 — Snippets engine + Conclusion templates shipped" (2nd commit). Decision-log entry: P-DEC-149 (snippets render description only; title is search-only). CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, no DB schema change.
`1e027f1` — `[2026-05-07] Claude Code — Color Library tab: rename customer-facing "Colors" → "Color Library" + fix search input focus loss` — Two scoped fixes Patrick called out in chat. **Fix 1 — rename.** The sidebar entry said "Colors" — ambiguous next to brand-color settings in Settings → Branding. Renamed at every label surface a customer / Patrick reads: sidebar nav (`src/components/site/sidebar.tsx`), `/colors` page `<h1>` + `<metadata title>`, `/colors/[id]` back link ("Back to all colors" → "Back to Color Library"), Settings tab label (`settings-tabs.ts`), Settings → Color Library card title, and the four cross-page "Settings → Colors" empty-state hints (`/colors` empty state, Job → Colors empty state, Customer → Colors empty state, photo color-picker no-matches). **Kept as `colors` / "Colors" intentionally (P-DEC-2026-05-07-06):** `/colors` URL slug, `?tab=colors` Settings query param, DB tables `color_library` / `color_library_photos`, internal API routes `/api/colors/*`, and the Job → Colors / Customer → Colors tab labels (context already implies "Color Library for this job/customer" and the tab strip can't accommodate the longer label). **Fix 2 — search input focus loss in `src/app/(app)/colors/color-search-controls.tsx`.** Patrick's symptom: type "ow", focus jumps out of the input, click back, type "en", focus jumps again, repeat ten times to spell "owens corning". Root cause was `<Input disabled={pending}>` from `useTransition()` — every debounced URL push set `pending = true` while `router.replace` ran; the input flipped to `disabled`, the browser dropped focus, and you couldn't get it back without a click. The 200ms debounce just made it more frequent — the disable was the killer. Fix (P-DEC-2026-05-07-07): dropped `useTransition` + `disabled={pending}` entirely (router transition is non-blocking on its own), moved the debounce out of `useEffect` and into a ref-tracked `setTimeout` triggered from `onChange` (the old effect listed `searchParams` as a dep and re-fired on every URL change because `useSearchParams()` returns a new reference each render), bumped debounce 200ms → 400ms, added `lastPushedRef` so external URL changes (back/forward / Link) still sync the input but our own debounced pushes don't reset it mid-keystroke. URL still updates so result links stay shareable. 9 files, +81 / -42. `pnpm build` PASS. Direct push to `origin/main` (pushed branch `claude/charming-yonath-12e66d` → `main` from worktree). **`local HEAD == origin/main` verified** at `1e027f1c9e8a80f75e15c09d4255fd81cf42a90d` via `git ls-remote origin main`. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-07 § "Color Library tab rename + search focus fix". Decision-log entries: P-DEC-2026-05-07-06 (rename labels only, keep URL/DB/API names) + P-DEC-2026-05-07-07 (drop useTransition, don't just bump debounce). CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, no client-specific literals in shared modules, no provider-SDK leak, no DB schema change, no Zod boundary added or removed.
logo upload bug surfaced)
`1350523` — `[2026-05-06] Claude Code: wire BrandResolver into damage report + estimate PDFs` — Closes the critical propagation gap from the editability audit: the Settings → Branding form (FPHI `2c49250`) was writing to `brand_settings`, the BrandResolver was merging overrides (FPHI `ec3c605`), and app shell / auth / sub portal / survival guide PDF were reading the resolver (FPHI `3eebe7f`) — **but the two highest-stakes customer-facing PDFs (damage report + estimate) were still importing `PATRICK_BRAND` / `PATRICK_COPY` directly** at `src/modules/damage-reports/pdf.ts:17-18` and `src/modules/estimates/pdf.ts:9-10`. Patrick could change his primary color in Settings and see no effect on the PDFs he sends to carriers. Refactor: `renderDamageReportPdf(data)` → `renderDamageReportPdf(data, brand: ResolvedBrand)`, `renderEstimatePdf(data)` → `renderEstimatePdf(data, brand: ResolvedBrand)`. `buildDamageReportHtml` / `buildEstimateHtml` now source company name / contact / address / website / license / tagline from `brandCopy(resolvedBrand)` + `resolvedBrand.{contact,license,website}`, and CSS `:root` vars from `resolvedBrand.brand.colors.{primary,primaryForeground,accent,secondary,headerBgLight}`. Threaded the dynamic accent through `sectionHtml` → `overlaySvg(annotations, accent)` → `shapeSvg(shape, accent)` so annotation strokes inherit the configured accent rather than the baked-in Patrick orange. Route handlers (`src/app/(app)/jobs/[jobId]/reports/[reportId]/pdf/route.ts`, `src/app/(app)/jobs/[jobId]/estimates/[estimateId]/pdf/route.ts`) now `await resolveBrand()` before render. Mirrors the survival-guide PDF pattern. 4 files, +74 / -50. `pnpm build` PASS (✓ Compiled successfully in 9.2s, all routes registered). Direct push to `origin/main`, no PR. **`local HEAD == origin/main` verified** at `1350523` post-push. Daily-log entry: `Patrick_Daily_Log.md` 2026-05-06 § "Pre-call fixes for tomorrow's Patrick Zoom — black-screen overlay + brand propagation into PDFs". CLAUDE.md compliance: no `any`, no `console.log`, no silent catches, no client-specific literals in shared modules — both PDF templates are now fully org-agnostic. Runtime smoke (edit Settings → Branding primary color → render a damage report PDF → confirm color picked up) NOT executed by the agent — recommend one-shot manual check before the Zoom.
`[2026-05-04] Claude Code — Territory house counts (OSM Overpass) + per-pin roof data (Google Solar API) + cost meter` — BOS hybrid approach in Patrick's repo, parallel with the same feature firing in TVE-ops; independent re-implementation against Patrick's `knock_polygons` (PostGIS `geometry(Polygon, 4326)`) + `knock_events` schema (no shared code). **Migration 0044** (`0044_knock_house_counts_and_roof_insights.sql`, idempotent — `ADD COLUMN IF NOT EXISTS`, `CREATE TABLE IF NOT EXISTS`, `pg_policies` guards, `CREATE OR REPLACE FUNCTION`): adds `knock_polygons.estimated_house_count INTEGER` + `house_count_fetched_at TIMESTAMPTZ`; new `knock_roof_insights` table (per-pin Solar API cache, 90-day TTL via `expires_at NOT NULL DEFAULT (now() + INTERVAL '90 days')`, columns `roof_area_meters_sq NUMERIC` / `max_array_panels_count INTEGER` / `roof_segments JSONB` / `raw_response JSONB`, RLS for `org_id = current_org_id()`); new `solar_api_usage_log` table (cost-meter rows, `cost_estimate_cents INTEGER`, `status TEXT CHECK IN ('success','error','cached')`, RLS SELECT gated to `rep_is_owner_or_manager()`); two SECURITY DEFINER RPCs (`knock_polygon_geojson(p_polygon_id UUID)` returning `ST_AsGeoJSON(geometry)` + `knock_polygon_set_house_count(p_polygon_id, p_count, p_fetched_at)`). **Applied directly to FPHI Supabase via psql.** **OSM Overpass adapter** (`src/modules/integrations/overpass/index.ts`) — free, no key, identifying User-Agent, `[out:json][timeout:50] (way["building"](poly:"…"); relation["building"](poly:"…")); out tags;`, residential filter (house|residential|apartments|detached|semidetached_house|terrace|bungalow|cabin|farm|static_caravan) with all-buildings fallback when every result is `building=yes`. **Google Solar adapter** (`src/modules/integrations/google-solar/index.ts`) — `GOOGLE_GEOCODE_API_KEY` (Solar API enabled per Chase), `buildingInsights:findClosest?requiredQuality=HIGH`, `SOLAR_COST_PER_CALL_CENTS = 10` (~$0.10 per call), **Sentry breadcrumb on every invocation** with cost_estimate_cents tag. **POST `/api/knocks/polygons/[id]/refresh-house-count`** (Overpass refresh via the new RPCs). **GET `/api/knocks/[id]/roof-insights`** (cache-first; miss → Solar API + persist + log success row, hit → return cached + log status='cached' cost=0). **`/knocks/insights`** gains `SolarApiCostMeterCard` (admin-only, live-this-month aggregate w/ budget bar; `SOLAR_API_BUDGET_CENTS` env var, default 5000=$50; turns red over 100%) + `PolygonHouseCountsCard` (per-row Refresh button → Overpass + `router.refresh()`). **Drawer "Show roof details"** wired into `recent-knocks-list.tsx` as a new section beneath Address actions; cache vs live badge + roof area in ft² (m²) + max panels + cache expiry. **Solar API fires only on explicit click — never render/hover/auto-prefetch.** 90-day server cache short-circuits second click within window with cost=0. **Coordinated with sibling task `local_8d57d53f` (knocks pin drawer expansion, commit `66d2ddc`)**: that landed first; my commit appends the Roof insights section beneath the existing 4 quick actions cleanly. **Build + deploy**: `pnpm build` PASS (Next 16.2.4 turbopack); pushed direct to `main` after rebasing onto `0894aee`. Files: 8 new + 2 modified, +1453 / -1.
`[2026-05-02] Claude Code — Phase 7.3: Quick Links launcher (Patrick 2nd-call promise)` — new `/links` page Patrick was promised on the 2026-05-01 2nd call. Verbatim ask: *"if I want a button to where like I want to push this button in my system, and it auto opens up an email to my rep"* — same idea but for the websites Patrick clicks every day (Acculynx, Hail Trace, QuickBooks, Bank of America, supplier portals). Mirrors the Quick Links pattern from the Whited Brain dashboard. **Distinct from the Survival Guide** (which is the office recovery dataset — bills, accounts, insurance — office+ only). Quick Links are a lightweight everyday launcher visible to every authenticated rep in the org; everyone can add / edit / delete. **Migration `0040_quick_links.sql`** applied via Supabase Management API directly to FPHI `ujbgksplpyqqektjfqak` (`POST https://api.supabase.com/v1/projects/<ref>/database/query` with the access token pulled from the macOS keychain `Supabase CLI` entry — HTTP 201, `[]` response, columns verified afterward via `information_schema.columns`). Schema: `quick_links(id, org_id, title, url, category, favicon_url, description, sort_order INT, is_pinned BOOL, created_by → auth.users(id) ON DELETE SET NULL, created_at, updated_at)` with CHECK on lengths (title 1–140, url 1–2048, category 1–80 nullable, description 1–600 nullable). RLS scoped via `current_org_id()` for SELECT / INSERT / UPDATE / DELETE (any authed rep) plus an explicit service-role ALL policy (matches the bug_reports / survival_guide standing pattern). Indexes on `(org_id, is_pinned DESC, sort_order ASC, created_at DESC)` + `(org_id, category, sort_order ASC)` so the page paint is one ordered scan. **Page** (`src/app/(app)/links/page.tsx` + `links-manager.tsx` + `actions.ts` + `types.ts`): search input scanning title / URL / category / description, pinned section pinned above all category groupings, links auto-grouped by category (alphabetical, "Uncategorized" pushed last), each card shows favicon + title + hostname + optional description, opens in a new tab with `target="_blank" rel="noopener noreferrer"`, hover-revealed (always-visible on mobile) edit / delete / arrow-up / arrow-down buttons. Add Link modal: title (required) + URL (required, auto-prefixed `https://` if missing, validated via `new URL()`) + category (free text + `<datalist>` of existing categories) + description + pin checkbox. Mobile-first: stacked single-column cards below `sm`, 2-col at `sm`, 3-col at `lg`, 4-col at `xl`. **Sidebar**: `Link2` lucide icon entry inserted between "Survival guide" and "Settings". **Audit**: every create / update / delete / reorder writes to `audit_events` via the service-role client (entity_type `quick_link`, event_type `quick_link.{created,updated,deleted,reordered}`). **URL handling**: `normalizeUrl()` prefixes `https://` when scheme missing, validates with `new URL()`, restricts to `http`/`https`; `deriveFaviconUrl()` builds the favicon URL server-side from `new URL(url).hostname` → **`https://www.google.com/s2/favicons?domain={hostname}&sz=64`** (Google's free favicon proxy, no API key, server-derived on every insert / update). Clients never set `favicon_url`. **Standing rules**: zero `any`, zero `console.log`, every external input parsed through Zod, RLS-scoped writes via the user-session client. **Build**: `npm run build` PASS (only the pre-existing turbopack-root warning). **Push**: rebased onto `1f052a7` (the survival-guide hotfix landed in parallel earlier today) then fast-forwarded direct to `main`. Files: `src/app/(app)/links/{page.tsx,links-manager.tsx,actions.ts,types.ts}` (new), `src/components/site/sidebar.tsx` (Link2 icon + nav row inserted between Survival guide and Settings), `supabase/migrations/0040_quick_links.sql` (new). 6 files, +1175 / -0.
resolves Decision #121 with "Map view + pinnable areas, replaces Hail Trace + Sales Rabbit; no third-party API integration") + Company Survival Guide module + Master To-Do dashboard widget. Demo seed already plants every pipeline stage (Phase 6.1) so the live system is ready for Patrick's screen-recorded walkthrough as soon as the owner login is provisioned.
`[2026-04-29] Claude Code — Phase 6.10: Training 'How It Works' — Patrick-specific 44-step walk + Try it now deep links`
`[2026-04-28] Claude Code — Phase 4.3: archival + cold storage`
Email queued to Patrick requesting Company Cam template + AccuLynx version + snippet library content + per-inspector conclusion paragraphs.
No code change. Plan awaits fire order per recommended sequence (Lane 1 Loom #2 first, no Patrick blockers).
`5d5f9f6` — `[2026-05-05] Claude Code — Renumber 0040_quick_links → 0046_quick_links to fix duplicate version prefix` — pre-Lane-3 cleanup. Two migration files were both prefixed `0040`: `0040_quick_links.sql` (added 2026-05-02, commit `92e6673`) and `0040_knock_events_map_view.sql` (added 2026-05-04, commit `d287544`). Supabase's `supabase_migrations.schema_migrations` PK is on `version` alone, so a single 0040 row could ever record both. Renaming `0040_quick_links.sql` → `0046_quick_links.sql` gives it a unique version slot. Pure rename, 0 content lines changed (`git mv` recorded as 100% similarity rename).
GCP `tve-os` / TVE-OS Server Key (id `ed3a45db-e1c7-46f7-88c9-84b03a0d40a9`): added Solar API to API allowlist (was Geocoding API only). Application restriction unchanged (None). See `Patrick_Daily_Log.md` 2026-05-05 for diagnosis + Vercel log evidence.
hook in place; dispatcher still routes on v2 global prefs only. Tracked in `AOS_Documented_Gaps.md`.
Phase 3.2 / 3.3.
wc-admin catalog UI for browsing drip rows (admin lane).
## [Apr 21, 2026] — Full-day sprint: 61 commits to `athlete-hub-pro/origin/main`
no per-kit copy work needed when each kit gets its first abandoned cart.
No code, no recordings, no Skool platform interaction. Pure documentation lane.
The cross-promo on existing 5 $15 kit landings (`a88a3d6`, separate lane), the abandoned-cart drip system (`89a29ba`/`9f9124c`, separate lane), Skool Module 6, and the wc-admin catalog UI are all explicitly out of scope for this lane.
Tooltip arrow points down toward the help button
Phase 3.3 server-side dispatcher gating
Lead-magnet drip: end-to-end cron-dispatcher fix (emails 5-7 were silently dropped by Resend)
Solar API key scope fix (GCP config only, no commit)
Territory house counts (OSM Overpass) + per-pin roof data (Google Solar API) + admin cost meter
`/knocks` pin drawer expanded (4 quick actions + editable notes + linked-job mini-profile + address actions) + map pin hover tooltip
Phase 7.3: Leads Kanban drag-drop + Calendar event drawer + drag-to-day
`/knocks` Recent rows clickable + `/jobs` tab strip scrollbar hidden
*correction* — pin→job pipeline link, auto-promote, hybrid coloring, Account Volume + Conversion funnel + dedup + route optimizer
superseded above)* Patrick (FPHI) Spotio Tier 3
Rep invite fix + OG image + photo thumbnail overhaul
Walkthroughs v5 refresh
3rd-call build sprint + walkthrough pipeline
BOS full-studio positioning pivot + Pocket Agent bundle decision
root cause found by direct invocation, not guesswork
**resolved**. PageHelpBanner shows live help on every page.
Phase 5b per-row drilldown UI for `notification_fanout_warnings` inside the PA cockpit remains the only outstanding item from the Event Matrix follow-up backlog.
second paid BOS Custom client sold + MVP delivered + BOS MVP Bar locked**
**Modal runtime deployed** — new repo `cwhited26/pa-orchestrator-runtime`, three FastAPI endpoints (dispatch + phase webhook + health). This is the hosted sandbox that runs the 7-phase Algorithm per sub-agent (PA-ORCH-3 / APA-ORCH-20).
**✅ COMPLETED** (all 20 CC prompts deployed by Apr 15 Late Night)
Org context surfaces (`e77adf6`), public `/orgs` discovery + `/orgs/:slug` profile + connect flow (`b2e56a3`), membership tiers with pin/mute/fan (`ac6c076`). See dedicated sub-entries below.
`AutoDraft` already renders `<PageHelpBanner pageKey="auto_draft" />`; article published in `…batch_3_evaluations.sql`.
DKIM authenticated (admin → Apps → Gmail → Authenticate email).
"Notification dispatch pipeline not yet gating on parent_org_memberships prefs" and "Notification dispatcher per-org routing" both moved to the Resolved section.
## 2026-05-08
log as Phase 5b.
Backfill inserts are gated on "last 60–90 days of activity" windows. If Chase wants older rows surfaced in the bell, rerun the backfill WITH modified time windows or migrate a standalone backfill-by-org script.
Fixed invalid `profiles` PostgREST join and added `apikey` header to inter-function calls in `process-onboarding-drip`.
Creates/backfills `public.season_programs`.
all four DB fixes in one idempotent migration.
Tournament Scheduler Phase 2, League/Season Scheduler, AI Scheduler Branding. All in `AOS/CC_Prompts/`.
3 workstreams (inline tooltips, empty-state callouts, deep-link cross-page navigation). Not yet started.
`cwhited94+democoach@gmail.com`
`decision` / `spec` / `open_question` / `change_log_entry` — one row per entry under `<file>#<anchor>` fragment paths riding the existing UNIQUE(user_id, path). Blob-sha skip for unchanged files, stale-fragment cleanup when a log changes, paged reads past PostgREST's row cap. **No migration** — the `type` column never had a CHECK.
`81212fc`
Hardware-delivered AI agent orchestration. One-time $5,000. Threat: **MODERATE-HIGH**.
Google ownership-verification TXT propagated and confirmed.
Domain registered through Cloudflare Registrar.
Phase 1 — canonical inbox views ✅ (`a297b14`, migration `20260725000000`)
Coach → Athlete → Add to Team threw `new row violates row level security policy for table athlete_teams`. Existing `"Coaches can manage their team rosters"` policy gated INSERT on `teams.coach_user_id = auth.uid()`, which is null in the demo and many real orgs.
Build Teams modal *Auto Draft* + *Manual Roster* buttons kicked coaches back to dashboard. Import CSV / Start Blank were unaffected because they fire callbacks (`onImportCsv`, `onStartBlank`) instead of navigating.
the original Event Matrix contract
`[2026-05-10] Claude Code — feat: Pareto SBW follow-up pack (content ideas + deliverables catalog + bootstrap wizard spec + competitive monitor)` — lands all the brain-doc follow-ups from `APA/Competitive_Intel/2026-05-10_Pareto_Talent_Second_Brain_Workshop.md` (commit `a76e7ab`). Files touched:
`[2026-05-09] Claude Code — docs: Whited Consulting Brand Architecture v1 — sub-brand reference under WC parent` — `Whited_Consulting_Brand_Architecture.md` at brain root. BOS · AOS · APA · TVE distinctions matrix.
`[2026-05-10] Claude Code — docs: complete brain update + cold-start handoff (May 10 launch state)` — closes the May 9 leftovers on the WC parent-brand setup. Creates `WC/` system directory at brain root (`Feature_Inventory.md`, `Daily_Log.md`, `Change_Log.md`, `Decision_Log.md`). Updates `Master_Ecosystem_Status.md` (WC row + Section 1 narrative — domain + DNS + Vercel + Workspace email all LIVE end-to-end). Adds Decisions #33 (alias-swap pattern) + #34 (Domain Alias vs Secondary Domain) to `shared/Decision_Log.md`. Appends to `APA/Daily_Log.md` (May 10 entry). Creates `BOS/Daily_Log.md` (new file, mirrors APA pattern). Adds entries to root `MEMORY.md` and `CHANGELOG.md`. Writes `handoffs/2026-05-10_Cold_Start.md` (new cold-start handoff).
`[2026-05-09] Claude Code — feat: register whited.consulting as parent-brand domain` — registers `whited.consulting` as the parent-brand domain in the brain canon. `Whited_Consulting_Brand_Architecture.md` Domains table updated. `Master_Ecosystem_Status.md` Section 1 + Section 2 row added. `Whited_Consulting_Monetization_Strategy.md` domain hierarchy line updated. `MEMORY.md` standing-fact entry added. Cold-start handoff `handoffs/2026-05-09_WhitedConsulting_GoogleWorkspace_Setup.md` written.
`<commit subject verbatim>` — what brain docs were updated and why.
**Resume handoff doc** at `BOS/Products/Patrick/Patrick_Resume_Handoff_2026-05-08.md` — twelve-section snapshot covering what landed today, what's in flight / queued, open questions awaiting Chase, standing rules in effect, repo state across FPHI + whited-brain + whited-brain-dashboard, connector / integration state, manual to-dos for Chase, the first action a resuming agent should take, toolkit + skills assumed, and memory files to re-read.
`[2026-05-01] Claude Code — May 1 full-day brain sync: Patrick paid in full $3,500 + 21 wc-admin SHAs logged + Session_2026-05-01_Full_Day_Recap.md`
`[2026-05-01] Claude Code — Patrick 2nd-call deal-close brain sync ($3,500 locked) + Decision #129 + Patrick_2nd_Call_Decisions + Patrick_Build_Journey + session handoff`
`[2026-04-29] Claude Code — Patrick documentation reconciliation (v4.0 spec + Build Journey + Custom Build Playbook v1 + Patrick Change Log) + Decision #119`
`[2026-04-28] Claude Code — Patrick Phase 4 brain sync (Decisions #110–#114) + final handoff doc`
clarifications doc for Patrick's first Loom. 11+ timestamped ambiguities flagged. Chase scrolls the video and answers; resolved items spawn either bug rows or feature-request entries.
Permit Automation idea row appended (from Patrick's 2026-05-11 Loom @ ~26:25 — municipality detect + per-municipality permit library + auto-fill engine; Tier 1 surfacing within Patrick's 60-day window; Tier 2 post-handoff as a `buildout-os-template` upgrade).
REWRITTEN as scaling-library index. Scope statement, discipline-rule table (techniques in / labels out with examples), when-to-load list, how-an-agent-composes section, active entries table (Russell LIVE, Hormozi QUEUED), add-an-influence criterion + folder shape, reproduction policy, pointer to standing-rule memory file.
NEW. Cross-cut pattern catalog from 4 Funnel University newsletters. 6 sections (Overview, Funnel types, Cross-cutting, Pattern → APA mapping, When to invoke, Source ledger, Reproduction policy). 8 cross-cutting principles. 9-row APA mapping table. Explicit guardrail: structural patterns only, no Russell framework jargon (Hook Formula, Dream 100, Value Stack) — APA stays operator-pattern-generic per APA-2 and APA-9.
§Funnel flow updated to describe the inline-form + dual-interstitial pattern; per-kit checklist swapped "checkout route" for "landing route at `/[kit-slug]`"; new anti-pattern: "Don't build per-kit checkout pages — `KitLandingPage` is the contract."
Fixed Patrick Tier 3 SHA in `Session_Handoff_2026-05-04.md`: `local_0503cc13` → `86b3f9f` (verified on origin/main after pulling FPHI local to `8da9356`).
**`APA/Products/Pocket_Agent_LoCoMo_Benchmark_Scoping.md` (NEW)** — Research + scoping doc (no benchmark run) on what it would take to run the LoCoMo memory benchmark against PA·dev, prompted by EverOS publishing 93.05% on LoCoMo prominently. Covers: what LoCoMo is (10 conversations × ~35 sessions × ~300 turns × ~9k tokens; 5 QA categories — single/multi-hop, temporal, open-domain, adversarial; F1 / BLEU-1 / LLM-judge metrics); how PA's tier split (Active work / Knowledge / Patterns) + Dev API (`/v1/memory/tier`, `/v1/memory/entry`, `/v1/personas/<id>/invoke`) maps to LoCoMo's ingest→retrieve→score harness; cost (tens of $/pass — not the gating factor) + engineering (multi-day lane, judge calibration is the hidden cost); run **after** v5 Wave B so the number reflects turbovec retrieval + dispatcher, not today's file-grep. **Recommendation: Option A (honest "why we don't lead with LoCoMo" essay) now → Option B (run it + report Y with framing) after Wave B → Option C (agent-execution benchmark) as long game.** Key honesty finding: published 90%+ LoCoMo numbers are the LLM-as-Judge metric, not F1 (F1 runs 38–49 even for leaders). Drafted by Claude Code.
`manual_sql/patrick_won.sql` running tonight against `bos-internal` Supabase. Flips Patrick's prospect row to `stage='won'`, creates the Fresh Page Home Improvement client row from prospect data, creates a build row at `current_phase='phase_0'`, `status='in_progress'`, `product='BOS Custom'`, repo `cwhited26/BOS_Fresh_Page_Home_Improvement_Build_Patrick`. Patrick predates the auto-promotion path shipped same day in wc-admin Decision #135 — his row has no `quote_id` to attribute from, so the manual SQL is the special-case handoff. The auto-promotion path covers everyone going forward.
Recovered the interrupted Batch E work from the local `athlete-hub-pro` dirty main checkout after syncing to Vercel's then-current production build (`31dc769`).
Recovered the interrupted Batch B work from `.claude/worktrees/batch-b-parent-polish`, preserved it in local WIP commit `42e2c9b`, cherry-picked it onto current `main`, cleaned lint warnings, amended to final commit `77fa3b0`, and pushed.
Zero-Context-Loss System
Contractor Quote Template Extraction
Contractor Ops Template Extraction
Deploy Verification & QA
Template Extraction from tve-os
Seed Data Enrichment + Verification
Google Workspace Secondary Domain — chase@buildoutstudios.co LIVE
PR #1 hardening fixes (whited-brain-dashboard)
AI-powered chat interface added to whited-brain-dashboard
Post-Call Updates
All 3 CC Builds Complete
CC Phase 3: Help Banners + How It Works Modals
wc-admin fully rebuilt as BOS business CRM (mothership), diverged from contractor-os-template
Global Error Handling System shipped across all 3 repos
Dual Email Provider Support shipped across all 3 repos
**AOS_Sales_Playbook.md:** Core strategy and tactical guide for sales operations.
**Plan Change Notifications:** Added `notify-plan-change` edge function that emails Chase on every org plan change (from Stripe webhooks, platform admin, or manual checks). Triggered by a DB trigger on `organizations.plan` AFTER UPDATE.
**Onboarding Drip Sequence:** `send-onboarding-email` edge function handles a 6-email drip sequence (Welcome, Getting Started, Pro Tips, Check-in, Feature Request, Value Reinforcement).
desktop and mobile
Marketing positioning rewrite under Russell ladder / Pocket Agent framing
Upload branded cover images for the Skool community + 12 classroom module covers.
`<commit subject verbatim>` — high-level summary of files changed + the why (decision number if applicable).
`[2026-05-10 Claude Code] Footer: remove dormant Twitter link` — removed wrong Twitter link from the `Founder()` component on `whited.consulting` (not Chase's account; was a placeholder).
Next.js 14 App Router scaffold; one-page hub site (hero / what-this-is / four-brand 2×2 grid / founder bio / footer). Tailwind dark mode aesthetic, mobile-first, OG meta wired. Vercel deployment created; `whited.consulting` + `www.whited.consulting` custom domains attached; SSL issued.
## 2026-06-06 (PA Orchestrator — SPEC v5 lock + Wave A shipped + Wave B in-flight)
`e1660f5` — `[2026-06-05] Claude Code — feat(APA): SPEC v5 — Orchestrator pivot (chat-as-surface + dispatcher + project scaffolding (Jeff Hunter pattern) + connector write-actions), unified tier ladder, pauses v3 Wave 2 to consolidate` — **THE strategic pivot.** `APA/Products/Pocket_Agent_Orchestrator_SPEC_v5.md` turns PA from a "Claude wrapper / memory + drafting chatbot" into **the one chat that does the work**: a Dispatcher that spawns sub-agents from user messages, **Project Scaffolding** (Jeff Hunter pattern, PA-ORCH-9 — Project → Milestones → Tasks → Sub-tasks decomposed + owner-approved BEFORE dispatch) as the planning layer, an **Approval Inbox** for every external write, and a **Connector write-actions** library. Triggered when Chase's own PA recommended Cursor/Devin/Lovable instead of helping spawn lanes — the SPEC realizes the May 23 "true partner" vision lock. 13 locked decisions PA-ORCH-1..13 (summarized in `APA/Decision_Log.md` → "PA v5 SPEC lock"). Three sequential waves (no parallel lanes — architecture too load-bearing): **A** chat-as-surface · **B** dispatcher + Modal runtime + approval · **C** connector library. Unified tier ladder across v3/v4/v5 (Starter $37 / Pro $97 / Pro+ $149 / Studio $297 / Studio+ $497 / Enterprise). Personas v3 Wave 2 PAUSES until A+B land.
`docs(todos): add PA Stripe (LIVE mode) section — SMB ladder + dev add-on price IDs/payment links + migration 020 + payment-link provisioning-gap blocker`.
review copy added for Chase.
third revision pass. Two Chase decisions folded in:
second revision pass correcting items from live Russell Chief of Staff screenshots (May 13):
major revision. Folded in all learnings from `APA/Competitive_Intel/2026-05-11_Russell_Brunson_Chief_of_Staff.md`. Changes:
11 new memory files under `memory/`, new `memory/MEMORY.md` index, new `APA/Products/Pocket_Agent_Spec.md`, new `APA/Products/Value_Ladder.md`, new `BOS/BOS_Studio_Positioning.md`, 5 new decisions (APA-25 through APA-29), major entries in `APA/Daily_Log.md`, `APA/Change_Log.md`, `APA/Decision_Log.md`, `APA/Feature_Inventory.md`, `BOS/BOS_Decision_Log.md` (#162), `BOS/BOS_Change_Log.md`, `BOS/BOS_System_Map.md`, `CLAUDE.md`, `Whited_Consulting_Roadmap.md`. All supersession pointers (`APA/Products/Dashboard_Play_Spec.md` → `Pocket_Agent_Spec.md`) added. Cascade-staleness `depends_on` frontmatter populated across all new docs.
new `ask` subcommand. Hybrid-searches `memory/`, `sessions/`, root `*.md`, and the high-signal sub-trees (`APA/`, `BOS/`, `AOS/`, `TVE/`, `shared/`, `Projects/`, `voice/`, `automations/`) with ripgrep (grep fallback), ranks the hits by distinct-keyword score, reads ~8 lines of context around each, and sends the top 10 context blocks to Claude Haiku 4.5 with a strict-citation system prompt. Returns a synthesized answer + `Sources:` block with `path/to/file.md:LINE` refs to stdout. Reuses the same `ANTHROPIC_API_KEY` env-or-`getsecret`-fallback resolution path as `consolidate` (no duplicated key-fetch logic). Stop-word filter strips common interrogatives; max-keyword regex caps at 8 to keep ripgrep responsive on the full brain. `--max-filesize=2M` ignores stray huge binaries that crept into a sub-tree. Returns the canonical no-context line — "I don't have context on this — first time it's come up." — when no hits land. Smoke-tested with three real questions (BOS internal-cost decision, Skool URL recall, nonsense control); citations verified against `BOS/BOS_System_Map.md:5` and `APA/Daily_Log.md:7`. Direct REST `fetch` to `api.anthropic.com/v1/messages` (no SDK). Model: `claude-haiku-4-5-20251001`. Help text updated; "ANTHROPIC_API_KEY" section now reads "`brain consolidate` and `brain ask`" since both share the resolver.
both alternatives that were rejected for v1 (hourly cron + per-kit variants) now flip to shipped.
Module 6 (Ambient Brain teach + Capture Pack walkthroughs) and Module 7 (Output Pack modules) Skool lesson scripts drafted in full spoken-script form, voice-spec compliant, ready for recording. Two new canonical files: `APA/Products/skool-community/Module_6_Scripts.md` (11 lessons — 6.1 Why a second layer, 6.2 Install ambient capture, 6.3 Consolidate weekly, 6.4 Search and recall, 6.5 Sanitize and stay safe, 6.6 When ambient memory fails clinic, 6.7 Voice → Brain, 6.8 Screenshot → Brain, 6.9 Share Sheet URL → Brain, 6.10 Email Forward → Brain, 6.11 Loom URL → Brain) and `APA/Products/skool-community/Module_7_Scripts.md` (8 lessons — 7.1 Daily Standup, 7.2 Pre-Call Brief, 7.3 Customer Q&A In Your Voice, 7.4 Real-Time Objection Handler [V2 stub], 7.5 Weekly Compete-Watch, 7.6 Content From Past Wins, 7.7 Plain-English Decision Query, 7.8 MVP Signal). Each lesson follows the Module 1 script precedent: cold open, walkthrough with `[bracketed]` screen direction, install steps, use cases, member exercise, close, failure modes, deliverables, pre-flight checklist. Cross-link references added to `APA/Products/Capture_Pack_Spec.md` and `APA/Products/Output_Pack_Spec.md` Live Status blocks plus `Ambient_Brain_Architecture.md` §Module 6 curriculum draft — those source specs now point at the script doc as the canonical place for lesson-text revisions. Decision: any future revisions to these lessons update the script docs, not the source specs.
brain hygiene corrective sweep covering both the 2026-05-12 evening Capture/Output Pack ship and tonight's reconciliation. `APA/Products/Capture_Pack_Spec.md` + `APA/Products/Output_Pack_Spec.md` Live Status tables updated to underscore form for the `waitlist_for` column value, status string format (`COMING SOON` / `LIVE`), corrected config variable names (`CAPTURE_PACK` / `OUTPUT_PACK`), and a pointer to the bos-internal Supabase project where migration 058 was applied. `MEMORY.md` gets one new index line for the waitlist landings entry.
APA brain: log kit landing marketing fill-out + cross-promo + bundle visibility` — this entry. No spec or memory files changed; the 4 kit specs already in `APA/Products/` carried the source material directly.
APA brain: log Capture/Output Pack scaffolding ship, add waitlist mode decision, MEMORY.md index update` — added "Live Status" tables at the top of `APA/Products/Capture_Pack_Spec.md` (C3 LIVE, others COMING SOON) and `APA/Products/Output_Pack_Spec.md` (all 8 COMING SOON). Documented the flip mechanic: each module goes LIVE by changing one row in `aipocketagency-website/src/app/_waitlist/waitlist-config.ts`. Decision APA-20 added to `APA/Decision_Log.md`. MEMORY.md gained one index line for the waitlist scaffolding.
kit #2 markdown source
End-to-end verification: simulated Stop event with a fake `sk_test_*` value → markdown landed at `sessions/2026-05-12/HHmmss-*.md` with redaction applied (`[REDACTED:secret]`) and tool calls collapsed to one-liners. Pre-commit hook blocked a commit containing `sk_test_abc123fake4567890definitely1234secret` with a clear remediation message.
**`feedback_skool_url_is_aipocketagency.md`** — Skool URL is `skool.com/aipocketagency` (plural *agency*), website domain is `aipocketagent.com` (singular *agent*) — both spellings are canonical for their respective surfaces, never find/replace one to match the other.
additive `waitlist_for TEXT` column on `apa_leads` plus partial index. Will land in the next wc-admin migration sweep along with the rest of the queued schema work.
`[2026-05-12] Claude Code — wc-admin: Telegram bot + shared-thread architecture (Migration 061, /api/telegram/webhook)` — Telegram bot with three-way shared-thread model: one chat = Chase + Claude + GPT, each message tagged by author. `telegram_conversations` table (migration 061) with unified `messages` jsonb array. Default both reply; `@claude` or `@gpt` to direct. Phase 3 (coding-agent dispatch) deferred.
`[2026-05-13] Claude Code — APA drip: migration 060 seeds 12 per-kit abandoned-cart emails (Dev-Team / CLAUDE.md / Discovery→MVP / Wire-Brain × 3 cadence each)` — seeds the T+60m / T+1440m / T+4320m abandoned-cart cadence for the four kits beyond `dispatch-playbook`. 12 rows (3 emails × 4 kits) into `public.apa_drip_emails` (`audience='abandoned'`, `day_offset=0`, `delay_minutes ∈ {60,1440,4320}`, `slug` pattern `abandoned-{Nm}-{kit-slug}`). Each email body grounded in the corresponding kit's `marketingContent` from `src/lib/kit-config.ts`: Dev-Team mentions `stale-audit.sh` + `lane-summary.sh`, CLAUDE.md mentions the 6 stack-specific templates, Discovery→MVP mentions the Patrick case study end-to-end, Wire-Brain mentions the Slack bot-vs-user token trap. Voice drafted to `voice/chase-spec.md` (T+60m ~65-75 words direct, T+24h ~110-130 words reframe with named content, T+72h ~85-100 words "three emails, last one" close mirroring the dispatch sequence). `ON CONFLICT (slug) DO NOTHING` safe-replay. Applied to Supabase project `earhglnkxdthsbraazmj`. 1 file / 520 ins.
`[2026-05-12] Claude Code — APA: add apa_leads.waitlist_for column for Capture/Output Pack signups` — finally lands `supabase/migrations/058_apa_leads_waitlist_for.sql` in source control (the migration was authored 2026-05-12 evening but stayed untracked on disk until tonight; meanwhile `059_apa_abandoned_cart.sql` was committed by a sibling lane, so 058 needed to land to keep wc-admin migrations sequential). Header rewritten to reflect the underscore convention. The migration itself was already applied to bos-internal Supabase project `earhglnkxdthsbraazmj` via the Supabase MCP earlier this evening (column verified: `waitlist_for TEXT NULL`, partial index on non-null values).
getpa.dev v3 build: **Obsidian-voice founder letter**, **Project Scaffolding card**, **EverOS comparison page** with the honest "EverOS is local-first SQLite + LanceDB" framing. (Base getpa.dev site `1f7bcf8` + env fix `8545e25` logged in the Wave 2 entry below.)
`APA/Decision_Log.md` — PA-TG-11.
`APA/Marketing/Skool_Template_Gallery_Launch_Post.md` — NEW: the "The 21-template Gallery is live" Skool post draft for the Pocket Agent Launchpad (Chase to copy/paste; not published).
`BOS/Sites/Master_Plan.md` — PA integration section updated to "all 20 captured."
`BOS/Sites/Master_Plan.md` — PA integration section updated: the sync step and the mock/capture pipeline are no longer TBD.
`APA/Decision_Log.md` — PA-TG-6..10 appended.
`lib/kv.ts`: Upstash REST client (raw fetch, no npm package)
`d776734`
`[2026-05-11] Claude Code — feat: add Layer 2 ambient capture (install + hook + pre-commit + brain CLI)` — public template now ships the second-layer pattern alongside the existing intentional one. Files added: `install-ambient.sh` (idempotent installer), `templates/hooks/brain-ambient-capture.sh` (Stop hook + Layer A redaction), `templates/git-hooks/pre-commit` (Layer B blob-scan with optional gitleaks pass), `templates/bin/brain` (CLI: status / search / consolidate / sync / prune), `templates/prompts/consolidate.txt` (Haiku extraction prompt with source_sessions traceback), `.brain-config.json.example`, `sessions/.gitkeep`, `memory/.proposed/.gitkeep`. README + `templates/bin/README.md` updated with the install walkthrough.
`[2026-05-09] Claude Code — feat: three-paths block on buildoutstudios.com — Done for you / by you / with community` — adds matching three-paths section so the funnel reads coherently across both brand sites (BOS = done-for-you · DIY = aipocketagency-brain · APA Skool = community).
`c9c5b34` — `[2026-06-08] Claude Code — PA Lead Scout Phase 1: URL list scraper (paste URLs → Bright Data Web Unlocker → Claude extraction → Haiku classifier → brain write → Mission Control batch card; Lead Source is a Project)`. The paste-a-URL-list flow, end to end: a new `lead_scout` Connection (paste a Bright Data API key, stored AES-256-GCM encrypted in the new `pa_connections.config` jsonb; Studio+/Enterprise can flip to PA's shared platform key); `src/lib/leads/*` (`scout.ts` orchestrator, `brightdata.ts` Web Unlocker fetch via the native `POST api.brightdata.com/request` REST API, `extract.ts` Claude-Sonnet profile extraction against the owner's plain-English pattern, `classify.ts` Haiku fit-bucket classifier mirroring the YouTube ingester, `source.ts` Lead-Source-is-a-Project CRUD, `runs.ts` data layer, `denylist.ts` URL screening, `csv.ts`); seven routes (`POST/GET /api/app/apps/lead-scout/sources`, `POST .../sources/[id]/run`, `GET .../runs/[id]`, `GET .../runs/[id]/csv`, `POST /api/connectors/lead-scout/{store,disconnect}`); the `/app/apps/lead-scout` surface (hero + three use-case cards Live/Phase-2/Phase-4 + sources list + new-source sheet); an Apps-tab card; and a `lead_scout_batch` Mission Control card (classification chips + top-5 + CSV download + a disabled Phase-3 "Generate outreach" hook). Migration `044_lead_scout.sql` (additive: 3 tables + the `pa_connections.config` column + the `pa_inbox_items` kind widen, all RLS owner-scoped). `pnpm build` + lint + tsc + **618 vitest** (16 new) green; isolated worktree off `origin/main` (`23baf8b`). Honest deviations from the SPEC draft, both following the build-brief: per-batch cap is 200 free / 2000 paid (not the spec's monthly per-tier ladder, still TODO); brain notes land at `brain/leads/url-list/<date>-<source>/<domain>.md`. 🔴 Chase: apply mig 044 + connect Bright Data (paste a key, or set `BRIGHT_DATA_API_KEY` + `BRIGHT_DATA_ZONE` on Vercel for the Studio+ shared lane).
`beb875b` — `[2026-06-07] Claude Code — PA: Google Calendar Connection (list/create/update/cancel/propose_times, approval-gated writes)`.
`9c90c9f` — `[2026-06-07] Claude Code — PA: Gmail API send-as-you for replies (extends OAuth scope, replaces external hand-off)`. Kills the external-Gmail-app hand-off that left the To field empty; extends Google OAuth to include `gmail.send`.
`4f99837` — `[2026-06-07] Claude Code — PA: unify capture flows (chat upload + iOS share extension now persist bytes to assets/ + trigger memory absorption — every capture surface reaches Documents)`.
`3df3b83` — `[2026-06-08] Claude Code — PA tab rewrites: every tab now tells the full story + shows example prompts + links to related tabs (Chase's plain-English standard)`.
**Wave A — `2a704ee`** — `[2026-06-05] Claude Code — feat(PA): v5 Wave A — chat-as-surface refactor`. Single chat input at PA home (`/app/home`), tabs collapse into slash-command filters (`/tasks`, `/personas`, `/brain`, `/inbox`, …) over one append-only `pa_chat_messages` log, inline card renderers (`memory_write`, `persona_invoke`, plus schema-ready inert `sub_agent_activity` + `action_approval` placeholders), persistent chat state, voice integration. Ships **dark** behind `PA_CHAT_AS_HOME` (default off) — `/app/home` redirects to the existing tabbed UI and chat APIs 404 until flipped, so paying customers see no change. Migration `018_chat_as_surface.sql` (additive: `pa_chat_messages` append-only + soft-archive, `pa_chat_filter_state`, RLS owner-reads-own, GIN index on `filter_tags`). Decisions APA-ORCH-14..19. Logged in brain at `954e3de`. **Chase manual:** apply migration 018 + test on preview + flip `PA_CHAT_AS_HOME` (see `shared/Chase_Manual_Todos.md`).
`[2026-06-05] Claude Code — feat(PA): wire Stripe payment links + tier-cap mapping for unified SMB ladder + PA Sync/Publish dev add-ons`. Locks the unified ladder (Decision **APA-35**): Starter $37 / Pro $97 / Pro+ $149 / Studio $297 / Studio+ $497 / Enterprise, plus PA Sync $96/yr + PA Publish $200/yr Dev add-ons. The `/pricing` CTAs point at the LIVE links; the Stripe webhook maps active price ID → SMB tier and writes `pocket_agent_subscriptions.tier`; `lib/personas/tier-caps.ts#getCurrentTier` reads that column (falls back to legacy status→`pro` when null). Migration `020_pocket_agent_tier.sql` (additive: `tier text` + CHECK + `addon_sync`/`addon_publish` booleans) — **applied to the PA Supabase project** (`tier` + `addon_sync` + `addon_publish` columns on `pocket_agent_subscriptions`).
`[2026-06-05] Claude Code — feat(PA): aipocketagent.com SMB marketing redo`. Repositions the SMB marketing surface (separate from the dev-facing `getpa.dev`). Fresh Page Home Improvement reframed as **customer testimony** (Patrick is a customer who runs his business on PA — not Chase's own business). Trust bar shows **Tennessee Valley Exteriors / Whited Consulting / AthleteOS** as the operator's real businesses. Intent-framed pricing on the unified SMB ladder (APA-35). EverOS comparison page added with the honest "EverOS is local-first SQLite + LanceDB" framing carried over from `2ce4fc7`. Copy run through `voice/chase-spec.md` §10 + the anti-slop checklist (`memory/feedback_no_ai_slop_in_marketing_copy.md`); forbidden-phrase grep clean.
`[2026-06-05] Claude Code — feat(PA): PAI-inspired primitives — ISA (12-section Spec) + 7-phase Algorithm + WORK/KNOWLEDGE/LEARNING memory tiers + TELOS + ContainmentGuard`. Adopts the architectural primitives validated by Daniel Miessler's PAI v5.0.0 (rationale: `memory/reference_pai_validation.md`; borrow the patterns, use our own customer-facing language, cite PAI as inspiration). The **ISA** (Ideal State Artifact) generalizes the SPEC pattern into a reusable 12-section brain artifact; the **7-phase Algorithm** (OBSERVE→THINK→PLAN→BUILD→EXECUTE→VERIFY→LEARN) scaffolds non-trivial agent tasks; **memory tiers** split the flat `memory/` into WORK / KNOWLEDGE / LEARNING; **TELOS** consolidates a business's mission/goals/beliefs; **ContainmentGuard** is the structural-privacy guard (`assertPathInZone` / `filterPathsToZone` / `withZone`, fail-closed) that pairs with Supabase RLS. These become the shared substrate for Personas Wave 1 (reused at `8c46103`) and the Orchestrator.
`[2026-06-05] Claude Code — feat(PA): Connections — Gmail OAuth + cron sync`. Promotes the Inbox from a draft-approval queue toward a real triage surface (Chase decision Jun 5: finish Connections ASAP). Gmail OAuth connect + scheduled cron sync pull incoming mail that needs triage into the same Inbox surface where drafts live. Dedicated `GMAIL_TOKEN_ENCRYPTION_KEY` for token-at-rest (kept separate from the BYO-LLM `LLM_PROVIDER_KEY_ENCRYPTION_KEY` so the two rotation paths stay decoupled). Slack / iMessage triage + Outlook remain parked on `APA/Roadmap.md`.
`[2026-06-05] Claude Code — feat(PA): Dev GTM Wave 1 — BYO LLM dispatcher + Public REST API v1`. Every PA agent + persona call routes through `lib/llm/dispatch.ts`, which resolves `pa_llm_provider_settings` and applies the locked fallback semantics (Decision APA-DEV-14): unset/`pa_managed` → PA Claude (Sonnet 4.6); BYO 401 → mark + silent fallback + `usedFallback` banner; BYO 429/5xx → surface (don't mask a real quota signal); broken config → degrade to managed; no managed key → hard 402. **6 BYO providers — Claude / OpenAI / Groq / xAI Grok / local-OpenAI-compatible / custom-endpoint** (Grok added per SPEC v4 §9.1, brain `ec5bc85`). Key decryption uses a dedicated `LLM_PROVIDER_KEY_ENCRYPTION_KEY`. Migration `019` adds `pa_llm_provider_settings`. Public REST API v1 ships at `app.aipocketagency.com/api/v1/` (no `vercel.json` rewrite needed — filesystem routes win; APA-DEV-13) with request-log sliding-window rate limiting (APA-DEV-15). Decisions APA-DEV-13/14/15 (already in `APA/Decision_Log.md`). **Chase manual:** verify `LLM_PROVIDER_KEY_ENCRYPTION_KEY` is set on Vercel (see `shared/Chase_Manual_Todos.md`).
`8c46103` — `[2026-06-05] Claude Code — feat(PA): Personas + Team Wave 1 — Mode A internal team agents (5 templates + wizard + catalog + detail + chat surface + invite flow + weekly digest + tier caps), reuses Specs/Memory tiers/ContainmentGuard primitives from 0f7b62a` — Ships Mode A only (internal team share); Modes B+C (public link + widget) are Wave 2. Migration `015_personas.sql` (7 tables + `increment_persona_usage()`, RLS owner-reads-own, `business_id` = owner PA user id — **apply to PA Supabase via Supabase MCP**). New `src/lib/personas/*` (types, 12-section persona Spec reusing the ISA primitive, 5 templates VSM/VCSA/VOM/VR/VMD, §10 tier-caps, opaque revocable share tokens, PostgREST data layer, zone-scoped knowledge RAG). `ContainmentGuard` extended with `assertPathInZone`/`filterPathsToZone`/`withZone` — every persona knowledge read is zone-wrapped, fail-closed. Full `/api/personas/*` surface (CRUD, versioned spec + rollback, knowledge upload PDF/DOCX/MD/TXT/URL→markdown, Resend seat invite + revoke, share-link regen, conversation log, streaming SSE chat with tier-cap + ContainmentGuard, public accept). UI: catalog, 5-step wizard, detail (Overview/Spec/Knowledge/Team/Conversations/Settings), `/persona/[token]` team chat, `/personas/accept/[token]`. Weekly digest cron (Fri 13:00 UTC). Vitest: tier-caps + templates + containment-zone (74 tests pass). `pnpm build` + `pnpm test` green. Decisions `APA-PERSONA-11..18` logged. New env: `PA_PERSONAS_DEFAULT_MODEL`, `PA_PERSONAS_FALLBACK_MODEL`, `PA_PERSONAS_PUBLIC_BASE_URL`, optional `PA_PERSONAS_DEFAULT_TIER` (see `shared/Chase_Manual_Todos.md`).
**Lead magnet ebook PDF built and live.** "How I Run 4 Businesses From My Phone Using an AI Brain" — 13-page designed PDF compiled from Typst source. Hosted at `aipocketagency.com/guide/how-i-run-4-businesses-from-my-phone.pdf`. Sourced from `whited-brain` brain docs (voice spec, Value Ladder, Pocket Agent Spec, CLAUDE.md). Written in Chase's voice per `voice/chase-spec.md`.
`[2026-05-13] Claude Code — APA: flip O7 Output Pack status to LIVE + decision-query install page` — flips `OUTPUT_PACK.modules[O7].status` from `coming-soon` → `live` in `src/app/_waitlist/waitlist-config.ts`, adds `liveHref: "/output-pack/decision-query"` + `liveCta: "Install recipe"` matching the C3 wiring pattern. Adds the install page at `src/app/output-pack/decision-query/page.tsx` — Hero / What it does / Install (CLI pull + `ANTHROPIC_API_KEY` check + iOS Shortcut wire-up + citation discipline) / Use cases / Next-step CTA back to `/output-pack#waitlist`. Mirror of the C3 install-recipe approach but on-site (the brain repo Shortcut recipe is linked from inside the install section). Output Pack hero subhead updated to "Plain-English Decision Query is live tonight; the rest of the operating layer ships behind it." per the locked anticipation-building pattern. Typecheck clean.
`[2026-05-13] Claude Code — APA drip: hourly cron + dedup safety check (vercel.json + drip/sweep)` — flips `vercel.json` cron schedule from `0 14 * * *` (daily 14:00 UTC) to `0 * * * *` (hourly). T+60m abandoned drip now lands within 0–60 min of crossing the 60-min threshold instead of 1–25h. Adds a second-line idempotency guard inside `src/app/api/apa/drip/sweep/route.ts`: new `hasPriorSentEvent(leadId, emailId)` helper in `src/lib/wc-admin-supabase.ts` queries `apa_email_events` immediately before `sendEmail`. Closes the partial-failure resend window (Resend ok + Postgres patch fail) introduced by the 24× invocation multiplier of hourly cron. Also swapped the event-insert + state-patch order — event row goes first so the next sweep's prior-event check works even if state patch fails. `SendResult` tightened to a discriminated union (`sent` / `skipped` / `error`) so report rows distinguish "skipped because already sent" from "skipped because not due". Typecheck clean. 3 files / 91 ins / 12 del.
`[2026-05-12] Claude Code — APA: reconcile waitlist_for values to underscore form (capture_pack / output_pack) to match user spec + lock DB column convention` — flipped the bundle slug values posted to `/api/apa/leads` from hyphen (`capture-pack`, `output-pack`) to underscore (`capture_pack`, `output_pack`) across `src/app/_waitlist/waitlist-config.ts` (CAPTURE_PACK.slug, OUTPUT_PACK.slug, WaitlistBundle.slug type), `src/app/_waitlist/WaitlistForm.tsx` (prop type), `src/app/_waitlist/WaitlistLanding.tsx` (slug prop type), and `src/app/api/apa/leads/route.ts` (WAITLIST_SLUGS const + docblock). URL paths (`/capture-pack`, `/output-pack`) stay hyphenated. Reason: clean Postgres identifier-style column reads + future drip/RLS predicate ergonomics. `pnpm build` clean, both pages still ○ static.
`[2026-05-13] Claude Code — APA: fill marketing content for 4 bare kit landings (Dev-Team / CLAUDE.md / Discovery→MVP / Wire-Brain), add Related Kits cross-promo + bundle visibility on all 5 landings` — Populated `KitConfig.marketingContent` for the four kits that were previously rendering the bare hero + form treatment (`dev-team-document-set`, `claude-md-template-library`, `discovery-to-mvp-prompt-pack`, `wire-brain-to-stack-guide`). Each block follows the locked Dispatch shape (heroPill, heroSubhead, problemHeadline, problemParagraphs, whatsInsideHeadline, whatsInsideIntro, whatsInsideItems, whatsInsideOutro, dealHeadline, dealParagraphs). Drafts sourced from each kit's spec in `APA/Products/<slug>_Spec.md` and cross-checked against `voice/chase-spec.md` §10 (problem-first hooks, felt-experience anchors, sourced specificity — Patrick $3,500 build / discovery-to-MVP-in-4-days for Discovery pack, competitor $497 anchor for Dev-Team, etc.). All four drafts cleared §10 banned-phrase scan. Two new shared sections render between WhatsInside and FormSection on all 5 kit landings: (a) `BundleCTA` — names $60 public bundle vs $75 sum-of-parts, "save $15 ≈ one kit free" framing, CTA to `/bundle`; (b) `RelatedKits` — 4-card grid showing the OTHER 4 kits with name + blurb + `$15` CTA each (reuses `KIT_SLUGS` so a sixth kit auto-populates). New public route `/bundle` listing all 5 kits, $60 anchor, CTA into the Dispatch funnel where the founder $47 bundle hits at step 2 — preserves the funnel-only "this price isn't shown anywhere else" structural scarcity on the upgrade-bundle interstitial. New constant `BUNDLE_PUBLIC_USD = 60` in `src/lib/kit-config.ts` (distinct from `BUNDLE_PRICING.offerUsd = 47`, which stays funnel-only). Touched: `src/lib/kit-config.ts`, `src/app/_kit/KitLandingPage.tsx`, `src/app/bundle/page.tsx` (new). No backend / DB / Stripe / drip changes — pure content/UI lane. `pnpm build` clean (20 static pages built). Smoke-tested all 5 kit landings + `/bundle` at HTTP 200 with expected copy present.
APA: Capture Pack + Output Pack waitlist landing pages + lead capture waitlist_for field` — two new public routes shipped: `/capture-pack` (5 capture modules, C3 LIVE / C1+C2+C4+C5 COMING SOON) and `/output-pack` (8 output modules, all COMING SOON). New shared components at `src/app/_waitlist/` (`waitlist-config.ts` → per-module status source of truth, `WaitlistLanding.tsx` → hero + catalog + Hormozi-anchor pricing + form, `WaitlistForm.tsx` → email capture POSTing to `/api/apa/leads` with `waitlist_for` set). `/api/apa/leads` route extended with waitlist-mode branch: when `waitlist_for ∈ {capture-pack, output-pack}` is present the kit-slug validation is bypassed and the response returns `{lead_id, waitlist_for, status:"waitlisted"}` instead of the order-bump `next_url`. Homepage gets one new section (`AmbientBrainStack`) between `DevTeamArtifacts` and `WhatItIsNot` — two cards linking to the bundle pages with live/coming counts. C3 module row on `/capture-pack` deep-links to `whited-brain/automations/brain-inbox-shortcut-recipe.md`. `pnpm build` + `pnpm lint` clean. No Stripe wiring on the new pages (waitlist mode). Existing 5 $15 kit landings + funnel untouched (cross-promo is a separate lane). Decision APA-20 logged.
`[2026-05-12] Claude Code — APA funnel: wire 7 clean kit hero PNGs (Dispatch, Dev-Team, Discovery, Wire-Brain, CLAUDE.md, bundle, Skool card) + flip heroAvailable true` — ChatGPT second-batch regen landed clean (no caption leak; proper `[ bracket ]` tag markers per kit; AI Pocket Agency badge present). 5 per-kit hero PNGs saved at `/public/funnel-images/<slug>-hero.png`, plus refreshed `bundle-hero.png` (5-kit composite across laptop / tablet / phone / 2 books, each labeled `[ KIT ]`) and `skool-community-card.png` (floating callouts: Live calls 3x/week, Founding 50 locked at $47/mo, Kits refresh monthly). All 5 `KIT_CONFIG[slug].heroAvailable` flags flipped `false` → `true` in `src/lib/kit-config.ts`. Per-kit landings now render hero image + form again. `tsc --noEmit` + `eslint --max-warnings=0` clean; full `next build` deferred to Vercel CI (full build exceeds Cowork 45s sandbox call ceiling, but type + lint cover the surface area of this diff). README at `public/funnel-images/README.md` updated — table flipped from PENDING REGEN → LIVE — clean, regen history block added. Decision APA-19 logged.
`[2026-05-12] Claude Code — pull 5 corrupt per-kit hero PNGs (baked-in agent caption)` — the 5 generated kit hero PNGs shipped with a visible "Premium PDF mockup" caption baked into the image (agent annotation leak). All 5 PNGs deleted from `/public/funnel-images/`; `kit-config.ts` flipped `heroAvailable: false` on every kit so the landings render the hero block without a missing-image fallback. Customer-facing pages are clean; PNGs pending regen by Chase via fresh ChatGPT prompt.
**Single landing per kit, form inline.** `src/app/_kit/KitCheckoutPage.tsx` → renamed `KitLandingPage.tsx`. New route `src/app/[kit-slug]/page.tsx` renders for all 5 kits via `generateStaticParams`. Standalone `src/app/dispatch-playbook/page.tsx` deleted; long-form copy migrated to `kit-config.ts` as `marketingContent` (problem block + 11 what's-inside items + deal block) and rendered by the shared component. Form lives at `#form` anchor on the same page — no `/checkout` hop.
**Dispatch Playbook PDF rebuilt on Typst template** — `pdf-templates/kits/dispatch-playbook.typ` authored from `Dispatch_Playbook_Draft.md` (11 sections). Built via `bun run pdf:build dispatch-playbook` → `public/dispatch-playbook.pdf` (235 KB, ~14 pp). Typography matches the 4 other kits. Replaces 921-byte placeholder.
`dev-team-document-set.pdf` (161 KB, 10 pp), `claude-md-template-library.pdf` (137 KB, 10 pp), `discovery-to-mvp-prompt-pack.pdf` (164 KB, 11 pp), `wire-brain-to-stack-guide.pdf` (208 KB, 12 pp). All public. Generated from `pdf-templates/kit.typ` (shared Typst template, Inter + JetBrains Mono).
`[2026-05-09] Claude Code — feat: APA landing v1 — memory-full pain hero + one-command setup framing + three-paths handoff hint + OG share` — initial landing for `aipocketagency.com`. Hero leads with the wall-you-just-hit framing; install is `git clone cwhited26/aipocketagency-brain`; three paths sketch routes toward BOS / DIY / APA. Cloudflare DNS pointed at Vercel; apex domain live.
`[2026-05-09] Claude Code — chore: initial seed — README + 11 deliverable PDFs + 14 markdown templates + MIT license` — public MIT repo. Brain conventions stubs (System Map, Decision Log, Daily Log, Feature Inventory, Change Log) + memory file format + multi-agent handoff protocol templates + 11 deliverable PDFs from `Skool_Launch_Plan.md` §7. Stable URLs via `raw.githubusercontent.com` and jsDelivr CDN.
**`Pocket_Agent_Webinar.pptx`** — 60 slides, dark-navy palette (BG `#0F1729`, surface `#1E293B`, accent blue `#3B82F6`, white text, slate muted), Arial Black headers + Calibri body. Built from `marketing/copy-drafts/chatgpt-6a29aaa5/parts/part-2-webinar-script.md` (1,621 lines of GPT-Hormozi-voiced webinar source — title + on-screen text + speaker script + 15-Q Q&A + 6 demo notes + 7 chat prompts + CTA buttons). Five slide archetypes (title / big-idea / content / quote / CTA). Initial bullet-overflow bug on slides 11 + 19 (broken `Inches(...)` × `914400` Emu arithmetic in `build_content_slide`) fixed; re-QA via `soffice --headless --convert-to pdf` + `pdftoppm` JPG inspection — slides 11/19/23/44/60 all render clean now.
Corrected documentation: chase@buildoutstudios.co is a Google Workspace alias (secondary domain under tnvex.com account), not Cloudflare Email Routing. Updated all references.