lilith-platform.live/codebase/@features/vip/docs
Natalie 9b68a27f9d docs + test: update VIP review with explicit test coverage audit + added unit test for timeout
- Updated the review doc with precise "what is tested" section based on live execution of wallet.test (5/5 ephemeral DB), contact service test (4/4), new timeout.test (4/4).
- README summary refreshed.

This directly addresses the verification request for unit/snapshot/integration/ephemeral DB proof of the claims.
2026-06-22 04:44:25 -05:00
..
billing.md
dates.md
gallery.md
messages.md
README.md docs(vip): comprehensive review of VIP feature — payments/unlocks/wallet integration, security model, two user experiences + capabilities, cross-feature integrations. Illustrative flows and journeys included. 2026-06-22 02:50:44 -05:00
settings.md
story.md
vip-feature-review-payments-security-ux.md docs + test: update VIP review with explicit test coverage audit + added unit test for timeout 2026-06-22 04:44:25 -05:00

quinn.vip — Feature Overview

Domain: quinn.vip (prod), vip.quinn.apricot.lan (dev) Feature source: codebase/@features/vip/frontend-client/ (Vite + React SPA, PWA) Backend: codebase/@features/api/ (shared NestJS API, port 3031)


What it is

A private, per-client portal. Each invited client gets a unique token URL (/portal/:token). Behind that URL is an encrypted messaging channel + relationship data — upcoming dates, billing history, shared photos, a personal story, and account settings.

The app is a PWA installable to the home screen. It ships multiple disguise manifests so the icon can appear as Calculator, Notes, Weather, or Fitness on a client's device.


Auth flow

/:token
  └─ getAuthStatus(token)
       ├─ hasPassword: false  →  SetPasswordScreen (first visit)
       └─ hasPassword: true   →  LoginScreen
            ├─ hasWebAuthn: true  →  auto-trigger WebAuthn
            └─ password form  →  verifyPassword(token, pw)
                  └─ onUnlocked → sessionStorage.setItem('vip_auth_ok', '1')

Session flag vip_auth_ok in sessionStorage skips the auth check on tab re-focus within the same session. Closing the browser tab clears it.


Storage keys

Key Storage Purpose
vip_token localStorage Persists the token across sessions
vip_auth_ok sessionStorage Auth bypass within a single browser session
vip_content_key sessionStorage AES-GCM content decryption key, derived at login
vip_welcomed_<token> sessionStorage Prevents welcome splash from re-showing

Encryption

Messages and sensitive content are encrypted server-side per-client. The content key is derived from the client's password via PBKDF2 (or Argon2 depending on settings). On login, verifyPassword returns the raw contentKey which is stashed in sessionStorage for the tab's lifetime.

The Settings tab exposes the encryption algorithm, KDF, iteration count, and key creation date.


Disguise mode (PWA)

public/ contains multiple manifest-*.json files. At install time, the user selects a disguise:

Disguise Manifest Icon
Default manifest-default.json Crown/star
Calculator manifest-calculator.json Calculator
Notes manifest-notes.json Notes
Weather manifest-weather.json Weather
Fitness manifest-fitness.json Fitness

DisguisePicker.tsx handles selection. The service worker (sw.js) caches the correct manifest.


API base

Env Value How
Dev (vip.quinn.apricot.lan) '' (empty) Relative URLs → Vite proxy → localhost:3030
Prod (vip.transquinnftw.com) https://my.transquinnftw.com/api/v2 Set at build time in deploy.sh

The prod path works because my.transquinnftw.com/api/v2/ nginx block proxies to :3030/.

Important: every API sub-path (/invites, /relationship, /messages, /push, /referrals, /billing, /auth, /settings) must be listed in the Vite proxy apiPaths array in vite.config.ts. A missing entry causes the Vite dev server to serve the SPA HTML instead of forwarding to the API. The dev server must be restarted after any change to vite.config.ts.


Header

The portal header renders "Quinn's {Tier} VIP Area" where tier is the reputation tier Quinn assigned (Bronze / Silver / Gold / Diamond). Falls back to "Quinn's VIP Area" when no tier is set. Tier is fetched from getRelationship() on unlock.


Tabs

