Architecture Overview

Tech Stack

LayerTechnologyPurpose
FrontendReact + Vite + TypeScriptCustomer portal
BackendSupabase (PostgreSQL + Edge Functions)Database, auth, serverless
HostingVercelFrontend deployment
PaymentsStripeBilling, subscriptions
SchedulingGoogle Calendar (native)Booking layer — service account writes to “SV Bookings”
CRMHubSpot (Free)Partnership sales pipeline
AuthSupabase Auth (Magic Links)Passwordless authentication
StorageSupabase StorageItem photos

Infrastructure

ServiceDetails
Supabase Projectgmjucacmbrumncfnnhua
Supabase URLhttps://gmjucacmbrumncfnnhua.supabase.co
Regionus-east-1
DatabasePostgreSQL 17.6.1
Vercel TeamStorageValet

Vercel Projects

ProjectURLRepo
sv-websitewww.mystoragevalet.comsv-website
sv-portal-2026portal.mystoragevalet.com (cut over Jun 10, 2026)sv-portal-2026
sv-portalsv-portal-gamma.vercel.app (v1 — decommissioned Jun 29, 2026; retired placeholder, repo archived)sv-portal (archived)
sv-wikiwiki.mystoragevalet.comsv-wiki

Development Tools

CategoryTools
RuntimeNode.js 24.14.0 LTS (Homebrew node@24, native ARM64) — npm 11.11.0 bundled
AI DevelopmentClaude Code (native installer — Homebrew cask on Mac Studio, ~/.local/bin on MacBook Air) + plugins
DatabaseSupabase CLI, Supabase MCP
HostingVercel CLI
PaymentsStripe CLI (STRIPE_API_KEY from sv-env auto-detects live mode; use env -u STRIPE_API_KEY stripe for test mode)
Analyticsgcloud CLI (Homebrew cask gcloud-cli) — Application Default Credentials for GA4 MCP
Pythonpipx (Homebrew formula pipx) — isolated Python app runner for GA4 MCP server
Secrets1Password CLI
TerminaliTerm2 + Ghostty (Ghostty is the default terminal for Claude Code work)
WorkflowRaycast (app switching, shortcuts, clipboard history, custom scripts)

MCP Servers (Claude Code Integrations)

Claude Code connects to 16 MCP servers, organized by type. These give Claude direct, tool-based access to external services without manual API calls or dashboard navigation.

User-Scoped MCPs (local processes, available in all Claude Code sessions)

ServerTool PrefixPurposeAuth Method
Supabasemcp__supabase__*Direct database access, schema exploration, SQL executionOAuth (remote HTTP)
Calendlymcp__calendly__*List/cancel events, view invitees, manage schedulingPAT from 1Password via launcher script
Resendmcp__resend__*Send/manage emails, contacts, domains, broadcasts — debug delivery issuesAPI key from 1Password via launcher script
GA4 Analyticsmcp__analytics-mcp__*Query website traffic, run reports, realtime visitors — read-onlyApplication Default Credentials via gcloud
Bear Notesmcp__bear-notes__*Search, read, create, edit, and tag Bear notes — no auth requiredLocal filesystem (Bear sqlite DB)

Google Workspace moved to Anthropic’s official managed connectors (Gmail / Google Calendar / Google Drive, listed below) on Jun 22, 2026; the former custom mcp__google-workspace__* local MCP and its Google Cloud OAuth app were retired — see the Modernization Watchlist.

Cloud Connectors (managed by Anthropic, available in Claude Code + Desktop + Web)

ConnectorTool PrefixPurpose
Canvamcp__claude_ai_Canva__*Design creation and management
Excalidrawmcp__claude_ai_Excalidraw__*Diagramming and whiteboarding
GitHubmcp__claude_ai_GitHub_MCP__*Repos, issues, PRs, releases, code search
Gmailmcp__claude_ai_Gmail__*Read/draft/search email
Google Calendarmcp__claude_ai_Google_Calendar__*Event management, free time, scheduling
Google Drivemcp__claude_ai_Google_Drive__*Read/search/share Drive files (Docs, Sheets, etc.)
HubSpotmcp__claude_ai_HubSpot__*CRM pipeline management (sales only)
Stripemcp__claude_ai_Stripe__*Customers, subscriptions, invoices, products
Vercelmcp__claude_ai_Vercel__*Deployments, projects, build logs

The Zapier connector (mcp__claude_ai_Zapier__*) was disconnected Jun 2026 — the paid annual plan went essentially unused (modern Claude models cover the automation it once bridged) and lapsed to free Jun 20, 2026. Being an account-level claude.ai connector, removing it applies across all machines automatically (unlike per-machine local skills).

Plugin MCPs (installed via Claude Code plugin marketplaces)

ServerTool PrefixPurpose
Context7mcp__plugin_context7_context7__*Live API documentation lookup for any library
Supabase (plugin)mcp__plugin_supabase_supabase__*Backup/fallback Supabase connection (separate OAuth from user-scoped Supabase)

Related: Claude in Chrome (not registered as an MCP)

Browser automation tools (mcp__claude-in-chrome__*) are provided by the Claude in Chrome browser extension, which speaks Claude Code’s extension protocol — it does not appear in claude mcp list. Used for screenshots, form filling, navigation, and reading web pages.

MCP Launcher Script Pattern

Three MCP servers use launcher scripts to inject secrets from 1Password at runtime, avoiding plaintext credentials on disk:

# Pattern: ~/Documents/storagevalet/Technology/Utility_Scripts/{name}-mcp-launcher.sh
# Each script fetches its secret via op read, then exec's the MCP server process.

# Calendly: calendly-mcp-launcher.sh โ†’ fetches PAT โ†’ npx calendly-mcp-server
# Resend:   resend-mcp-launcher.sh   โ†’ fetches API key โ†’ npx resend-mcp
# GA4:      ga4-mcp-launcher.sh      โ†’ sets ADC path โ†’ pipx run analytics-mcp

GA4 MCP Infrastructure

The GA4 Analytics MCP (mcp__analytics-mcp__*) provides read-only access to GA4 reports. Requires Google Cloud project storage-valet-analytics with ADC credentials. Read-only — cannot inspect admin settings (traffic rules, data filters). Full details →

Claude Code Skills

Skills are packaged units of Storage Valet expertise that Claude Code auto-discovers and loads on demand — each is a single SKILL.md file (occasionally plus support files) with YAML frontmatter describing when it applies. They inject SV-specific context (brand rules, pricing, promo codes, pipeline structure, architecture constraints) so Claude does not have to re-learn the business each session. A skill can be triggered automatically when its description matches the task, or invoked explicitly as /skill-name.

Storage Valet maintains a custom skill library of roughly 28 SV-specific skills plus a few general-purpose design skills (as of Jun 25, 2026). The full library was activated in Claude Code on Jun 23–25, 2026 (previously only three — sv-brand-voice, sv-portal-dev, sv-promo-pages — ran locally; the rest were claude.ai web-library only).

