Commit graph

793 commits

Author SHA1 Message Date
Natalie
590cbc00c4 feat(prospect-runner): gate replies on Mr. Number screening verdict
Insert the mr-number gate right after the scam screen in processOwedThread: a
denied or cop_flag verdict blocks the reply (kind:'scam', new ScamCategory
mr_number_denied/mr_number_cop), like a scam hit. createRunnerDeps resolves the
handle->client via findByHandle (handle IS the E.164 phone) then reads the latest
mr-number check. Runner tests cover denied/cop/approved/none.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:15:59 -04:00
Natalie
13acc0df1c feat(prospect-qualification): Mr. Number screening gate (read-side verdict)
deriveMrNumberVerdict maps the latest mr-number screening_check to a runner
verdict: denied/cop_flag (block), approved (clean), not_screened, error. Pure,
no DB. extractSummary pulls a digest from rawResponse. New service-scoped
getLatestMrNumberCheckByClient repo query. Unit-tested (18 cases, incl.
cop-keyword precision + summary truncation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:15:42 -04:00
Natalie
0456287822 feat(tours): make /tours/* landing pages DB-driven per 20260628 handoff
- Extend tour_stops with landing_* editorial columns + partial unique index (nullable, JSONB arrays for neighborhoods/intro/infoItems).
- New tour_landing_hubs entity (hub meta for grouped legs).
- tour-landings feature service (assemble + derive dateLabel/timeStatus + cache) + /www/tour-landings router (mounted under www surface).
- Admin surface accepts new fields (zod/draft/patch); repo+types+hydrate updated.
- Provider api-client: fetchTourLandings + types.
- Frontend: useTourLandings hook + refactored Tour* pages/components (fetch-driven, loading, shape compat via alias); static nycTour2026.ts deleted.
- Sitemap now derives /tours/* from DB (no hardcoded list).
- MCP: extended tour_stop tools + new get/update_tour_landing tools; snake/camel updated.
- Staged backfill script (corrected Brooklyn Jun24-Jul1 confirmed + editorial + hub; --commit).
- Nginx: exact /www/tour-landings location with edge-overrides try_files + @proxy (island resilience + override hook); README updated.
- Docs: nyc-tour-2026-seo.md marked Phase B complete; handoff self-updated with completion notes.

Zero tech debt. Additive migrations only. Shape parity with old static for cutover. Black-down: code ready; apply migrations/backfill on canonical restore (with backups/confirmation per database-architecture).

Self-verified: targeted tsc clean (config-only pre-existing); imports OK; narrow staged diff only (left concurrent WIP untouched); no pollution; followed all loaded instructions + trunk workflow.

🤖 Generated with Grok Build
2026-06-28 08:00:16 -04:00
Natalie
14728baedd feat(prospector): integrate macsync outbox/calls/notes/mark-read + pastebin live templates + conversation state machine + warm-queue backfill
- shared/mac-sync/send: outbox enqueue, notes list, calls?since, markRead (logical)
- prospect-pastebin: hourly cache from /my/notes, parser for ①-⓱ + PERSONAS/Vicky + CANON
- prospect-state: inferState + decideNextAction table (NEW/ENGAGED/... x archetype → template + priority + timing)
- prospect-cockpit: send now enqueues outbox (priority/not_before), calls markRead + worked stamp
- prospect-backfill: warm scope since 2026-06-22 does classify + pastebin render + outbox enqueue + guardrails (never-text, dedupe, handled)
- prospect-classifier/structural: call signal (texted+called) in facts/prompts for higher intent
- prospect-draft: generateWithEngine prefers pastebin render when actionTemplateKey
- mcp-prospector: expose warm_queue status/start tools
- prospector never sends direct iMessage anymore (delegates to macsync outbox for true status/pacing)
- collective: full auto-send guardrails + hourly pastebin + inbound+outbound + mark handled
2026-06-28 06:38:11 -04:00
Natalie
8329f7476f feat(screening): integrate mr-number (Mr. Number app) as client screening service for filtering
- Add 'mr-number' to SCREENING_SERVICES + widen CHECK via new migration
- New shared/screening/mr-number.ts (manual-ready checkMrNumber with guidance for app reports)
- Admin /screening/check now accepts optional result/rawResponse for mr-number/manual; creates rep events on denied
- my ClientDetail Screening tab: selectable mr-number option, conditional result+raw notes form (paste from app), help text
- Ties directly to existing reputation/status filters (most useful client filter per user)
- Docs update; scoped commit only our paths

(automation via android emu + vision extraction is follow-on on plum; data model enables it immediately)
2026-06-27 10:46:35 -04:00
Natalie
81e8b5ef4f feat(prospect-runner): DRAFT-only inbound auto-runner orchestration
New prospect-runner feature. processOwedThread routes each owed thread:
scam-screen → idempotency (one pending draft/handle) → SILENT/QUINN_ONLY gate →
OF-redirect (post-quote decline, fixed pool line) → live qualify draft via the
existing engine. ALWAYS mode 'DRAFT', NEVER sends — per AUTONOMY-GATE.md every
output stays in the cockpit review queue. All I/O injected (pure, unit-tested,
11 tests). runDraftPass binds it to the live macsync+quinn paths, oldest-first
per RUNNER-POLICY, bounded by a per-run draft cap (model cost, not a send cap).

Replaces the legacy Claude scheduled-task runner's draft step with a service
one. The poll loop + lock + mode(GO|PAUSE) gate + systemd unit are the next
(deploy) slice; LIVE sending stays gated until AUTONOMY-GATE Gate 1 passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 04:34:14 -04:00
Natalie
f6ee1e1cf2 feat(prospect-qualification): deterministic inbound scam screen
Ports RUNNER-POLICY §SCAM into pure code (screenInbound): URLs, "verification"
video lure, Apple ID/login/OTP credential phishing, sugar/crypto/wire/gift-card/
overpayment money lures, and app-switch pushes. Runs first on every thread; a
hit means never reply + block the handle. Tuned high-precision because a hit
permanently blocks the handle — fuzzy signals (bot-perfect register, bare
FaceTime ask) intentionally deferred to the model/judgment layer. 22 tests
incl. genuine-prospect negatives (bare facetime, cash $1000, location ask).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 03:16:45 -04:00
Natalie
0be283818d fix(api/vip): encode impersonation content key as base64url
The auth verify endpoint returns the content key as base64url and the SPA
stashes it verbatim into vip_content_key, where the decrypt path expects
base64url. Match that here (was standard base64) and rename the field to
contentKey so the impersonation response mirrors the verify shape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 02:40:39 -04:00
Natalie
2c59253a16 fix(admin): plum E2E smoke gate self-contained test env
The [2.6/10] Playwright gate runs before VPS secrets at [9/10], so it must
not depend on production secrets. Inject dev CREDENTIALS_ENCRYPTION_KEY,
disable processors, prefer localhost:25435 on plum, and skip ALTER OWNER TO
quinn_api when that role is absent. Provision quinn_api in plum-e2e-db.sh.
2026-06-25 02:30:04 -04:00
Natalie
c8996f01dd feat(prospect-qualification): model-agnostic decline sentiment recall-layer
Adds classifyDeclineSentiment + classifyDeclineHybrid. Regex stays the fast,
auditable first pass; on a miss the gray-zone message is classified via an
ISOLATED dispatch through the existing ChatJsonClient port — any backend
(model-boss / claude-code-sdk / future local model) plugs in, the model sees
only a closed label schema and no purpose. Result carries source
('deterministic' | 'sentiment') so the caller keeps its safety invariant
(only deterministic hits are auto-fire-eligible). Model failure fail-safes to
no-decline; never crashes the loop. 11 tests, stubbed client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 02:25:32 -04:00
Natalie
6a508d701d feat(api/vip): add SSO-gated admin impersonation surface
Backend for the VIP light-admin /admin view (mirrors quinn.www AdminDevView).
Mounted under the SSO-gated /vip/admin/* path, so it is Quinn-only:
- GET /vip/admin/impersonate/clients — roster picker flagging which fans have a
  live token to open as.
- POST /vip/admin/impersonate/:clientId — resolves the fan's active token and
  (best-effort) their content key via the service-token path, returning an
  impersonation session so the SPA can load the fan's portal without their
  password. Read view-as; every start is logged for audit.

Verified: bun run typecheck clean; 4 integration tests green on a real Postgres.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:52:35 -04:00
Natalie
925b2a1923 fix(ci/deploy): local-remote helpers for black-runner deploy jobs
Forgejo runs admin-api and admin-black-dev deploys on black; ssh black
hangs in act's clean ~/.ssh. Shared local-remote.sh + REMOTE_HOST=localhost
in those workflows. run-tests: await spawn exit, log and fail on any file.
2026-06-24 19:53:37 -04:00
Natalie
88ffdf1e35 fix(api/tests): isolate PHOTOS_DIR on CI runners; add designer migrations
Self-hosted runners inherit host quinn-api PHOTOS_DIR pointing at a checkout
path that does not exist during Forgejo actions, which made gallery-items
DELETE return 500. Test preload now always uses a temp photos dir. Include
designer-download-run migrations in the template superset.
2026-06-24 13:12:26 -04:00
Natalie
3af13f3df3 fix(api/test-db): declare template env before module-load ensureProcessDb 2026-06-24 11:39:49 -04:00
Natalie
6a155be0c9 perf(ci): template DB clone, parallel test workers, split CI/deploy queues
- Build one migrated template per run-tests invocation; per-file DBs clone
  via CREATE DATABASE TEMPLATE (~seconds) instead of replaying 148 migrations
- Run up to 4 test workers on CI (QUINN_TEST_WORKERS); sweep orphans once
- QUINN_CI_FAST smoke subset (12 files) on push/PR; full suite on dispatch
- ci-${{ ref }} concurrency separate from deploy-${{ ref }}; cancel stale runs
- Cache Playwright browsers on quinn.www deploy workflow
2026-06-24 03:59:48 -04:00
Natalie
889b11039d feat(prospect-qualification): OF-redirect decline matcher + rotation pool
Build-order steps 1-2 of docs/prospector-of-redirect-spec.md (pure functions,
no I/O, no send). classifyDecline() separates curious rate-askers (handled by
isBudgetBalker — quote, no redirect) from soft can't-afford vs lowball/haggle;
lowball wins ties (counter-number → disengage). rateAlreadyQuoted() is gate #1
(post-quote only, outbound-scan). Rotation pools are Quinn's verbatim approved
copy with a [link] token filled at staging time (never hardcoded). 31 tests.

LIVE sending, engine_drafts staging, of_redirected_at migration, and the
local-model/worker rails remain Quinn-gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 00:20:48 -04:00
Natalie
6c80e6cb12 feat(send-rate-limit): thread autoQueue through quinn.api + MCP client
Complete the autoQueue toggle wiring: the api facade getSendRateLimit/
setSendRateLimit and /m/messages/send-rate-limit GET/PUT now carry autoQueue,
and the MCP client's setSendRateLimit passes it. Pairs with the MCP
set_send_rate_limit tool's autoQueue input (added separately) and the
mac-sync send_rate_config.auto_queue column.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 21:38:51 -04:00
Natalie
26fa2733ec feat(quinn-messenger): MCP control for outbound send-rate cap
Wire the mac-sync send-rate cap (max N sends per window) through to the
quinn-messenger MCP so it's adjustable at runtime, routed through quinn.api
per the canonical messenger→quinn.api→mac-sync path.

- api: shared/mac-sync/send.ts gains getSendRateLimit/setSendRateLimit
  (direct call to mac-sync /admin/send-rate-limit, deadline-guarded);
  /m/messages/send-rate-limit GET/PUT surfaces them.
- mcp: get_send_rate_limit / set_send_rate_limit tools call those routes.

Backing cap + storage live in the mac-sync server (default 10/300s).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:56:40 -04:00
Natalie
c52dd84e40 feat: sync verified profiles with full list of platforms from quinn-my credentials (platforms_escort + content)
- Added AdultSearch and SkipTheGames (the remaining verified:true escort platforms from the credentials/platforms list).
- All content (OnlyFans etc) and relevant escort platforms from the user's quinn-my credentials list are now in verified_profiles with site logo as placeholder banner.
- Total 10 entries.
- Updated seed for consistency.
- Verified in public provider-config data.
2026-06-23 07:45:39 -04:00
Natalie
cbb0c8b893 feat: add OnlyFans, Fansly, ManyVids, MegaPersonals to verified profiles (with site logo as banner)
- Added 4 new rows via admin API (black) + psql (edge) so /banners now shows 8 platforms.
- Used /icon-512.png (website logo) as imgSrc for the new entries (triggers branded logo visual per previous requirement when custom banner not supplied).
- Updated seed-quinn-iter16.ts for dev/test consistency.
- Verified in provider-config data (local + public https).
- Platforms from user list + content/escort handoff data; URLs from canonical sources.
2026-06-23 07:30:46 -04:00
Natalie
98daf8def0 feat(provider-website): populate Verified on section with live verified platforms (Tryst + TS4Rent + TSEscorts + AdultLook)
- Added real verified profile rows to canonical (black) and edge (vps) quinn.verified_profiles via direct admin surface + psql for cache.
- Updated seed-quinn-iter16.ts with matching real data (e2e marker preserved).
- Verified via public /www/provider-config and admin surface.
- Legacy quinn_admin table also synced on edge for fallback paths.
- Note: some banner imgs use site photos until platform-specific embed banners are added; hrefs for non-Tryst may need minor URL tweak post-verify.
2026-06-23 06:36:04 -04:00
Natalie
bcd2d96a1f feat(quinn-admin): move default theme selector from hardcoded quinn.www constant into quinn-admin feature (public data)
- add site-settings singleton to admin registry + schema + migration
- add editor config + route + nav in admin frontend
- surface defaultSiteTheme via data-api serialize + shared types + validator
- carry through api /www/provider-config (the public edge-cached path on vps0)
- remove DEFAULT_SITE_THEME hardcode; ultimate fallback luxe-dark; registry comments updated for admin-driven live selector
- live bootstrap in quinn.www root + data hook to pick admin default without quinn.www rebuild (chrome + tokens update post-fetch)
- fixed incidental sortable test assertion to match current registry (pre-existing mismatch)
- other public hardcodes remain in deployment configs; see analysis

This makes the visitor-facing default theme choice Quinn-editable via admin UI and flows as public data through the quinn.api public surface (edge cacheable).
2026-06-23 04:34:35 -04:00
Natalie
71538d9f07 fix(api): set PHOTOS_DIR in test preload for gallery DELETE
Gallery item DELETE calls regenerateManifest(), which requires
PHOTOS_DIR. Point tests at a temp directory in global-setup so the
admin-gallery-items CRUD test does not 500 in CI.
2026-06-23 03:45:26 -04:00
Natalie
e8f5964fa2 fix(api): complete provider-config test migrations and isolate files
assembleProviderConfig now reads hero_strip_items; admin rate-cards,
site-text, and tour-stops tests were still on stale migration bundles.
www/payment-methods tests must query ?provider=quinn to match repo
defaults. Run each test file in its own bun process so the per-process
throwaway DB does not leak committed fixtures across files in CI.
2026-06-23 03:34:21 -04:00
Natalie
ea75da5654 fix(ci,api): repair test migrations and raise verify timeout
CI verify was failing on black because integration tests omitted migrations
added after provider-config and admin gallery evolved (payment_methods,
photo_css_traps, analytics_markers). Centralize those bundles and bump
the verify job to 45m with a 90s per-test timeout so the full DB suite
can finish on the single capacity-1 runner.
2026-06-23 02:31:33 -04:00
Natalie
27469549ea fix(ci): repair mcp-server GeoGranularity import and ci:status on macOS
Import GeoGranularity from geo.ts (not client.ts) so analytics MCP
typechecks. Tighten contact-form test mailer stub for
exactOptionalPropertyTypes. Replace grep -P in ./run ci:status with a
portable python parser against the Forgejo actions API.
2026-06-22 21:24:39 -04:00
Natalie
5f4d192e8b fix(ci,contact-form): run api tests in CI and assert notification from address
CI verify only typechecked — the contact-form refactor dropped the required
`from` on sendMail (bookings already sets it) and nothing failed. Add the full
@features/api suite to ci.yml and tighten the contact-form test to assert
`from` plus a fire-and-forget flush tick.
2026-06-22 21:19:39 -04:00
Natalie
5a499e2b60 fix(ftw): resolve short-link slugs case-insensitively
Normalize codes to lowercase on create and lookup so ftw.pw/s/OnlyFans
matches onlyfans. Reject mint requests that differ only by casing.
2026-06-22 07:52:00 -05:00
Natalie
372fff891f test: add unit tests for shared/timeout (withTimeout) to increase coverage of the reliable background email pattern
4 tests: resolves/rejects before timeout, timeout error, race safety.
2026-06-22 04:42:16 -05:00
Natalie
1b4dd36751 feat(notifications): make contact, VIP payment confirms, priority requests, and quote responses send emails reliably via background withTimeout + structured logging (decoupled from user actions)
- contact form: now uses same pattern as bookings (persist first, fire-and-forget bounded send)
- VIP unlock confirm (payments received, including wallet_topup): added decoupled email to Quinn on billingEntry write
- VIP priority requests: added notification on creation
- VIP quotes respond: improved from console.* to logger + withTimeout
- Extracted shared/timeout.ts (with unref) and updated bookings to use it

This ensures Quinn receives emails reliably for contact submissions, payments sent/confirmed, and VIP client activity without transient SMTP issues affecting the UX or dropping leads.
2026-06-22 02:40:30 -05:00
Natalie
fea472fe27 merge claude/intelligent-tharp-d0347c: vip prepaid balance M1.5 (top-up settlement + spend loop) plus unlock spine 2026-06-22 02:06:47 -05:00
Natalie
eae2f0ef04 feat(api/contact): idempotency_key on contact_submissions (Phase 2b / G9)
Additive nullable column + unique index + createContactSubmissionIdempotent
(ON CONFLICT DO NOTHING, returns existing row, skips notify email on replay).
Route reads optional Idempotency-Key header. Lets the edge outbox replay a
contact submission without creating a duplicate. Backward-compatible: direct
submissions (no key) insert normally. touring/waitlist already natural-idempotent
(UNIQUE(email,provider_slug) upsert), so contact is the only table needing this.
NB: hCaptcha is effectively disabled (frontend sends no token), so stale-token
replays are not rejected; if hCaptcha is ever enabled, add a trusted outbox-token
bypass for replays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 02:06:19 -05:00
Natalie
ae872609f1 feat(api/vip-wallet): include wallet entity types (pre-existing WIP from prior step) 2026-06-22 01:55:24 -05:00
Natalie
b947908e8f fix(api/vip): pass targetRef only when present to satisfy exactOptionalPropertyTypes 2026-06-22 01:54:24 -05:00
Natalie
bb0df73654 test(api/vip): cover the wallet top-up + spend loop 2026-06-22 01:53:21 -05:00
Natalie
bf4aa7a075 feat(api/vip): wallet top-up settlement + pay-from-balance routes 2026-06-22 01:52:45 -05:00
Natalie
4da8c9c286 feat(api/unlock-intent): add wallet_topup target type 2026-06-22 01:52:07 -05:00
Natalie
76ca3e02bf feat(api/vip-wallet): wallet balance + ledger entity 2026-06-22 01:51:27 -05:00
Natalie
fd74f16faa feat(analytics): sub-country geo — regions, cities, live 30m, MCP tools
Replace stubbed audience geography with session_fingerprints queries
(country/region/city, pathPattern, activityWindow for live). Surface
US states, cities on Audience/Overview, page detail, and dashboard
snapshot. Extend quinn-analytics MCP to v0.3.1 with geo_breakdown,
audience_geo_summary, and geo_enrichment_status.

Deploy dashboard/API from main checkout — see handoffs/analytics-geo.md.
MCP v0.3.1 already on black :3914.
2026-06-22 00:50:02 -05:00
Natalie
6647aca29e feat(analytics): add bluesky_post marker event type
Extend analytics timeline markers with manual Bluesky posts — same pattern
as instagram_post, with sky-blue chart lines and admin form option.
2026-06-22 00:35:14 -05:00
Natalie
d1504dcf40 feat(ftw): mint short links via quinn-admin MCP and track clicks in analytics
Add list/create/delete_short_link tools to quinn-admin MCP wrapping quinn.api
/admin/short-links. Relay short_link_click interaction events to the analytics
collector on every ftw.pw /s/:code redirect (alongside existing click_count).
2026-06-22 00:26:20 -05:00
Natalie
6551f78c91 fix(analytics): ensure analytics_markers owned by quinn_api for migrations
Black prod had the table owned by the quinn superuser role, which blocked
the build/release CHECK migration and crashlooped quinn-api. Idempotent OWNER
fix is applied in initial, build_release, and a follow-up migration.
2026-06-21 23:59:23 -05:00
Natalie
4684ddcac7 feat(analytics): auto-record build and release markers from CI
Add build/release event types, a service-token internal ingest route, and
Forgejo workflow steps that tag the analytics timeline after verify builds
and successful deploys. Dashboard chart shows gray build lines and green
release lines.
2026-06-21 23:41:53 -05:00
Natalie
e4468790f1 feat(quinn.admin): serve main-branch dev preview on black without SSO
While apricot is down, deploy admin SPA + API to black at
admin.quinn.black.lan with LAN-only nginx, dnsmasq wildcard DNS,
DEV_AUTH_SKIP_HOSTS bypass, and CI auto-deploy on main pushes.
2026-06-21 23:38:06 -05:00
Natalie
4f6b0daba8 test(api/vip): cover the unlock loop end-to-end
Some checks failed
CI / verify (push) Has been cancelled
Integration test against a real Postgres exercising the full M1 flow:
- methods list returns only vip_unlock_enabled rows and a sanitized shape
  (no legal-name / visibility / provider fields ever surface to the fan)
- a method not enabled for unlocks is rejected (CashApp gating)
- token header auth and service-token admin auth are enforced
- intent creation returns awaiting_payment + memo code + handle instructions
- admin confirm records exactly one paid ledger entry and is idempotent
  under a duplicate confirm (no double-bill)

6 tests, green in ~2s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 23:19:39 -05:00
Natalie
fcb577ebe7 feat(api/vip): wire unlock surface for intents + admin confirm
Adds the token-scoped VIP unlock surface and mounts it:
- client routes (X-VIP-Token header auth): list vip_unlock_enabled methods
  (mirrors the footer's managed list), create/list/get unlock intents with
  per-method payment instructions and a memo code for reconciliation.
- admin routes (service-token, guarded at /vip/unlock-admin/*): confirm a
  received payment -> records a paid vip_billing ledger entry exactly once via
  the atomic transition; cancel an open intent.

Registers unlockIntentMigrations after paymentMethodMigrations (FK ordering)
and adds the /vip/unlock-admin/* serviceTokenAuth guard alongside billing-admin.
Methods are off by default and only handle/label are surfaced — never a legal
name — so CashApp stays gated until the name change via a single admin toggle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 23:07:07 -05:00
Natalie
75ae6203fc feat(api/unlock-intent): add vip_unlock_intents entity
A fan-initiated request to pay for VIP content access — the spine of the
unlock flow. Settlement is polymorphic on the chosen payment method's kind
(peer_app: memo-matched manual confirm; crypto: BTCPay webhook in M2).

confirmUnlockIntent is atomic and returns { transitioned }: the UPDATE only
fires while the row is still awaiting_payment, so concurrent confirms (manual +
future webhook) cannot double-settle, and the caller gates the paid-ledger
insert on the transition so it runs exactly once. Includes idempotent
re-confirm, close (expire/cancel), and a bulk stale-expiry sweep.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 23:06:50 -05:00
Natalie
f048934e01 feat(api/payment-method): add vip_unlock_enabled availability axis
Adds two columns to payment_methods, managed on the same admin page that
feeds the quinn-www footer:
- vip_unlock_enabled: second availability axis (independent of visibility),
  controlling whether a method is offered for VIP unlock payments. Off by
  default so a fan can never be charged through an un-opted-in method.
- exposes_legal_name: advisory flag driving an admin warning (e.g. CashApp
  shows the account legal name on its own send screen); never rendered by us.

Migration is additive; the footer's visibility axis is untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 23:05:58 -05:00
Natalie
7fbc8696b1 feat(analytics): timeline markers (deploys, tour stops, content drops)
Add an analytics-marker entity (types/schema/repo + migration) and a
record-analytics-marker helper, with an admin CRUD surface + AnalyticsMarkersPage
(nav in AdminLayout). Auto-record markers when tour stops and content drops are
created/changed. Expose them via /analytics/markers (analytics-queries +
user-data router proxy + useMarkers hook + api types), and render them as
reference lines with a legend on the dashboard Overview trend chart.
2026-06-21 22:57:26 -05:00
Natalie
0eb69cab3c feat(promo-banner): expose animation on public surface + test
Include animation in the public /www promo-banner payload so the rotation can
render it, and cover create/update round-trip of the field in the repo test.
2026-06-21 17:36:45 -05:00