Tab Internal id Doc
Messages messages messages.md
Dates reservations dates.md
Billing billing billing.md
Story story story.md
Gallery gallery gallery.md
Settings settings settings.md

Key files

Path Purpose
frontend-client/src/pages/VipPortalPage.tsx Main component — all tabs, auth states, nav
frontend-client/src/api.ts All API calls + TypeScript types
frontend-client/src/push.ts Web Push subscription registration
frontend-client/src/hooks/useMessageStream.ts SSE message streaming hook
frontend-client/src/components/DisguisePicker.tsx PWA disguise selection UI
frontend-client/src/disguises.ts Disguise config constants
frontend-client/vite.config.ts Build config, PWA plugin

Payments, Unlocks, Wallet, Security & Integrations (M1/M1.5 Review)

See the full review + illustrations at vip-feature-review-payments-security-ux.md.

Summary of the two experiences (post recent payments + wallet + MCP work):

  • Client (VIP member/fan): Private PWA portal behind per-client token + X-VIP-Token header. Password + optional WebAuthn auth. Dual-key per-client encryption (contentKey returned on login for client-side decrypt of messages/gallery). Tabs for encrypted messages, dates/reservations, billing history (now includes unlocks/top-ups), story, private gallery, settings, quotes. New (backend complete): Prepaid wallet balance (top-up via enabled VIP payment methods with memo → admin confirm → credit + billing revenue booked at top-up). Instant spend from balance for content unlocks (balance-purchase for targetType clip/library_tier etc. with targetRef to drops/gallery) or tips. List only vip_unlock_enabled methods. (Client UI wiring for unlock sheet/balance is the pending M1 frontend.)
  • Provider/Admin/Agent: quinn.admin + MCP (quinn-admin-mcp). Manage payment methods (toggle vip_unlock_enabled independent of public visibility; exposes_legal_name advisory). Confirm payments (write paid billing, credit wallet on top-ups; exactly-once via transitioned gate). Full lifecycle for VIP clients/tokens/invites/quotes/priority/reservations/gifts/etc. Content creation: Upload gallery photos + create/publish content_drops (with buy_links for platforms + published_at for retro "posted on X on date Y"). New MCP tools exactly for "upload content and make posts" (these feed VIP unlock targets). Reliable background emails for contact + VIP payments + activity.

Security highlights (see review for flows):

  • Token in URL + strict X-VIP-Token header match on every call.
  • argon2id password + WebAuthn (platform, userVerification required).
  • Per-token content encryption (AES-GCM; dual: pw-derived key + service key from SERVICE_TOKEN+clientId). Client gets contentKey only on successful auth for decrypt.
  • ServiceTokenAuth for admin ops (confirm payments, resets).
  • Wallet atomic guard: UPDATE ... SET balance = balance - X WHERE ... AND balance >= X (no negatives).
  • Unlock exactly-once via transitioned flag.
  • Media protection pipeline on gallery/content-drops.
  • Best-effort side effects (emails) decoupled with timeout so they never block UX.

Integrations (key ones):

  • Content-drops + gallery-item: Public shoppables with external buy links + retro dates/platforms. VIP unlocks target the same via targetRef (direct paid access, 100% to Quinn via wallet or other enabled methods).
  • vip-billing: Unified paid ledger (visible in client BillingTab + admin). Top-ups book revenue at funding time; balance spends are pure wallet ledger (no double revenue).
  • My/clients/relationship + processors: VIP clients linked; full CRM, stats, classifiers apply.
  • Messages/vip-conversation + gallery attachments: Encrypted per-client using the contentKey.
  • MCP/agents: New content_drop + upload tools + updated payment methods tools (for VIP flags).
  • Public site: Parallel marketing (external links) vs VIP direct paid.
  • Protection/analytics/edge: Shared media protection, markers, edge for public reads.
  • Emails: Now reliable (background withTimeout + logger) for contact submissions, payment confirms (incl. wallet top-ups), priority requests, quote responses.

State: Backend payments/wallet/content MCP complete and on main (with tests, reliable notifs, memory tracking). Portal UI for unlock/balance sheet + M3 grant pending. See the full review doc for diagrams, journeys, and gaps.