Library by function

AreaSkills
Technical / portalsv-portal-dev, sv-promo-pages, sv-web-design, sv-email-design
Brand & contentsv-brand-voice, sv-copywriting, sv-cro, sv-seo-content, sv-social-media, sv-visual-design
Sales & partnershipssv-sales-email, sv-sales-enablement, sv-partnership-outreach, sv-crm-pipeline, sv-meeting-prep, sv-competitive-intel
Growthsv-growth-experiments, sv-referral-program, sv-google-ads, sv-facebook-ads
Finance & strategysv-financial-model, sv-investor-materials, sv-quarterly-review, sv-data-analyzer
Ops / legal / CXsv-operations-sop, sv-compliance-tracker, sv-contract-reviewer, sv-customer-comms
General (non-SV)design-taste-frontend, emil-design-eng, review-animations

Canonical source & the per-machine sync trap

The canonical source of truth is the set of staged .zip files in iCloud at ~/Documents/storagevalet/Technology/claude-skills/ (each zip holds SKILL.md at its root). Skills install to ~/.claude/skills/<name>/SKILL.md.

Two-layer sync trap (confirmed Jun 25, 2026)

The iCloud zip files sync between the Mac Studio and the MacBook Air automatically. The installed ~/.claude/skills/ folder does NOT — it is per-machine, like all of ~/.claude/. So a skill edited on one Mac reaches the other as a file, but Claude Code there will not use it until it is re-extracted locally:

# Install / refresh one skill on a machine from the canonical zip:
unzip -o ~/Documents/storagevalet/Technology/claude-skills/<skill>.zip \
  -d ~/.claude/skills/<skill>/

After editing skills on either Mac, run the extract on the other to keep them in parity.

Freshness discipline: skills capture point-in-time data (pipeline counts, promo-page totals, scheduling mechanics) that drifts. Verify and patch on install rather than trusting a stale snapshot — e.g. the Jun 2026 activation corrected promo-page counts (63 → 70) and customer-scheduling references (Calendly → native portal / Google Calendar) before installing. Skills that quote CRM figures point Claude to query HubSpot live instead of hardcoding a number.

Tech-Stack Ledger & Modernization Watchlist

Last modernization review: Jun 29, 2026. This is the anchor for the weekly modernization review in the ;update daily-maintenance prompt (Part 5) and the [MODERNIZATION DRIFT] check in sv-audit. Its job: catch a custom/DIY component that an official option has since superseded — instead of letting it run unmaintained for months (as the Google Workspace custom MCP did before this review existed).

The watchlist tracks every self-hosted / custom integration that should be re-evaluated whenever the vendor ships an official, managed, or better-supported alternative. The Anthropic-managed claude.ai * cloud connectors are not on the watchlist — Anthropic maintains them.

ComponentTypeWhy custom / what to watch forLast reviewed
Calendly MCP (mcp__calendly__*)Custom — launcher + PATCalendly shipped an official Claude MCP connector (Jun 2026), but Calendly is being retired for native Google Calendar (DEC-0001) and v1 was decommissioned Jun 29, 2026 — so drop this launcher, do not modernize it.Jun 29, 2026
Resend MCP (mcp__resend__*)Custom — launcher + API keySuperseded: Resend now ships an official Claude Code plugin (bundles the MCP server + Resend Skills; install via /plugin → resend). It’s vendor-maintained vs. our hand-rolled launcher. Evaluate switching (low-urgency — confirm it handles the 1Password-injected API key cleanly first).Jun 29, 2026
GA4 Analytics MCP (mcp__analytics-mcp__*)Custom — pipx + ADCStill no official Anthropic-managed analytics connector (only 3rd-party, e.g. portermetrics). Keep; watch for one.Jun 29, 2026

Retired (reference case): the Google Workspace custom MCP (@alanse/mcp-server-google-workspace + Google Cloud app claude-mcp-485220) was replaced Jun 22, 2026 by Anthropic’s official Gmail / Calendar / Drive connectors. It had run unmaintained for months after the official connectors shipped (Feb 2026); its Google Cloud app sat in “Testing” mode, so the OAuth token expired every 7 days and a failed re-auth popup fired on nearly every Claude Code launch. This is exactly the drift the watchlist exists to prevent.

Review cadence: Part 5 of ;update runs weekly (it skips if the date above is < 7 days old). Each review web-searches vendor changelogs (Anthropic, Google, Supabase, Stripe, Vercel, Node LTS) for replacements / deprecations / EOL, outputs a recommendations table (no auto-changes), and bumps the “Last modernization review” date above.

Codex ↔ Claude Code Collaboration (GPT‑5.5 + Opus 4.8)

This is the standard, canonical way Storage Valet uses OpenAI Codex: as a collaborator inside Claude Code, driven from the terminal. Claude Code (Opus 4.8) is the orchestrator and owns the final result; it delegates specific work to Codex (GPT‑5.5) through the official codex@openai-codex plugin. Installed on Mac Studio May 31, 2026.

For future AI agents — read this first

If you are Claude Code (Opus 4.8): you may hand work to Codex for an independent second opinion, adversarial code review, rescue debugging, or a parallel background task. Use the codex-rescue subagent or the /codex:rescue command. You stay the orchestrator and integrate whatever Codex returns.

If you are Codex (GPT‑5.5): you are being called as a delegate from within a Claude Code session. Return focused diagnosis or implementation; Claude reconciles it and presents the result to Zach.

The terminal is irrelevant. This is an in-process CLI plugin, not a screen reader — it behaves identically in Ghostty (the default terminal for SV work) and iTerm2. Never assume a specific terminal is required for Codex collaboration.

How to invoke

MethodUse for
/codex:rescueExplicitly hand a problem to Codex — debugging, a fix request, or deeper root-cause investigation
codex-rescue subagentClaude invokes Codex proactively when stuck or when a second implementation/diagnosis pass adds value
Natural language“Get a Codex second opinion” / “Have Codex review this” — Claude routes to the subagent
/codex:setupVerify the runtime is ready. --enable-review-gate forces a Codex review before each stop (off by default)

What Codex contributes

Setup & key facts

Marketplaceopenai/codex-plugin-cc (GitHub-verified OpenAI org)
Plugincodex@openai-codex v1.0.4
RuntimeLocal Codex CLI, authenticated via ChatGPT login; the shared runtime starts on demand on the first review/task
Data boundaryCode and context are sent to OpenAI whenever Codex is invoked. Approved for SV code. Never route secrets (op / .sv-session values) through it.
MachinesInstalled on Mac Studio. Plugins do not sync between machines — the MacBook Air needs the same install before Codex is available there.

Install (per machine) — run these inside Claude Code:

/plugin marketplace add openai/codex-plugin-cc
/plugin install codex@openai-codex
/reload-plugins
/codex:setup   # wires to the local Codex CLI; sign in with the ChatGPT account if prompted

Do not confuse with ChatGPT “Work with Apps”

