Commit graph

2688 commits

Author SHA1 Message Date
Natalie
236f0638d0 feat(vip/admin): SSO-gated /admin view with fan impersonation
Replicates quinn.www's AdminDevView pattern for the VIP app:
- VipAdminView at /admin lists VIP clients and offers Open as for each fan with
  a live token. Reaching /admin means an authenticated session (the nginx edge
  gates it to SSO, wired next).
- Open as calls the admin impersonate endpoint, then seeds the same session
  storage a real login would (vip_auth_ok + content key) plus an impersonation
  marker, and navigates to the fan's portal — which then skips the password
  screen (appState goes straight to unlocked).
- VipPortalPage shows a persistent ImpersonationBanner with a one-tap Exit that
  clears the session and returns to the roster.

contentKey from the endpoint is base64url, matching the verify contract, so the
fan's encrypted content decrypts under impersonation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 02:41:06 -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
49b5dec89b feat(quinn.www): SSO-gated /admin dev view; Theme Lab gated to it
Replace the ?theme-viewer opt-in with a hidden /admin route: the Theme Lab now
mounts ONLY on /admin (never on public pages). /admin is SSO-gated at the nginx
edge (auth_request to quinn SSO :3025 — unauth redirects to sso.transquinnftw.com)
and declared outside the route registry so it is absent from the sitemap; the
page sets noindex. Authenticated dev surface to preview themes without the full
admin panel. One-click set-as-site-default save is the next addition.
2026-06-24 04:05:26 -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
8c57818276 fix(quinn-my/mcp): verified-profile update verb PATCH→PUT to match API route
The API exposes PUT /admin/verified-profiles/:id (partial body accepted);
the MCP tool was issuing PATCH and 404ing on every reorder/update.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:52:13 -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
91f6dc2adb fix(promo-banner): make entire banner clickable, not just the pill
The whole bar is now a single anchor (CardLink) wrapping artwork, copy and
the CTA pill, so clicking anywhere navigates — eliminating the ~80px dead
zone that was the real cause of near-zero promo CTR. The pill becomes an
aria-hidden visual affordance; dismiss (X) stays outside the link so it
never triggers navigation. One folded aria-label + focus-visible outline
keep it accessible; hover lift now driven from the parent anchor. Thumb
bumped 44->48px so the image creative reads as the hero.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 22:31:39 -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
c572c1c18d fix(ci): repair typecheck failures blocking verify on main
quinn-messenger MCP set_send_rate_limit passes autoQueue (3rd arg).
Admin site-settings drops unsupported EditorField.help property.
my backend-api removes unused QUINN_API_URL/TOKEN module constants.
2026-06-23 21:37:14 -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
7e6c27c100 feat(quinn-my): add show_on_site toggle to platforms (SSOT from credentials) with admin toggle to list on public site
- Added migration for show_on_site boolean on platforms_escort and platforms_content.
- Updated platforms-data handler to support PATCH for the field, serialize it, and on toggle=true auto-create default verified_profile entry (using site logo) via quinn.api admin surface. This makes platforms the SSOT for the list, and the toggle the choice to 'list on site' in verified on / banners.
- Updated my frontend: Platform type, PlatformsPage (dots + table checkbox for 'Site'), PlatformModal (dots).
- The toggle in quinn-my PlatformsPage (the admin for the credentials/platforms list) now controls populating the public verified on section.
- Verified profiles remain for rich customization (custom img/desc/href per platform); defaults use logo per prior requirement.
- Env for quinn api added to my server for the sync call.
- Matches the platforms list from quinn-my credentials (escort+content) as source of truth for which to manage/toggle.
2026-06-23 13:20:24 -04:00
Natalie
c4d4ec5ecb docs(my-socials): scaffold socials feature (plan + composer UX docs)
Add the my-socials feature skeleton (backend-api/frontend-public/mcp-server/
shared dirs) with CLAUDE.md, README, PLAN.md, the general promo-graphic-composer
UX spec + HTML mockup, and the ts4rent avatar-overlay spec.
2026-06-23 13:19:58 -04:00
Natalie
efd3b2fcbf feat(quinn.my): platform graphic composer page
Add PlatformCreativePage (new-file / dimensions / asset placement / crop /
rotate), lazy-routed at /platforms/:name/creative on desktop and mobile, and
linked from PlatformsPage and the platform ad-copy editor.
2026-06-23 13:19:52 -04:00
Natalie
5769d5c874 feat(messenger): canonical domain messenger.transquinnftw.com
Align the messaging surface with other quinn.* subdomains (my, admin, data).
m.transquinnftw.com and m.quinn.apricot.lan now 301 to messenger.*.
App switcher id/subdomain updated to messenger; shared SAN cert expanded
on deploy.
2026-06-23 07:55:54 -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
9b3f4f9201 fix(analytics): guard .sessions?.current in useDataHealth to prevent 'can't access property current, o.sessions is undefined' crash in Audience/Traffic/Network pages (shape tolerance for compose rollout + defensive nulls) 2026-06-23 07:31:00 -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
30d9a082ae fix(banners): show website logo as placeholder banner image when no platform-provided imgSrc
- On /banners, if a VerifiedProfile has empty imgSrc (no banner from user/platform), render the site logo (/icon-512.png) as a branded visual placeholder inside linked or static frame.
- Introduced LogoPreview, LogoPlaceholder, LogoImg styled components for contained square logo with padding/bg.
- Removed redundant text-only ProfileLink (the logo image now provides the visual + click target when href present).
- Updated comments and logic in BannerItem.
- Always provides an image slot now for uniform card layout (real banner or site logo).
- Typecheck clean; e2e smoke test for /banners passes.
- Fallback only affects UI rendering (data can still omit imgSrc); matches request for transquinnftw.com/banners.
- Uses existing static icon from manifest/public for the 'logo of the website'.
2026-06-23 06:53:41 -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
a38920aae5 fix(analytics): restore sessions field on overview BFF response
composeOverview omitted sessions (and conversions) from the
DashboardOverview payload, so useDataHealth crashed accessing
sessions.current on Audience/Traffic/Network. Populate both from
session metrics and fall back to visitors in the hook.
2026-06-22 23:50:22 -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
2309a6a477 fix(my): unwrap credentials list envelope from quinn.api
GET /api/credentials now returns { total, credentials } via the quinn.api
proxy, but the dashboard still treated the body as a bare array and crashed
with .filter is not a function. Add a shared parser, unit test, and e2e guard.
2026-06-22 21:15:42 -04:00
Natalie
d8207f4c4f feat(provider-website): site-wide MagicCard hover pool with headless e2e
Introduce a shared magic score picker (geek vs sparkle by theme) wired across
public pages, balance rates incall/outcall columns, and fix pool math to track
actively mounted cards so async-loaded rate rows pick a real index. Adds
Playwright coverage for etiquette and rates hover animations.
2026-06-22 12:34:34 -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
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
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
445bfcae57 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.
New file: vip-feature-review-payments-security-ux.md (detailed review + mermaid-like text flows, step-by-step client/admin examples, data model notes, state/gaps).
Updated: README.md (links the review + high-level summary of the two UXes, security, integrations post-M1/M1.5 + MCP content work).
2026-06-22 02:50:44 -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
6bf26998d6 feat(mcp/quinn-admin): add MCP tools for uploading content (upload_gallery_photo) and making posts (content drops with buy links + published_at for retro platform dates)
Supports the VIP prepaid wallet channel: create posts whose gallery media can be unlocked via balance-purchase or intents (targetRef to drop).

Also synced payment_methods tools for vip_unlock_enabled.
2026-06-22 02:21:19 -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
ef339b3cea fix(provider-website): guard indexed access in EtiquettePage luminance helper
The surfaceLuminance regex-match access m[1] is string|undefined under
noUncheckedIndexedAccess; guard with ?? '0' so the flourish typechecks clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 01:04:26 -05:00
Natalie
da332a3c86 feat(provider-website): token-driven theming for EtiquettePage flourish
Make the closing flourish auto-tune per site theme (sunny pink+sunshine Malibu
card on barbie-light, glowing pink on dark moods) with glow intensity adapting
to surface luminance (surfaceLuminance helper).
2026-06-22 00:57:51 -05:00