Technology
Architecture, repositories, deployment, and integrations
Last updated: Jul 1, 2026
On This Page
Architecture Overview
Tech Stack
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | React + Vite + TypeScript | Customer portal |
| Backend | Supabase (PostgreSQL + Edge Functions) | Database, auth, serverless |
| Hosting | Vercel | Frontend deployment |
| Payments | Stripe | Billing, subscriptions |
| Scheduling | Google Calendar (native) | Booking layer — service account writes to “SV Bookings” |
| CRM | HubSpot (Free) | Partnership sales pipeline |
| Auth | Supabase Auth (Magic Links) | Passwordless authentication |
| Storage | Supabase Storage | Item photos |
Infrastructure
| Service | Details |
|---|---|
| Supabase Project | gmjucacmbrumncfnnhua |
| Supabase URL | https://gmjucacmbrumncfnnhua.supabase.co |
| Region | us-east-1 |
| Database | PostgreSQL 17.6.1 |
| Vercel Team | StorageValet |
Vercel Projects
| Project | URL | Repo |
|---|---|---|
| sv-website | www.mystoragevalet.com | sv-website |
| sv-portal-2026 | portal.mystoragevalet.com (cut over Jun 10, 2026) | sv-portal-2026 |
| sv-portal | sv-portal-gamma.vercel.app (v1 — decommissioned Jun 29, 2026; retired placeholder, repo archived) | sv-portal (archived) |
| sv-wiki | wiki.mystoragevalet.com | sv-wiki |
Development Tools
| Category | Tools |
|---|---|
| Runtime | Node.js 24.14.0 LTS (Homebrew node@24, native ARM64) — npm 11.11.0 bundled |
| AI Development | Claude Code (native installer — Homebrew cask on Mac Studio, ~/.local/bin on MacBook Air) + plugins |
| Database | Supabase CLI, Supabase MCP |
| Hosting | Vercel CLI |
| Payments | Stripe CLI (STRIPE_API_KEY from sv-env auto-detects live mode; use env -u STRIPE_API_KEY stripe for test mode) |
| Analytics | gcloud CLI (Homebrew cask gcloud-cli) — Application Default Credentials for GA4 MCP |
| Python | pipx (Homebrew formula pipx) — isolated Python app runner for GA4 MCP server |
| Secrets | 1Password CLI |
| Terminal | iTerm2 + Ghostty (Ghostty is the default terminal for Claude Code work) |
| Workflow | Raycast (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)
| Server | Tool Prefix | Purpose | Auth Method |
|---|---|---|---|
| Supabase | mcp__supabase__* | Direct database access, schema exploration, SQL execution | OAuth (remote HTTP) |
| Calendly | mcp__calendly__* | List/cancel events, view invitees, manage scheduling | PAT from 1Password via launcher script |
| Resend | mcp__resend__* | Send/manage emails, contacts, domains, broadcasts — debug delivery issues | API key from 1Password via launcher script |
| GA4 Analytics | mcp__analytics-mcp__* | Query website traffic, run reports, realtime visitors — read-only | Application Default Credentials via gcloud |
| Bear Notes | mcp__bear-notes__* | Search, read, create, edit, and tag Bear notes — no auth required | Local 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)
| Connector | Tool Prefix | Purpose |
|---|---|---|
| Canva | mcp__claude_ai_Canva__* | Design creation and management |
| Excalidraw | mcp__claude_ai_Excalidraw__* | Diagramming and whiteboarding |
| GitHub | mcp__claude_ai_GitHub_MCP__* | Repos, issues, PRs, releases, code search |
| Gmail | mcp__claude_ai_Gmail__* | Read/draft/search email |
| Google Calendar | mcp__claude_ai_Google_Calendar__* | Event management, free time, scheduling |
| Google Drive | mcp__claude_ai_Google_Drive__* | Read/search/share Drive files (Docs, Sheets, etc.) |
| HubSpot | mcp__claude_ai_HubSpot__* | CRM pipeline management (sales only) |
| Stripe | mcp__claude_ai_Stripe__* | Customers, subscriptions, invoices, products |
| Vercel | mcp__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)
| Server | Tool Prefix | Purpose |
|---|---|---|
| Context7 | mcp__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
| Area | Skills |
|---|---|
| Technical / portal | sv-portal-dev, sv-promo-pages, sv-web-design, sv-email-design |
| Brand & content | sv-brand-voice, sv-copywriting, sv-cro, sv-seo-content, sv-social-media, sv-visual-design |
| Sales & partnerships | sv-sales-email, sv-sales-enablement, sv-partnership-outreach, sv-crm-pipeline, sv-meeting-prep, sv-competitive-intel |
| Growth | sv-growth-experiments, sv-referral-program, sv-google-ads, sv-facebook-ads |
| Finance & strategy | sv-financial-model, sv-investor-materials, sv-quarterly-review, sv-data-analyzer |
| Ops / legal / CX | sv-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.
| Component | Type | Why custom / what to watch for | Last reviewed |
|---|---|---|---|
Calendly MCP (mcp__calendly__*) | Custom — launcher + PAT | Calendly 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 key | Superseded: 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 + ADC | Still 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
| Method | Use for |
|---|---|
/codex:rescue | Explicitly hand a problem to Codex — debugging, a fix request, or deeper root-cause investigation |
codex-rescue subagent | Claude 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:setup | Verify the runtime is ready. --enable-review-gate forces a Codex review before each stop (off by default) |
What Codex contributes
- Rescue mode — independent debugging of a stuck problem
- Adversarial code review — a different model architecture examining the same code
- Second opinions — cross-model validation of an approach before committing to it
- Parallel work — background Codex tasks while Claude keeps assisting
Setup & key facts
| Marketplace | openai/codex-plugin-cc (GitHub-verified OpenAI org) |
| Plugin | codex@openai-codex v1.0.4 |
| Runtime | Local Codex CLI, authenticated via ChatGPT login; the shared runtime starts on demand on the first review/task |
| Data boundary | Code and context are sent to OpenAI whenever Codex is invoked. Approved for SV code. Never route secrets (op / .sv-session values) through it. |
| Machines | Installed 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
| Repo | Purpose | URL |
|---|---|---|
sv-website | Marketing site, signup flow, 70 promo pages | www.mystoragevalet.com |
sv-wiki | Internal 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
- Live since: Jun 10, 2026 — portal.mystoragevalet.com serves the
sv-portal-2026codebase (DEC-020 domain re-point) - Tech: Vite 8 + React 19 + TypeScript (strict) + Tailwind 4 — v6 Operation Midnight Teal tokens
- Auth: Supabase magic links (email-based, no passwords)
- Write path:
fn_v2_*SECURITY DEFINER RPCs only (sole exception: two-step photo upload) - Scheduling: native Google Calendar — availability via
get-available-slots; bookings up to 365 days out (month-paged picker) - State: TanStack Query for server state; React Router 7
Customer Surfaces (6)
| Surface | Purpose |
|---|---|
| Login | Magic link authentication |
| Dashboard | Inventory overview + booking management |
| Items + Add Item | Item catalog; photos-first item creation |
| Item Detail | Single-item view — schedule, reschedule, revert, remove |
| Booking | Items-first scheduling flow (native Google Calendar slots) |
| Account | Profile, 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
- Runtime: Deno (Supabase Edge Functions)
- Auth: JWT verification + RLS
- Deployment:
supabase functions deploy
Functions (20)
Scheduling & v2 Booking (Google Calendar)
| Function | Purpose | Trigger |
|---|---|---|
get-available-slots | Availability: plan service windows ∩ 48-hour lead ∩ calendar FreeBusy | Portal booking flow |
v2-booking-confirm | Confirm booking; create “SV Bookings” calendar event | Portal |
v2-booking-cancel | Cancel booking; revert items; delete calendar event | Portal |
v2-booking-reschedule | Move booking to a new slot; update calendar event | Portal |
v2-booking-revert | Per-item revert off a scheduled booking | Portal |
Stripe Integration
| Function | Purpose | Trigger |
|---|---|---|
create-checkout-trial | Stripe 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-checkout | DECOMMISSIONED Jun 14, 2026 โ returns HTTP 410 Gone; superseded by create-checkout-trial (legacy setup-fee flow contradicted the no-setup-fee canon). | โ |
create-portal-session | Stripe billing portal | Portal |
stripe-webhook | Process Stripe events | Webhook |
Calendly (Legacy v1 Path)
| Function | Purpose | Trigger |
|---|---|---|
calendly-webhook | Legacy v1 read-only path (HMAC verification ENABLED, fails closed) — not the live booking layer | Webhook |
Booking Operations
| Function | Purpose | Trigger |
|---|---|---|
bookings-list | List all bookings (ops dashboard) | Ops UI |
booking-get | Get single booking with items | Ops UI |
booking-cancel | Cancel booking, revert item statuses | Portal, Ops UI |
complete-service | Mark service completed | Ops UI |
update-booking-items | Modify items on pending booking | Portal |
Customer & Email
| Function | Purpose | Trigger |
|---|---|---|
admin-create-customer | Create customer bypassing Stripe | Ops/admin |
signup-webhook | Pre-register, check service area | Landing page |
partnership-inquiry | Partnership form submissions | Website |
send-email | Transactional emails via Resend | System |
Utility
| Function | Purpose | Trigger |
|---|---|---|
health-check | Returns 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
| Webhook | URL |
|---|---|
| Stripe | https://gmjucacmbrumncfnnhua.supabase.co/functions/v1/stripe-webhook |
| Calendly | https://gmjucacmbrumncfnnhua.supabase.co/functions/v1/calendly-webhook |
Incident Log
| Date | Incident | Root Cause | Resolution |
|---|---|---|---|
| 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:
- Reads the URL slug (e.g.,
beaconfrom/promo/beacon) - Looks it up in a
PROPERTIESobject embedded in the HTML (70 entries) - If found: populates the page with the property name, promo code, share links, and QR code
- If not found: hides the promo content and shows a “Page Not Found” message with a link back to the main site
File Structure
| File | Purpose |
|---|---|
sv-website/promo/index.html | The promo page (single file, all 70 properties — inline CSS + JS) |
sv-website/vercel.json | Rewrite rule: /promo/:code → /promo |
sv-docs/specs/promo-landing-pages.md | Technical implementation spec (future phases) |
sv-docs/guides/promo-landing-pages.md | Comprehensive operational guide |
Design Characteristics
- Brand-consistent: Uses the Operation Midnight Teal palette and the same DM Serif Display + Outfit typography as the rest of the Storage Valet brand system. Dark-first surfaces with Electric Teal accents. Text-only logo (no box icon). See Brand Identity for the full system.
- Mobile-first responsive: Tested at 390px (iPhone), 600px (tablet), and desktop widths. Two-column layouts collapse to single-column on mobile.
- Performance: All styles inline (no external CSS). GPU-accelerated scroll animations (
translate3d,will-changelifecycle management). SVG noise texture instead of heavy images. - Accessibility:
prefers-reduced-motionsupport,focus-visibleoutlines, semantic HTML, sufficient color contrast - Animation:
translate3d(0, 28px, 0),0.8s ease-out, IntersectionObserver withthreshold: 0.1androotMargin: '0px 0px -40px 0px'— matches main site patterns
Analytics Events
Promo pages fire events to both Vercel Web Analytics and GA4:
| Event Name | Trigger | Data |
|---|---|---|
promo_page_view | Page loads with a valid slug | slug, property, code |
promo_share_copy | User clicks “Copy to clipboard” on the share snippet | slug |
promo_link_copy | User clicks the copy icon on the direct link | slug |
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:
| View | URL | Behavior |
|---|---|---|
| Resident (default) | /promo/beacon | Clean page with property name, promo code, and signup CTA. Share tools are hidden. |
| Property Manager | /promo/beacon?pm=true | Adds “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
- Lowercase only, no spaces, no hyphens, no special characters
- Must be unique across all entries in the
PROPERTIESobject - Should be recognizable (e.g.,
beacon,greyson,sable)
Adding a New Property (Technical Steps)
- 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" - Add to the PROPERTIES object in
sv-website/promo/index.html(alphabetical by slug):"newproperty": { name: "New Property Name", code: "NEWPROPERTY" }, - Commit and push to main. Vercel auto-deploys within ~60 seconds.
- Verify: Visit
mystoragevalet.com/promo/newpropertyand confirm property name, promo code, QR code, and share tools render correctly.
Removing a Property
- Remove the entry from the
PROPERTIESobject inpromo/index.html - Deactivate the corresponding Stripe promotion code
- 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:
- Embedded signup form with address fields pre-filled from property data
- Promo code auto-application at Stripe Checkout (no manual entry)
- Database attribution (
promo_codecolumn onpre_customerstable) - Additional analytics events:
promo_signup_submit,promo_checkout_redirect,promo_signup_waitlist
Integrations
Stripe
| Item | Value |
|---|---|
| Mode | LIVE |
| Product | $299/month subscription |
| Trial | 14-day complimentary on all new subscriptions |
| Setup Fee | None (eliminated) |
| Webhook Version | v4.1 |
| Events Processed | checkout.session.completed, customer.subscription.created, customer.subscription.updated, customer.subscription.trial_will_end, customer.subscription.deleted, invoice.payment_succeeded, invoice.payment_failed |
| Idempotency & Recovery | Claim-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 Alerts | fireOpsAlert on payment failure, membership cancellation (customer.subscription.deleted), and unresolved-profile billing events (Jun 15, 2026). |
| Duplicate-Bill Guard | An 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).
| Item | Value |
|---|---|
| Calendar | “SV Bookings” (Google, founder-owned) |
| Service Account | sv-calendar@sv-scheduling.iam.gserviceaccount.com |
| Secrets | GOOGLE_SERVICE_ACCOUNT_JSON + GOOGLE_CALENDAR_ID (Supabase secrets) |
| Availability | plans.service_windows ∩ 48-hour lead ∩ calendar FreeBusy (via get-available-slots) |
| Booking Horizon | Up to 365 days (month-paged slot picker) |
| Signal vs Truth | The 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
| Item | Value |
|---|---|
| Bucket | item-photos (private) |
| Access | Signed URLs (1-hour expiry) |
| Max Size | 20MB per file |
| Allowed Types | image/jpeg, image/png, image/webp |
| HEIC Handling | iPhone 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 Photos | 5 per item |
Transactional Email (Resend)
All transactional mail renders via the send-email edge function (Resend). Customer-facing types, plus the internal ops_alert:
| Type | Sent on |
|---|---|
welcome | New member (checkout completed) — carries the magic sign-in link |
booking_confirmed | Booking confirmed (with .ics + Google Calendar link) |
booking_rescheduled | Booking moved (monotonic ICS SEQUENCE updates the saved event) |
booking_canceled | Booking 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_canceled | Membership ended; branches on items-in-storage to prompt the complimentary final delivery (Jun 15, 2026) |
payment_failed | Failed charge (member now past_due) |
ops_alert | Internal — 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).
| Item | Value |
|---|---|
| Account ID | 245438948 |
| Plan | Free (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 Extension | HubSpot Sales for Gmail (installed Mar 8, 2026) |
| Email Integration | Gmail — emails sent from Gmail, tracked/logged by HubSpot |
| Claude Code Access | HubSpot MCP tools (mcp__claude_ai_HubSpot__*) |
Pipeline Stages
| Stage | Internal ID | Probability |
|---|---|---|
| Research | 3313102548 | 10% |
| Outreach Sent | 3313102549 | 20% |
| Follow-up | 3313102550 | 30% |
| Meeting Scheduled | 3313102551 | 50% |
| Negotiation | 3313102552 | 75% |
| Signed Partnership | closedwon | 100% |
| Not Interested | closedlost | 0% |
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
- HubSpot is sales pipeline ONLY — tracks B2B partnership outreach to property management companies
- Not connected to product infrastructure — Stripe handles billing, Supabase handles customer data, Google Calendar handles scheduling
- Promo tracking: 69 deals have
promo_code+promo_urllinking to property-specific promo landing pages - Company associations: deals are progressively linked to their management companies (counts drift with enrichment) — see HubSpot for live association status
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.
| Item | Value |
|---|---|
| Primary | Google 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 |
| Backup | PandaDoc Free eSign (zach@mystoragevalet.com) — 60 sends/year, 2 recipients/doc, 5 templates, no integrations, no hyperlinks inside sent docs |
| Workflow home | Google Drive → Storage Valet — Contracts/ (01 Templates / 02 Out for Signature / 03 Executed; see its _README.txt for the step-by-step) |
| Single-archive rule | Every executed contract is filed in 03 Executed regardless of which tool collected the signature |
| Where it lives | Open 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)
| Item | Detail |
|---|---|
| JSON-LD Structured Data | Organization 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 Contrast | Full-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-Content | Visually-hidden skip link on all 6 main pages (WCAG 2.4.1 Level A). |
| robots.txt | Permits all crawlers. Points to /sitemap.xml. |
| sitemap.xml | Lists all 8 HTML pages with <priority> and <lastmod> dates. Auto-deployed via Vercel. |
| Heading Hierarchy | Footer 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 Landmark | All 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 Contrast | Footer 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 Scores | Accessibility 100/100 on all pages (up from 93 landing, 91 partnerships). Best Practices 100/100 after adding favicon.ico. |
Security
Authentication
- Method: Magic links (email-based, no passwords)
- Provider: Supabase Auth
- Token: JWT with user ID
- Session: Persisted to localStorage; survives new tabs, refresh, and browser restart. Auth guard (
ProtectedRoute) gates on SupabaseINITIAL_SESSIONevent before rendering redirects.
Row Level Security (RLS)
All customer tables are protected by RLS:
- Users can only see/modify their own data
auth.uid()used in all policies- Service role bypasses RLS (edge functions only)
Billing Protection
Billing fields on customer_profile are protected:
- Users CANNOT update
subscription_status,stripe_customer_id,subscription_id - Updates only via SECURITY DEFINER functions from webhooks
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
| Secret | 1Password Item | Field | Consumers |
|---|---|---|---|
| Supabase DB Password | Supabase Production | password | Local psql only |
| Stripe Live Secret Key | Stripe Live | secret_key | Supabase secrets (STRIPE_SECRET_KEY), local Stripe CLI |
| Stripe Webhook Secret | Stripe Live | webhook_secret | Supabase secrets (STRIPE_WEBHOOK_SECRET) |
| Resend API Key | Resend API | credential | Supabase secrets (RESEND_API_KEY); Resend MCP (via resend-mcp-launcher.sh, 1Password-backed) |
| GA4 OAuth Client ID | Google Cloud - GA4 Analytics MCP | client_id | GA4 MCP (via ga4-mcp-launcher.sh; ADC at ~/.config/gcloud/application_default_credentials.json) |
| Calendly PAT | Calendly | PAT | Calendly MCP (via calendly-mcp-launcher.sh, 1Password-backed); manual API calls |
| Calendly Webhook Signing Key | Calendly | webhook_signing_key | Supabase secrets (CALENDLY_WEBHOOK_SIGNING_KEY) |
| Supabase Service Role Key | Supabase Production | service_role_key | Supabase-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
- Generate/rotate the credential in the service's dashboard
- Update the value in 1Password (Storage Valet vault)
- If the secret is in Supabase secrets, push via:
op read '...' | xargs -I {} supabase secrets set KEY="{}" - 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
| Setting | Value |
|---|---|
| Author Email | zach@mystoragevalet.com |
| Author Name | Zachary Brown |
Use only real, verified email addresses. Fabricated emails cause Vercel permission failures.
Architecture Constraints (Non-Negotiable)
- 6 customer surfaces only: Login, Dashboard, Items + Add Item, Item Detail, Booking, Account
- Staff routes live in the v2 portal’s
/opssurface (portal.mystoragevalet.com/ops), staff-gated (is_staff()) and AAL2-stepped-up - Supabase backend only โ no custom API servers
- Stripe Hosted flows only โ no custom card UI
- Single pricing tier: $299/month
- Magic links only โ no password auth
- Portal is authentication-only โ account creation happens exclusively through the website registration flow (Stripe Checkout). The portal login does not create new users.
- RLS on all tables โ zero cross-tenant access
- 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
| Issue | Solution |
|---|---|
| Webhook 401 | Deploy 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 JWT | Use getUser(token) with explicit token arg, not getUser() on a stateless client |
| Migration not applied | Run supabase db push --linked |
| Docker not running | Start Docker Desktop |
| RLS blocking query | Check user_id matches auth.uid() |
| Preview auth redirects fail | Supabase 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 mismatch | Bookings 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
| Command | Purpose | When 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.
| Command | What It Does |
|---|---|
sv <repo> | Navigate to a repo (e.g., sv portal) |
sv-status | Git status across all 7 repos (SV_REPOS) |
sv-pull | Pull latest across all 7 repos (SV_REPOS) |
sv-audit | Run the end-of-session audit script |
sv-hygiene | Weekly repo cleanup (merged branches) |
sv-health | Platform health check (migrations, deploys, webhooks, schema drift) |
sv-check | Full platform check (audit + hygiene + health) |
sv-remote | Pre-flight checks for remote Claude Code sessions (1Password unlock, secrets, Docker, 1Password biometric agent socket for git signing, GitHub CLI) |
sv-issues | Open GitHub issues across all repos |
sv-env | Load secrets from 1Password (cached for sub-agents) |
sv-env-clear | Clear cached secrets and remove session file |
sv-help | Show all available commands |
gt-help | Show Ghostty keyboard shortcuts and tips |
title <name> | Set iTerm2 tab title (no args = reset to auto directory name) |
stripe | Authenticated 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.
| File | Purpose |
|---|---|
~/.zshrc | Exports SSH_AUTH_SOCK pointing to the 1Password agent socket |
~/.ssh/config | Sets IdentityAgent to the same socket for all hosts |
~/.config/1Password/ssh/agent.toml | Tells 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.
| Script | Canonical 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
- Non-destructive audits:
sv-auditreads only (except 1Password socket cleanup). It never modifies git state, GitHub, or infrastructure. - Explicit cleanup:
sv-hygieneis a separate, opt-in command. It only deletes branches confirmed merged into main. - Canonical paths: Scripts reference
~/code/sv-docs/scripts/directly. Symlinks exist for convenience but are not dependencies. - Dual observability: Stale PRs are detected at session start (bootstrap snippet) and session end (audit script).
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:
- Ensure iCloud Desktop & Documents sync is enabled (System Settings โ Apple ID โ iCloud โ iCloud Drive)
- 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
- Clone repos to
~/code/:sv-portal,sv-portal-2026,sv-edge,sv-db,sv-docs,sv-website,sv-wiki - Install Node.js via Homebrew:
brew install node@24(do NOT use .pkg installer) - Install CLI tools:
brew install supabase gh stripe deno pipx - 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-codecask 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-codeis deprecated in favor of native installers. - Install remaining apps directly: Docker Desktop (required for Supabase CLI), 1Password (biometric agent for
opCLI), 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 - Run
op signinto authenticate 1Password
App Update Strategy (as of March 2026)
Desktop apps are managed in three tiers:
- Homebrew casks (26 packages): Updated via
brew upgrade --greedy. The--greedyflag is required because most casks are markedauto_updates, which plainbrew upgradeskips. Includes desktop apps, CLI tools, and fonts. - App Store apps: Updated via
mas upgrade(requires password in terminal). - 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 hostnameMac. - 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,
ghCLI, 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):
- 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"
- 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
- Verify migrations:
supabase migration list --linked - Read context:
~/.claude/CLAUDE.mdand~/code/CLAUDE.md - Docker Desktop must be running for Supabase CLI
Session End Protocol
- Run
sv-audit(orsv-checkfor full platform verification) โ do not declare clean untilAudit: CLEAN - If the audit flags GitHub hygiene issues (stale PRs, orphan branches): review and resolve
- 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.