That is a separate, unrelated feature where the ChatGPT desktop app passively reads the terminal screen via macOS Accessibility. It supports iTerm2 / Terminal / Warp but not Ghostty, and is not how SV collaborates on code. The Codex plugin above is the chosen path precisely because it is terminal-agnostic and makes Codex an active participant rather than a screen-watcher — which is what lets Ghostty stay the default terminal.

Repository Structure

Integrated System (two portals + shared backend)

These repos share the common Supabase backend (project gmjucacmbrumncfnnhua) and must be considered together:

~/code/sv-portal-2026  # LIVE customer portal (Vite 8 / React 19) โ€” native Google Calendar; cut over Jun 10, 2026
~/code/sv-portal       # v1 portal (Vite 5 / React 18) โ€” DECOMMISSIONED Jun 29, 2026 (frontend retired, repo archived)
~/code/sv-edge         # Supabase Edge Functions (Deno/TypeScript) โ€” shared by both portals
~/code/sv-db           # Database migrations (SQL) โ€” shared by both portals
~/code/sv-docs         # Ops scripts, runbooks, archives (orchestrates all 7 repos)

Cutover executed Jun 10, 2026 (~20:33 ET): portal.mystoragevalet.com now serves the sv-portal-2026 codebase — the Vercel domain was re-pointed per DEC-020, with the founder waiving the remaining checklist items (signup E2E, Manage-Billing verify, $2,000-cap verify; now post-flip tasks). The first live-domain booking was verified end-to-end the same evening: DB custody + audit trail + Google Calendar event. v1 (sv-portal) was decommissioned Jun 29, 2026 (DEC-021): frontend retired, v1-only edge functions + RPCs + views removed, repo archived. Ops now lives in the v2 portal’s staff /ops surface (W1.4).

Both portals run against the same Supabase project. The v2 backend (additive) was merged and applied to prod on Jun 9, 2026; native Google Calendar scheduling is the live booking layer on the customer domain (DEC-0001 executed): service account sv-calendar@sv-scheduling.iam.gserviceaccount.com writes to the founder-owned “SV Bookings” calendar (secrets GOOGLE_SERVICE_ACCOUNT_JSON + GOOGLE_CALENDAR_ID set in Supabase). Availability comes from get-available-slots (plan service windows ∩ 48-hour lead ∩ calendar FreeBusy), with bookings available up to 365 days out via the month-paged slot picker. Calendly remains only as v1’s legacy read-only webhook path.

Marketing & Docs Sites

RepoPurposeURL
sv-websiteMarketing site, signup flow, 70 promo pageswww.mystoragevalet.com
sv-wikiInternal docs (AI + human reference)wiki.mystoragevalet.com

sv-website is not standalone — its signup/partnership forms call three sv-edge functions (signup-webhook, create-checkout-trial, partnership-inquiry) on the shared Supabase project, so changing those contracts breaks the site. Only sv-wiki is fully standalone (no integrations).

Customer Portal (sv-portal-2026 — LIVE)

Overview

Customer Surfaces (6)

SurfacePurpose
LoginMagic link authentication
DashboardInventory overview + booking management
Items + Add ItemItem catalog; photos-first item creation
Item DetailSingle-item view — schedule, reschedule, revert, remove
BookingItems-first scheduling flow (native Google Calendar slots)
AccountProfile, billing, settings

Staff operations live in the v2 portal’s /ops surface (shipped W1.4) — staff-gated (is_staff()) and AAL2-stepped-up, reading fn_v2_ops_queue / fn_v2_ops_booking_detail and acting through the staff edge wrappers (complete-service, staff-booking-cancel, staff-booking-reschedule, staff-item-revert).

v1 (sv-portal) — Decommissioned

v1 was decommissioned Jun 29, 2026 (DEC-021): frontend retired (static placeholder at sv-portal-gamma.vercel.app, git disconnected), its v1-only edge functions + RPCs + views removed from prod, repo archived. Its former staff routes (/ops, /admin/waitlist, /admin/customers) and the get_ops_actions RPC behind them no longer exist — ops moved to the v2 /ops surface above.

Commands

cd ~/code/sv-portal-2026
npm run dev          # Vite dev server (mock mode by default โ€” every surface browsable)
npm run build        # tsc -b && vite build (must pass before any push)
npm run typecheck    # tsc -b
npm run test         # vitest run

cd ~/code/sv-portal  # v1 โ€” DECOMMISSIONED Jun 29, 2026 (archived; reference only, not deployed)

sv-edge (Edge Functions)

Overview

Functions (20)

Scheduling & v2 Booking (Google Calendar)

FunctionPurposeTrigger
get-available-slotsAvailability: plan service windows ∩ 48-hour lead ∩ calendar FreeBusyPortal booking flow
v2-booking-confirmConfirm booking; create “SV Bookings” calendar eventPortal
v2-booking-cancelCancel booking; revert items; delete calendar eventPortal
v2-booking-rescheduleMove booking to a new slot; update calendar eventPortal
v2-booking-revertPer-item revert off a scheduled bookingPortal

Stripe Integration

FunctionPurposeTrigger
create-checkout-trialStripe checkout with 14-day trial (LIVE). Returns HTTP 409 for an existing member (trialing/active/past_due) to prevent double-billing (B1, Jun 15, 2026).Landing page
create-checkoutDECOMMISSIONED Jun 14, 2026 โ€” returns HTTP 410 Gone; superseded by create-checkout-trial (legacy setup-fee flow contradicted the no-setup-fee canon).โ€”
create-portal-sessionStripe billing portalPortal
stripe-webhookProcess Stripe eventsWebhook

Calendly (Legacy v1 Path)

FunctionPurposeTrigger
calendly-webhookLegacy v1 read-only path (HMAC verification ENABLED, fails closed) — not the live booking layerWebhook

Booking Operations

FunctionPurposeTrigger
bookings-listList all bookings (ops dashboard)Ops UI
booking-getGet single booking with itemsOps UI
booking-cancelCancel booking, revert item statusesPortal, Ops UI
complete-serviceMark service completedOps UI
update-booking-itemsModify items on pending bookingPortal

Customer & Email

FunctionPurposeTrigger
admin-create-customerCreate customer bypassing StripeOps/admin
signup-webhookPre-register, check service areaLanding page
partnership-inquiryPartnership form submissionsWebsite
send-emailTransactional emails via ResendSystem

Utility

FunctionPurposeTrigger
health-checkReturns status + timestamp (edge function health probe)System

Deployment (CRITICAL)

โš ๏ธ ALWAYS use --no-verify-jwt flag for webhooks

cd ~/code/sv-edge

# Individual function
supabase functions deploy stripe-webhook --no-verify-jwt
supabase functions deploy calendly-webhook --no-verify-jwt

# Or use the deploy script (recommended)
./deploy-and-test.sh

Webhook URLs

WebhookURL
Stripehttps://gmjucacmbrumncfnnhua.supabase.co/functions/v1/stripe-webhook
Calendlyhttps://gmjucacmbrumncfnnhua.supabase.co/functions/v1/calendly-webhook

Incident Log

DateIncidentRoot CauseResolution
Feb 18-19, 2026 update-booking-items and booking-cancel returning CORS errors and HTTP 401 from portal. Item attachment and booking cancellation completely blocked for customers. Two bugs: (1) CORS preflight Access-Control-Allow-Headers missing apikey and x-client-info — browser blocked POST after OPTIONS. (2) Auth called getUser() without token argument on a stateless Supabase client, so JWT was never verified (always 401). Commit a32c6d3: added apikey, x-client-info to CORS headers; switched to getUser(token) pattern (matching create-portal-session). Both functions redeployed. Verified via production logs: POST 200 on v44/v16.
Feb 11-15, 2026 stripe-webhook returning HTTP 500 for all event types. 6 unique events failed (34 total retry attempts). 1 subscription stuck as past_due. Two overloaded versions of update_subscription_status existed in the database (one accepting text, one accepting subscription_status_enum). PostgREST could not disambiguate (error PGRST203). Created by Nov 25 + Jan 7 migrations using CREATE OR REPLACE with different param types (PostgreSQL treats as separate functions). Migration 20260215000001 drops the subscription_status_enum overload, leaving only the text version. No edge function changes needed. Failed events replayed from Stripe Dashboard.

Lesson Learned: Function Overloading + PostgREST

PostgreSQL's CREATE OR REPLACE FUNCTION does NOT replace a function when the parameter types differ — it creates a second overload. PostgREST cannot disambiguate overloaded functions with the same parameter names but different types. Always DROP FUNCTION the old signature before creating a new one with different parameter types.

Promo Page Architecture

Property-Specific Promo Landing Pages

70 property-specific promo pages are served from a single HTML file at sv-website/promo/index.html. No build step, no framework, no external CSS dependencies. See Partnerships > Promo Landing Pages for the business context.

How Routing Works

A Vercel rewrite rule in sv-website/vercel.json maps all promo slugs to a single file:

{ "source": "/promo/:code", "destination": "/promo" }

Vercel's cleanUrls: true setting automatically serves promo/index.html at the /promo path. When the page loads, client-side JavaScript:

  1. Reads the URL slug (e.g., beacon from /promo/beacon)
  2. Looks it up in a PROPERTIES object embedded in the HTML (70 entries)
  3. If found: populates the page with the property name, promo code, share links, and QR code
  4. If not found: hides the promo content and shows a “Page Not Found” message with a link back to the main site

File Structure

FilePurpose
sv-website/promo/index.htmlThe promo page (single file, all 70 properties — inline CSS + JS)
sv-website/vercel.jsonRewrite rule: /promo/:code/promo
sv-docs/specs/promo-landing-pages.mdTechnical implementation spec (future phases)
sv-docs/guides/promo-landing-pages.mdComprehensive operational guide

Design Characteristics

Analytics Events

Promo pages fire events to both Vercel Web Analytics and GA4:

Event NameTriggerData
promo_page_viewPage loads with a valid slugslug, property, code
promo_share_copyUser clicks “Copy to clipboard” on the share snippetslug
promo_link_copyUser clicks the copy icon on the direct linkslug

Events use the window.va?.('event', ...) script-tag API pattern (not .track()). GA4 captures page views and scroll depth automatically via Enhanced Measurement.

QR Code Generation

QR codes are generated dynamically via api.qrserver.com (external API). Each encodes the full promo URL (e.g., https://mystoragevalet.com/promo/beacon). The QR image uses Midnight (#0A1628) for brand consistency with the Operation Midnight Teal palette. Suitable for lobby flyers, elevator screens, move-in packets, and digital signage.

Dual-View: Resident vs Property Manager

Promo pages support two view modes controlled by a URL parameter:

ViewURLBehavior
Resident (default)/promo/beaconClean page with property name, promo code, and signup CTA. Share tools are hidden.
Property Manager/promo/beacon?pm=trueAdds “Share with Residents” section: copy-paste email snippet, direct link with copy button, and printable QR code for lobby signage.

When PM mode activates, the ?pm=true parameter is stripped from the URL bar via history.replaceState so the clean URL can be shared. PM-originated share links include ?src=pm for analytics attribution. The toggle is implemented client-side — the .share-section element is hidden by default and displayed only when isPM is true.

Slug Convention

Adding a New Property (Technical Steps)

  1. Create a Stripe promotion code on coupon W8K6JWCH (LIVE mode — confirm with Zach):
    stripe promotion_codes create \
      -d coupon=W8K6JWCH \
      -d code=NEWPROPERTY \
      -d metadata[property_slug]=newproperty \
      -d metadata[property_name]="New Property Name"
  2. Add to the PROPERTIES object in sv-website/promo/index.html (alphabetical by slug):
    "newproperty": { name: "New Property Name", code: "NEWPROPERTY" },
  3. Commit and push to main. Vercel auto-deploys within ~60 seconds.
  4. Verify: Visit mystoragevalet.com/promo/newproperty and confirm property name, promo code, QR code, and share tools render correctly.

Removing a Property

  1. Remove the entry from the PROPERTIES object in promo/index.html
  2. Deactivate the corresponding Stripe promotion code
  3. Commit and push — the URL will then show the “Page Not Found” fallback

Planned Enhancements

Future phases (documented in sv-docs/specs/promo-landing-pages.md) will add:

Integrations

Stripe

ItemValue
ModeLIVE
Product$299/month subscription
Trial14-day complimentary on all new subscriptions
Setup FeeNone (eliminated)
Webhook Versionv4.1
Events Processedcheckout.session.completed, customer.subscription.created, customer.subscription.updated, customer.subscription.trial_will_end, customer.subscription.deleted, invoice.payment_succeeded, invoice.payment_failed
Idempotency & RecoveryClaim-first dedup; on handler failure the claim is released (release_stripe_webhook_event_v2, migration 20260612233000) so Stripe’s retry re-processes, and an ops alert fires (Jun 14, 2026). A $0-invoice guard skips payment stamping on the trial-activation invoice so trial members are not mis-marked as paid.
Ops AlertsfireOpsAlert on payment failure, membership cancellation (customer.subscription.deleted), and unresolved-profile billing events (Jun 15, 2026).
Duplicate-Bill GuardAn existing member re-running the unauthenticated signup is blocked (HTTP 409) before a second subscription is created; a belt-and-suspenders check in the webhook alerts ops if one ever slips through (B1, Jun 15, 2026).

Google Calendar (Native Scheduling — LIVE)

The live booking layer on the customer domain since Jun 10, 2026 (DEC-0001 executed). Booking confirm creates the calendar event; cancel/revert deletes it. Custody never mutates without a calendar event (block-on-outage).

ItemValue
Calendar“SV Bookings” (Google, founder-owned)
Service Accountsv-calendar@sv-scheduling.iam.gserviceaccount.com
SecretsGOOGLE_SERVICE_ACCOUNT_JSON + GOOGLE_CALENDAR_ID (Supabase secrets)
Availabilityplans.service_windows ∩ 48-hour lead ∩ calendar FreeBusy (via get-available-slots)
Booking HorizonUp to 365 days (month-paged slot picker)
Signal vs TruthThe calendar is signal; the database stays canonical

Calendly (Legacy — v1 read-only path)

Calendly is no longer the live booking layer. The calendly-webhook function remains deployed as a legacy webhook path (kept past the v1 decommission, pending a separate Calendly-retirement decision) with HMAC verification ENABLED (fails closed — keep CALENDLY_WEBHOOK_SIGNING_KEY set in Supabase secrets). Events: invitee.created, invitee.canceled. Webhook only, no outbound API calls.

Supabase Storage

ItemValue
Bucketitem-photos (private)
AccessSigned URLs (1-hour expiry)
Max Size20MB per file
Allowed Typesimage/jpeg, image/png, image/webp
HEIC HandlingiPhone HEIC/HEIF library photos are transcoded to JPEG on-device before upload (imageTranscode.ts, I1 Jun 15, 2026) so they render outside Safari; the bucket allowlist stays JPEG/PNG/WebP (HEIC is never stored).
Max Photos5 per item

Transactional Email (Resend)

All transactional mail renders via the send-email edge function (Resend). Customer-facing types, plus the internal ops_alert:

TypeSent on
welcomeNew member (checkout completed) — carries the magic sign-in link
booking_confirmedBooking confirmed (with .ics + Google Calendar link)
booking_rescheduledBooking moved (monotonic ICS SEQUENCE updates the saved event)
booking_canceledBooking canceled OR last item reverted off a booking; attaches a METHOD:CANCEL invite (E3/E4, Jun 15, 2026)
trial_ending~3 days before trial end; a no-card variant tells card-less members to add one (E6)
membership_canceledMembership ended; branches on items-in-storage to prompt the complimentary final delivery (Jun 15, 2026)
payment_failedFailed charge (member now past_due)
ops_alertInternal — routed to OPS_ALERT_EMAIL (scheduler/calendar/billing anomalies)

Deliverability (Jun 15, 2026): every customer email send is wrapped in EdgeRuntime.waitUntil across all 7 sending functions, so the Supabase isolate cannot freeze before the Resend POST completes (previously these fire-and-forget sends could silently never deliver). send-email also fires an ops_alert when a customer send returns non-200. Booking emails + their ICS now include the service address (LOCATION).

Website Analytics

GA4 (G-9SBYJ8QEC1) and Vercel Web Analytics Plus ($10/month) run on all sv-website pages. GA4 handles behavioral analytics, acquisition channels, and UTM attribution. Vercel handles custom event tracking and privacy-friendly visitor counts. Vercel Speed Insights tracks Core Web Vitals.

Full analytics documentation → — configuration, internal traffic filtering, custom events, UTM attribution, and GA4 MCP tools.

HubSpot CRM

Added Mar 2026. Partnership sales pipeline management โ€” tracks prospective property partners from research through signed partnership. Separate from product infrastructure (Supabase, Stripe, Google Calendar handle customer-facing operations).

ItemValue
Account ID245438948
PlanFree (Sales Hub)
Pipeline“Property Partnerships” (internal name: default)
Records~225+ deals (prospective partners); company/contact counts grow with ongoing enrichment — query HubSpot live for current totals
Chrome ExtensionHubSpot Sales for Gmail (installed Mar 8, 2026)
Email IntegrationGmail — emails sent from Gmail, tracked/logged by HubSpot
Claude Code AccessHubSpot MCP tools (mcp__claude_ai_HubSpot__*)

Pipeline Stages

StageInternal IDProbability
Research331310254810%
Outreach Sent331310254920%
Follow-up331310255030%
Meeting Scheduled331310255150%
Negotiation331310255275%
Signed Partnershipclosedwon100%
Not Interestedclosedlost0%

Custom Deal Properties

property_address, unit_count, property_type, city, outreach_status, data_source, date_sent, followup_date, response_date, promo_code, promo_url, activity_log

Scope & Boundaries

E-Signature & Contracts (Jun 2026)

Go-to stack for generating and sending Storage Valet agreements (partners, customers, vendors). Decided Jun 12, 2026: Google Workspace eSignature is primary; PandaDoc Free eSign is the backup for recipient preference or outages. No additional paid tool. Full step-by-step workflow → Legal § Contracts & E-Signature.

ItemValue
PrimaryGoogle Workspace eSignature — included in the Business Standard plan ($0 incremental); 200 requests/user/month; up to 10 signers/doc; works on Google Docs and Drive PDFs; recipients need no Google account; auto-generates signed PDF + audit trail
BackupPandaDoc Free eSign (zach@mystoragevalet.com) — 60 sends/year, 2 recipients/doc, 5 templates, no integrations, no hyperlinks inside sent docs
Workflow homeGoogle Drive → Storage Valet — Contracts/ (01 Templates / 02 Out for Signature / 03 Executed; see its _README.txt for the step-by-step)
Single-archive ruleEvery executed contract is filed in 03 Executed regardless of which tool collected the signature
Where it livesOpen the PDF/Doc in Drive preview → Request eSignature toolbar button (or the ⋮ menu). NOT in Drive’s right-click “Open with” menu. Verified in-product Jun 12, 2026 on the zach@ account — no admin enablement was needed

SEO & Performance (Feb 2026)

ItemDetail
JSON-LD Structured DataOrganization schema on all 6 main pages; LocalBusiness on index; FAQPage on faq. Enables Google rich snippets.
Canonical URLs<link rel="canonical"> on all 6 main pages. No trailing slash (matches cleanUrls: true).
Cache Headers (vercel.json)Images: 1 year immutable. CSS: max-age=0, must-revalidate (browser always revalidates; Vercel returns 304 if unchanged). Favicon: 1 year immutable.
WCAG Text ContrastFull-opacity Mist (#8BA4A8) on Abyss/Midnight in dark mode (6.9–7.4:1 AA). Full-opacity Storm (#3D4F54) on Cloud/White in light mode (7.9–8.6:1 AAA). Opacity-based secondary text tokens are prohibited system-wide — see Brand §08.
Skip-to-ContentVisually-hidden skip link on all 6 main pages (WCAG 2.4.1 Level A).
robots.txtPermits all crawlers. Points to /sitemap.xml.
sitemap.xmlLists all 8 HTML pages with <priority> and <lastmod> dates. Auto-deployed via Vercel.
Heading HierarchyFooter headings converted from <h4>/<h5> to <p class="footer-heading"> on all pages. Eliminates skipped heading levels flagged by Lighthouse without changing visual appearance.
Main LandmarkAll subpages (terms, privacy, faq, testimonials, partnerships, signup/*) wrapped in <main> element. Landing page already had one. Enables screen reader "skip to main content" navigation.
Footer ContrastFooter uses Mist (#8BA4A8) text on Midnight (#0A1628) background — 6.9:1 AA. All text/link contrast ratios verified against the Operation Midnight Teal accessibility table (Brand §08).
Lighthouse ScoresAccessibility 100/100 on all pages (up from 93 landing, 91 partnerships). Best Practices 100/100 after adding favicon.ico.

Security

Authentication

Row Level Security (RLS)

All customer tables are protected by RLS:

Billing Protection

Billing fields on customer_profile are protected:

Secrets Management

Policy (Non-Negotiable)

Never paste secrets into chat, docs, or commits. Never ask a human to paste secrets into chat. 1Password CLI (op) is the approved source of truth for all credentials.

Secret Inventory

Secret1Password ItemFieldConsumers
Supabase DB PasswordSupabase ProductionpasswordLocal psql only
Stripe Live Secret KeyStripe Livesecret_keySupabase secrets (STRIPE_SECRET_KEY), local Stripe CLI
Stripe Webhook SecretStripe Livewebhook_secretSupabase secrets (STRIPE_WEBHOOK_SECRET)
Resend API KeyResend APIcredentialSupabase secrets (RESEND_API_KEY); Resend MCP (via resend-mcp-launcher.sh, 1Password-backed)
GA4 OAuth Client IDGoogle Cloud - GA4 Analytics MCPclient_idGA4 MCP (via ga4-mcp-launcher.sh; ADC at ~/.config/gcloud/application_default_credentials.json)
Calendly PATCalendlyPATCalendly MCP (via calendly-mcp-launcher.sh, 1Password-backed); manual API calls
Calendly Webhook Signing KeyCalendlywebhook_signing_keySupabase secrets (CALENDLY_WEBHOOK_SIGNING_KEY)
Supabase Service Role KeySupabase Productionservice_role_keySupabase-managed; used by all edge functions

Approved Patterns

# Fetch a secret (never prints to terminal)
OP_BIOMETRIC_UNLOCK_ENABLED=true op read 'op://Storage Valet/Stripe Live/secret_key'

# Push to Supabase without exposing the value
OP_BIOMETRIC_UNLOCK_ENABLED=true op read 'op://Storage Valet/Stripe Live/secret_key' \
  | xargs -I {} supabase secrets set STRIPE_SECRET_KEY="{}" --project-ref gmjucacmbrumncfnnhua

# Shell env vars โ€” lazy-loaded via sv-env function (run once per session)
# Secrets are NOT resolved at shell startup to avoid macOS permission popups
sv-env         # loads SUPABASE_ACCESS_TOKEN, STRIPE_SECRET_KEY, RESEND_API_KEY
               # also caches to ~/.sv-session (chmod 600) for sub-agent inheritance
               # session file PERSISTS across shells; run sv-env-clear to wipe manually

Never Do This

Never run secrets inline in commands (e.g., PGPASSWORD="actual_password" psql ...). Claude Code's permission caching stores the literal command string, persisting the secret to disk. Always use op read or $ENV_VAR references instead.

Rotation Procedures

  1. Generate/rotate the credential in the service's dashboard
  2. Update the value in 1Password (Storage Valet vault)
  3. If the secret is in Supabase secrets, push via: op read '...' | xargs -I {} supabase secrets set KEY="{}"
  4. Verify the dependent service still works (e.g., test checkout, test email)

Deferred: Supabase service role key rotation. High blast radius (breaks all 16 edge functions). Requires planned maintenance window. See DEC-011.

Tripwire (Automated Detection)

sv-final-audit.sh includes a secrets tripwire that scans ~/.zshrc, ~/.claude/settings.json, and ~/.claude/settings.local.json for plaintext credential patterns at the end of every session. It also flags any .env files in ~/code/ repos. Additionally, the audit script includes a GitHub hygiene layer that detects stale PRs (open >7 days) and orphan remote branches (>30 days with no associated PR) across all 7 repositories.

Deployment

Portal (Vercel)

cd ~/code/sv-portal-2026
npm run build        # tsc -b && vite build โ€” must pass before any push
vercel               # Preview deploy (NOT --prod)

Both portal projects auto-deploy from their GitHub main branches; the customer domain has pointed at sv-portal-2026 since Jun 10, 2026. AI commits, pushes, and merges PRs to main directly to ship โ€” no manual promotion step. Do not run vercel --prod directly. Rollback = re-point the domain back to sv-portal (seconds).

PR validation (sv-portal): GitHub Actions workflow (.github/workflows/pr-validation.yml) runs npm run typecheck and npm run build on all PRs to main. Both must pass before merge.

Edge Functions

cd ~/code/sv-edge
./scripts/deploy-customer-facing.sh   # Deploy all 14 customer-facing functions
./scripts/smoke-edge.sh               # Post-deploy smoke test
# OR manually:
supabase functions deploy <function-name> --no-verify-jwt

Always run ./scripts/check-deps.sh before deploying. Deploy ALL customer-facing functions together to avoid version drift.

Database Migrations

cd ~/code/sv-db
supabase db push --linked

Requires: Docker Desktop running

Git Configuration

SettingValue
Author Emailzach@mystoragevalet.com
Author NameZachary Brown

Use only real, verified email addresses. Fabricated emails cause Vercel permission failures.

Architecture Constraints (Non-Negotiable)

  1. 6 customer surfaces only: Login, Dashboard, Items + Add Item, Item Detail, Booking, Account
  2. Staff routes live in the v2 portal’s /ops surface (portal.mystoragevalet.com/ops), staff-gated (is_staff()) and AAL2-stepped-up
  3. Supabase backend only โ€” no custom API servers
  4. Stripe Hosted flows only โ€” no custom card UI
  5. Single pricing tier: $299/month
  6. Magic links only โ€” no password auth
  7. Portal is authentication-only โ€” account creation happens exclusively through the website registration flow (Stripe Checkout). The portal login does not create new users.
  8. RLS on all tables โ€” zero cross-tenant access
  9. Private storage bucket โ€” signed URLs, 1h expiry

Architecture Notes

Insurance Coverage

Included insurance coverage ($2,000 per customer) is enforced server-side via the v_user_insurance database view. All portal UI elements (coverage progress bar, remaining coverage text) derive their values from this view; no insurance limits are hardcoded in the frontend.

Common Issues & Solutions

IssueSolution
Webhook 401Deploy with --no-verify-jwt
CORS blocking POST (OPTIONS 200, POST never fires)Ensure Access-Control-Allow-Headers includes apikey, x-client-info alongside authorization, content-type
Edge function auth 401 despite valid JWTUse getUser(token) with explicit token arg, not getUser() on a stateless client
Migration not appliedRun supabase db push --linked
Docker not runningStart Docker Desktop
RLS blocking queryCheck user_id matches auth.uid()
Preview auth redirects failSupabase Auth requires preview domains to be allowlisted in URL Configuration (Redirect URLs). If not allowlisted, magic links may redirect to production or fail.
React Query key mismatchBookings list cache uses query key ['bookings-list']. Do not invalidate ['bookings'] expecting it to refresh the dashboard; ensure key alignment.

Platform Operations Toolkit

A tiered toolkit for session integrity, repository hygiene, and infrastructure observability. Introduced March 2026 after an orphaned PR went undetected for 35 days. See DEC-017 for rationale.

Commands

CommandPurposeWhen to Use
sv-audit Session integrity and repo hygiene detection. Checks git status, stale PRs (>7d), orphan branches (>30d), secrets tripwire, root directory policing, leaked code-sign clone detection (apps like Chrome that crash-loop and accumulate temp copies of themselves), 1Password socket cleanup. Non-destructive. End of every session (mandatory)
sv-hygiene Deletes remote branches already merged into main. Never deletes unmerged work. Weekly, or after audit flags stale branches
sv-health Platform observability: Supabase migration drift, Vercel deployment failures, GitHub Actions failures, Stripe webhook backlog, and schema drift (agent/runbook example SQL checked against live tables/enums). DB reads run through supabase db query --linked (Management API) โ€” no psql/timeout/1Password, so they work on any machine or IPv4-only network. Weekly, or when verifying production health
sv-check Unified command: runs audit, auto-triggers hygiene if ATTENTION, runs health checks, then the Sales-Engine smoke test. The "is everything OK?" command
sales-engine-check.sh Non-destructive smoke test of the Sales-Engine across every layer: data source-of-truth + drift, asset generation + QR decode, Google Drive distribution, send-pipeline safety gates (no email sent), and live promo-page reachability. Standalone: bash ~/Documents/storagevalet/Marketing/Collateral/promo-cards-v6/sales-engine-check.sh; also runs automatically inside sv-check. After changing the Sales-Engine; runs in every sv-check

All Shell Shortcuts

Defined in ~/.zshrc. Run sv-help to see this list in the terminal.

CommandWhat It Does
sv <repo>Navigate to a repo (e.g., sv portal)
sv-statusGit status across all 7 repos (SV_REPOS)
sv-pullPull latest across all 7 repos (SV_REPOS)
sv-auditRun the end-of-session audit script
sv-hygieneWeekly repo cleanup (merged branches)
sv-healthPlatform health check (migrations, deploys, webhooks, schema drift)
sv-checkFull platform check (audit + hygiene + health)
sv-remotePre-flight checks for remote Claude Code sessions (1Password unlock, secrets, Docker, 1Password biometric agent socket for git signing, GitHub CLI)
sv-issuesOpen GitHub issues across all repos
sv-envLoad secrets from 1Password (cached for sub-agents)
sv-env-clearClear cached secrets and remove session file
sv-helpShow all available commands
gt-helpShow Ghostty keyboard shortcuts and tips
title <name>Set iTerm2 tab title (no args = reset to auto directory name)
stripeAuthenticated via STRIPE_API_KEY env var (from sv-env); auto-detects live mode. For test mode: env -u STRIPE_API_KEY stripe <cmd>.

iTerm2 Tab Auto-Naming

Tabs automatically show the current directory name via a precmd hook in ~/.zshrc. Use title "My Topic" to set a custom title, or title (no args) to reset to auto. Claude Code also sets the tab title to the chat name when running.

Setup (one-time per machine): Disable custom tab titles in iTerm2’s plist (quit iTerm2 first, run via Terminal.app), then add the _update_tab_title precmd function and title() helper to ~/.zshrc. Both machines were configured March 2026.

Ghostty Config Sync

Ghostty configuration lives in iCloud at ~/Documents/storagevalet/Technology/Ghostty/config and is symlinked to ~/.config/ghostty/config on each machine.

1Password SSH Agent

The 1Password SSH agent is configured on both machines for any SSH-based operations (ssh to other hosts, SSH-backed git clones, etc.). Git commit signing is intentionally disabled — 1Password’s signing-authorization prompts appear on the Mac Studio screen and cannot be answered when working remotely via the Claude app’s code tab. Sole-developer context plus private repos means signing has no practical benefit to offset that friction.

FilePurpose
~/.zshrcExports SSH_AUTH_SOCK pointing to the 1Password agent socket
~/.ssh/configSets IdentityAgent to the same socket for all hosts
~/.config/1Password/ssh/agent.tomlTells the 1Password SSH agent which key to serve (required — auto-discovery is unreliable)

SSH_AUTH_SOCK (in ~/.zshrc):

export SSH_AUTH_SOCK="$HOME/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"

agent.toml (in ~/.config/1Password/ssh/):

[[ssh-keys]]
item = "Github SSH Key (SV)"
vault = "Storage Valet"

Key details: The SSH key is a single ed25519 key named “Github SSH Key (SV)” stored in the Storage Valet vault. Both machines share the same key via 1Password vault sync. The key is registered on GitHub for authentication on any SSH-based git remotes.

Script Locations

All scripts are canonical in ~/code/sv-docs/scripts/ (git-tracked). A convenience symlink at ~/code/sv-final-audit.sh points to the canonical audit script for manual use, but no script depends on this symlink.

ScriptCanonical Path
Session audit~/code/sv-docs/scripts/sv-final-audit.sh
Repo hygiene~/code/sv-docs/scripts/repo-hygiene.sh
Platform health~/code/sv-docs/scripts/platform-health-check.sh
Unified check~/code/sv-docs/scripts/sv-check.sh

Design Principles

Developer Setup

Code Repositories

~/code/
โ”œโ”€โ”€ sv-portal-2026/  # LIVE customer portal (Vite 8 / React 19 / Tailwind 4) โ€” cut over Jun 10, 2026
โ”œโ”€โ”€ sv-portal/       # v1 portal (Vite 5 / React 18 / Tailwind 3) โ€” DECOMMISSIONED + archived Jun 29, 2026
โ”œโ”€โ”€ sv-edge/         # Supabase Edge Functions (Deno/TypeScript)
โ”œโ”€โ”€ sv-db/           # Database migrations (SQL)
โ”œโ”€โ”€ sv-docs/         # Operational scripts, runbooks, and archive
โ”œโ”€โ”€ sv-website/      # Landing page (www.mystoragevalet.com)
โ””โ”€โ”€ sv-wiki/         # Internal wiki (wiki.mystoragevalet.com)

Sync Strategy

Mac Studio (primary): iCloud Desktop & Documents sync ON, all files stored locally.
MacBook Air (secondary): iCloud sync ON + "Optimize Mac Storage" ON (auto-offloads when space is tight).
Dropbox: Redundant โ€” subscription lapses ~Sept 2026. Not used for active files.

File organization (Jun 2026): ~/Documents has two domains โ€” storagevalet/ (all business files) and personal/ โ€” documented on disk in ~/Documents/_README.md. Machine tooling must stay at storagevalet/Technology/{Brewfile, Shell/, Ghostty/, Raycast/}: Raycast script registration, the Ghostty config symlink, and sv-audit drift checks hard-code these paths on both machines. Relocating them breaks tooling everywhere (happened Jun 10, 2026 via an automated Documents cleanup; restored same day).

New Machine Setup

When setting up Claude Code on a new machine:

  1. Ensure iCloud Desktop & Documents sync is enabled (System Settings โ†’ Apple ID โ†’ iCloud โ†’ iCloud Drive)
  2. Create symlinks:
    ln -sf ~/Documents/storagevalet/Technology/Reference/CLAUDE.md ~/.claude/CLAUDE.md
    ln -sf ~/Documents/storagevalet/Technology/Reference/code-CLAUDE.md ~/code/CLAUDE.md
  3. Clone repos to ~/code/: sv-portal, sv-portal-2026, sv-edge, sv-db, sv-docs, sv-website, sv-wiki
  4. Install Node.js via Homebrew: brew install node@24 (do NOT use .pkg installer)
  5. Install CLI tools: brew install supabase gh stripe deno pipx
  6. Install Homebrew-managed casks: brew install --cask 1password-cli appcleaner bettermouse claude-code@latest cleanshot codex cursor font-jetbrains-mono framer gcloud-cli ghostty hazel loom miro obsidian rectangle-pro spotify superwhisper textexpander typora visual-studio-code whimsical wispr-flow
    Note: claude-code cask is used on Mac Studio. On MacBook Air, Claude Code is installed via the native installer (claude install, installs to ~/.local/bin/claude). Do NOT install via npm — the npm package @anthropic-ai/claude-code is deprecated in favor of native installers.
  7. Install remaining apps directly: Docker Desktop (required for Supabase CLI), 1Password (biometric agent for op CLI), iTerm2 (custom Dock icon; has reliable built-in updater), Google Chrome, Google Drive — these must NOT be installed via Homebrew as replacement can break system integrations
  8. Run op signin to authenticate 1Password

App Update Strategy (as of March 2026)

Desktop apps are managed in three tiers:

  1. Homebrew casks (26 packages): Updated via brew upgrade --greedy. The --greedy flag is required because most casks are marked auto_updates, which plain brew upgrade skips. Includes desktop apps, CLI tools, and fonts.
  2. App Store apps: Updated via mas upgrade (requires password in terminal).
  3. Direct-install apps (1Password, Docker, iTerm2, Google Chrome, Google Drive): Self-update or update via their own mechanisms. These are excluded from Homebrew to avoid breaking system-level integrations (biometric auth, Docker VM, Drive sync daemon).

The Raycast script SV Update All (sv-update-all.sh) is the all-in-one daily maintenance command. It runs Homebrew (--greedy + cleanup), App Store checks, Docker cleanup (docker system prune -f, guarded), and Claude Code plugin updates. The npm section was removed in March 2026 after Claude Code migrated to native installers. This script replaces the need for the separate sv-brew shell function.

The Raycast snippet SV Platform โ€” Daily Update & Maintenance (keyword ;update, canonical JSON at Technology/Raycast/Snippets/update_raycast-snippet.json) is the Claude Code counterpart: pasted into a fresh session, it drives the full daily routine โ€” tooling updates (Homebrew, npm globals, plugins, App Store), MCP/connector health (claude mcp list, all must show Connected), platform health (sv-health), and stale-session cleanup โ€” reporting one compact table. Note for agents: the plugin-update loop must pipe through while read because zsh does not word-split unquoted variables.

A shared Brewfile at ~/Documents/storagevalet/Technology/Brewfile (iCloud-synced) is the canonical source for all Homebrew-managed packages. On a new machine, run brew bundle install --file=~/Documents/storagevalet/Technology/Brewfile then skip machine-specific packages as needed (see per-machine notes above).

Machine Topology & Machine-Aware Maintenance (single source of truth)

Why this exists: the MacBook Air is disk/RAM-constrained, so apps it doesn’t use locally get removed. The old uniform maintenance routine assumed both machines were identical, so every removal broke it. Per-machine divergence is now declared data, not implicit state.

  • Mac Studio = canonical dev environment (always-on, caffeinated, RC host). Runs the FULL daily SV maintenance protocol.
  • MacBook Air = thin-ish client; dev work happens via RC into the Studio, with light local upkeep only. Aggressive local app removal is expected and fine.
  • Selector: ~/.sv-machine (local, non-synced, studio|mba) — deliberately a local marker, not the hostname, because both machines report hostname Mac.
  • Data: ~/Documents/storagevalet/Technology/machine-profiles.md (iCloud, shared). Read both before treating any absence as a problem.
  • Intentionally-absent tools are intentional — do NOT reinstall or flag as drift. MBA: Docker (reinstall on-demand, only where a migration runs) + GitHub Desktop. Studio: GitHub Desktop (removed Jun 29, 2026; redundant with Claude Code, Codex, gh CLI, web UI). Docker IS present on the Studio (the dev/migration machine) — keep it.

The ;update routine now carries a Part 0: Machine awareness preamble: read the selector + profile first, never flag declared-absent tools, run only light upkeep on a thin client, and never let one failed formula abort the whole brew upgrade batch.

Stripe CLI = homebrew-core stripe-cli on BOTH machines. The legacy stripe/stripe-cli tap was removed Jun 29, 2026 after a completion-file collision aborted a --greedy upgrade (silently blocking every step queued behind it). The Brewfile no longer taps it — do not re-add the tap.

Portability Rule

~/.claude/CLAUDE.md must never contain absolute paths starting with /Users/ or /Library/. Use ~/ and symlink resolution instead. Both CLAUDE.md files are iCloud-synced via symlinks:

  • ~/.claude/CLAUDE.md โ†’ ~/Documents/storagevalet/Technology/Reference/CLAUDE.md (global context)
  • ~/code/CLAUDE.md โ†’ ~/Documents/storagevalet/Technology/Reference/code-CLAUDE.md (cross-repo session startup)

Session Startup Checklist

Run these steps at the beginning of each Claude Code session (human or agent):

  1. Check for CLAUDE.md sync conflicts:
    CLAUDE_DIR="$(cd "$(dirname "$(readlink ~/.claude/CLAUDE.md || echo ~/.claude/CLAUDE.md)")" && pwd)"
    ls "$CLAUDE_DIR" 2>/dev/null | grep -i conflict && echo "CLAUDE.md CONFLICT โ€” resolve before continuing"
  2. Pull latest for all repos:
    for repo in sv-portal sv-edge sv-db sv-docs sv-website sv-wiki; do (cd ~/code/$repo && git pull); done
  3. Verify migrations: supabase migration list --linked
  4. Read context: ~/.claude/CLAUDE.md and ~/code/CLAUDE.md
  5. Docker Desktop must be running for Supabase CLI

Session End Protocol

  1. Run sv-audit (or sv-check for full platform verification) โ€” do not declare clean until Audit: CLEAN
  2. If the audit flags GitHub hygiene issues (stale PRs, orphan branches): review and resolve
  3. If session was significant (deploy, E2E verification, architecture decision): update relevant SV-Wiki pages and commit to sv-wiki repo

See Platform Operations Toolkit for the full command reference.